#!/bin/env python
#
# This file is part of GNU Enterprise.
#
# GNU Enterprise is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2, or (at your option) any later version.
#
# GNU Enterprise is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with program; see the file COPYING. If not,
# write to the Free Software Foundation, Inc., 59 Temple Place
# - Suite 330, Boston, MA 02111-1307, USA.
#
# Copyright 2001-2002 Free Software Foundation
#
#
# FILE:
# gnuedtd
#
# DESCRIPTION:
# This script generates a DTD for the client apps based on
# their GParser markup
#
# NOTES:
#

import sys, string, time
from gnue.common import GParser, GTypecast

# This is simply to initialize gettext support :(
from gnue.common import GBaseApp

class GenerateDTD:
  def __init__ (self, tool, dest=sys.__stdout__):

    if tool == 'reports':
      from gnue.reports import GRParser, VERSION, PACKAGE
      xmlElements = GRParser.getXMLelements()
      topLevelElement = 'report'
      name = PACKAGE
      version = VERSION

    elif tool == 'forms':
      from gnue.forms import GFParser, VERSION, PACKAGE
      xmlElements = GFParser.getXMLelements()
      topLevelElement = 'form'
      name = PACKAGE
      version = VERSION

    elif tool == 'navigator':
      from gnue.navigator import GNParser, VERSION, PACKAGE
      xmlElements = GNParser.getXMLelements()
      topLevelElement = 'processes'
      name = PACKAGE
      version = VERSION

    elif tool == 'schema':
      from gnue.common.schema import GSParser
      from gnue.common import VERSION, PACKAGE
      xmlElements = GSParser.getXMLelements()
      topLevelElement = 'schema'
      name = 'GNUe Schema'
      version = VERSION

    elif tool == 'gnurpc':
      from gnue.common.commdrivers._parser import Parser
      xmlElements = Parser.getXMLelements()
      topLevelElement = 'gnurpc'
      name = 'GNURPC'
      from gnue.common import VERSION as version

    else:
      print "\nSyntax: gnuedtd <forms|reports|schema|navigator|gnurpc> [outfile.dtd]\n"
      sys.exit()

    self.parentMappings = {}
    self.xmlElements = xmlElements
    self.dest = dest
    self.processedTags = {}
    self.typecasts = {}

    for element in xmlElements.keys(): 
      # Get a mapping of all parent tags
      if xmlElements[element].has_key('ParentTags') and \
         xmlElements[element]['ParentTags'] != None:
        for parent in xmlElements[element]['ParentTags']: 
          if not self.parentMappings.has_key(parent): 
            self.parentMappings[parent] = {}
          self.parentMappings[parent][element] = \
             xmlElements[element].has_key('UsableBySiblings') and \
             xmlElements[element]['UsableBySiblings']

      # Find out what Typecasts are being used
      if xmlElements[element].has_key('Attributes'):
        for att in xmlElements[element]['Attributes'].keys():
          self.typecasts[xmlElements[element]['Attributes'][att]['Typecast']] \
                      = 'CDATA'

    dest.write ('<!-- %s DTD (Version %s) -->\n' % (name, version))

    dest.write ("""
<!--
     Copyright 2001-2002 Free Software Foundation

     This file is part of GNU Enterprise.

     GNU Enterprise is free software; you can redistribute it
     and/or modify it under the terms of the GNU General Public
     License as published by the Free Software Foundation; either
     version 2, or (at your option) any later version.

     GNU Enterprise is distributed in the hope that it will be
     useful, but WITHOUT ANY WARRANTY; without even the implied
     warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE. See the GNU General Public License for more details.

     You should have received a copy of the GNU General Public
     License along with program; see the file COPYING. If not,
     write to the Free Software Foundation, Inc., 59 Temple Place
     - Suite 330, Boston, MA 02111-1307, USA.
-->

<!-- Entities for characters/symbols -->
<!ENTITY % lt "&#38;#60;">
<!ENTITY % gt "&#62;">
<!ENTITY % amp "&#38;#38;">

<!--  =================================================  -->
<!--  Datatype entities to make DTD more human readable  -->
<!--  =================================================  -->

""")
    if self.typecasts.has_key (GTypecast.text):
      self.typecasts[GTypecast.text] = '%text;'
      dest.write('<!ENTITY % text "CDATA">\n\n')

    if self.typecasts.has_key (GTypecast.boolean):
      self.typecasts[GTypecast.boolean] = '%boolean;'
      dest.write('<!ENTITY % true "Y">\n'
                 '<!ENTITY % false "N">\n'
                 '<!ENTITY % boolean "(%true;|%false;) #IMPLIED">\n\n')

    if self.typecasts.has_key (GTypecast.number):
      self.typecasts[GTypecast.number] = '%number;'
      dest.write('<!ENTITY % number "NMTOKEN">\n'
                 '<!-- [-]?[0-9]+([.][0-9]+)? -->\n\n')

    if self.typecasts.has_key (GTypecast.whole):
      self.typecasts[GTypecast.whole] = '%whole;'
      dest.write('<!ENTITY % whole "NMTOKEN">\n'
                 '<!-- [0-9]+ -->\n\n')

    if self.typecasts.has_key (GTypecast.integer):
      self.typecasts[GTypecast.integer] = '%integer;'
      dest.write('<!ENTITY % integer "NMTOKEN">\n'
                 '<!-- [-]?[0-9]+ -->\n\n')

    if self.typecasts.has_key (GTypecast.name):
      self.typecasts[GTypecast.name] = '%name;'
      dest.write('<!ENTITY % name "NMTOKEN">\n'
                 '<!-- [A-Za-z]([A-Z][a-z][0-9][#$_-])* -->\n\n')

    if self.typecasts.has_key (GTypecast.names):
      self.typecasts[GTypecast.names] = '%names;'
      dest.write('<!ENTITY % names "CDATA">\n'
                 '<!-- (%name;)(,[ ]*(%name;))* -->\n\n')

    dest.write('\n')

    self.elementStack = [['Top level element',topLevelElement]]

    while len(self.elementStack) and \
              (self.elementStack!=[['Child elements of service']]):
      i = 1
      ##print "first loop: %s, %s " % \
      ##      (len(self.elementStack),self.elementStack)

      ## remove allready processed tags
      while i < len(self.elementStack[0]):
        if self.processedTags.has_key(self.elementStack[0][i]):
          ## print "Popping already used tag %s" % self.elementStack[0][i]
          self.elementStack[0].pop(i)
        else:
          i += 1

      if len (self.elementStack[0]) > 1:
        elements = self.elementStack[0][1:]
        elements.sort()
        dest.write('\n<!-- ' + '=' * (len(self.elementStack[0][0])) + ' -->\n')
        dest.write('<!-- %s -->\n' % self.elementStack[0][0])
        dest.write('<!-- ' + '=' * (len(self.elementStack[0][0])) + ' -->\n')
        for element in elements:
          self.handleElement(element)
        self.elementStack.pop(0)
      else:
        self.elementStack.pop(0)


    dest.write ("\n<!-- This DTD was created by gnuedtd\n"
                  "     Updated: %s   -->\n\n" \
       % ( time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(time.time()))))
    

  #
  # If a dictionary has a key, return value 
  # associated w/that key. Otherwise return dflt.
  #
  def default(self, dict, key, dflt):
    if dict.has_key(key): 
      return dict[key]
    else:
      return dflt


  #
  # Line wrap a string.  Note, this eliminates redundant spacing
  #
  def wrap(self, indent, text, firstindent=None): 
      if firstindent == None:
        firstindent = indent

      pos = 0
      rv = ""
      width = 78 - firstindent
      for word in string.split(text): 
        if (len(word) + pos) > width: 
          pos = 0
          rv = rv + "\n" + " " * indent
          width = 78 - indent

        pos = pos + len(word) + 1

        rv = rv  + word + " "

      return string.rstrip(rv) + "\n"


  #
  #
  #
  def handleElement(self, element): 
    if self.processedTags.has_key(element): 
      return

    self.processedTags[element] = 1

    #
    # Create the ELEMENT tag
    #
   # for i in self.xmlElements.keys():
    #  print "element: %s" % i

    deprecated = self.default(self.xmlElements[element],'Deprecated',0)
    if deprecated:
      if type(deprecated) != type(""):
        deprecated = ""
      else:
        deprecated = "[%s] " % deprecated

      self.dest.write(self.wrap(17,
         '<!-- DEPRECATED: Use of the <%s> element has been deprecated %s -->' % \
             (element, deprecated),0))

    if self.parentMappings.has_key(element):
      es = self.parentMappings[element].keys()
    else:
      es = []
    if len(es):
      es.insert(0,'Child elements of %s' % element)
      self.elementStack.append(es)


    descr = self.default(self.xmlElements[element],'Description','')
    if len(descr):
      self.dest.write(self.wrap(5,
        '<!-- %s -->' % (descr), 0))

    elemdef = "<!ELEMENT %s" % element
    indent = len(elemdef) + 2
    if self.parentMappings.has_key(element) and \
       len(self.parentMappings[element]): 
      children = []
      m = self.parentMappings[element].keys()
      m.sort()
      for child in m:
        required = self.default(self.xmlElements[child], 'Required', 0)
        single = self.default(self.xmlElements[child],'SingleInstance',0)
        if single and required:
          children.append("%s" % child)
        elif single:
          children.append("%s?" % child)
        elif required:
          children.append("%s+" % child)
        else:
          children.append("%s*" % child)

      if self.default(self.xmlElements[element], 'MixedContent', 0):
        children.append ('#PCDATA')

      elemdef = elemdef + (" (%s)" % string.join(children,', '))

    else:
      elemdef = elemdef + " EMPTY"

    elemdef = elemdef + ">\n"
    self.dest.write(self.wrap(indent, elemdef, firstindent=0))

    #
    # Create the ATTLIST tags for element
    #
    reqAndUniqAtts = []
    reqAtts = []
    otherAtts = []
    atts = self.default(self.xmlElements[element], 'Attributes',{}).keys()
    atts.sort()
    for att in atts:

      req = self.default(self.xmlElements[element]['Attributes'][att],
                         'Required',0)
      unq = self.default(self.xmlElements[element]['Attributes'][att],
                         'Unique',0)
      if req and unq:
        reqAndUniqAtts.append(att)
      elif req: 
        reqAtts.append(att)
      else:
        otherAtts.append(att)

    for atts in (reqAndUniqAtts, reqAtts, otherAtts):
      for att in atts:
        set = self.default(self.xmlElements[element]['Attributes'][att],
                         'ValueSet', None)
        refs = self.default(self.xmlElements[element]['Attributes'][att],
                         'References', None)
        dflt = self.default(self.xmlElements[element]['Attributes'][att],
                           'Default', None)
        unq = self.default(self.xmlElements[element]['Attributes'][att],
                           'Unique', None)
        req = self.default(self.xmlElements[element]['Attributes'][att],
                           'Required', None)

        attdef = "<!ATTLIST %s %s " % (element, att)

        deprecated = self.default(self.xmlElements[element]['Attributes'][att],
                           'Deprecated',0)
        if deprecated:
          if type(deprecated) != type(""):
            deprecated = ""
          else:
            deprecated = "\n[%s] " % deprecated

          self.dest.write(self.wrap(17,
            '  <!-- DEPRECATED: The "%s" attribute of "%s" '
            'has been deprecated %s -->' % (att, element, deprecated),0))


        descr = self.default(self.xmlElements[element]['Attributes'][att],
                         'Description','')
        if len(descr):
          self.dest.write(self.wrap(7,
            '  <!-- %s: %s -->' % (att, descr), 0))

        datatype = self.default(
             self.xmlElements[element]['Attributes'][att],
             'Typecast',GTypecast.text)

        if datatype == int:
          tokentype = "%number;"
        else:
          tokentype = "NMTOKEN"

        # handle bool attributes specially
        if datatype == GTypecast.boolean:
          self.dest.write(self.wrap(len(attdef),attdef +'%boolean;>',0))

        elif set != None:
          set = set.keys()
          set.sort()
          if len(set) == 1:
            indent = len(attdef)
            attdef = attdef + '%s #FIXED "%s">' \
                % (self.typecasts[datatype], set[0])
          else:
            attdef = attdef + "("
            indent = len(attdef)
            attdef = attdef + "%s)" % string.join(set, ' | ')
            if dflt != None and len(dflt):
              attdef = attdef + ' "%s">' % dflt
            else:
              attdef = attdef + ' #IMPLIED>'
          self.dest.write(self.wrap(indent, attdef, 0))

        elif refs != None:
          indent = len(attdef)
          if req:
            attdef = attdef + ' IDREF #REQUIRED>'
          else:
            attdef = attdef + ' IDREF #IMPLIED>'
          self.dest.write(self.wrap(indent, attdef, 0))

        elif unq:
          indent = len(attdef)
          if req:
            attdef = attdef + ' ID #REQUIRED>'
          else:
            attdef = attdef + ' ID #IMPLIED>'
          self.dest.write(self.wrap(indent, attdef, 0))

        elif dflt != None:
          indent = len(attdef)
          attdef = attdef + ' %s "%s">' \
              % (self.typecasts[datatype], dflt)
          self.dest.write(self.wrap(indent, attdef, 0))

        else:
          indent = len(attdef)
          if req: 
            attdef = attdef + ' %s #REQUIRED>' \
              % (self.typecasts[datatype]) 
          else: 
            attdef = attdef + ' %s #IMPLIED>' \
              % (self.typecasts[datatype]) 
          self.dest.write(self.wrap(indent, attdef, 0))
          
    
    self.dest.write ("\n")

if len(sys.argv) > 1:
  try:
    dest = open(sys.argv[2],'w')
  except IndexError:
    dest = sys.stdout
  GenerateDTD(sys.argv[1], dest)
  dest.close()
else:
  GenerateDTD(None)
