Orb/python/orb/XSDToPackageDef.py
changeset 4 468f4c8d3d5b
equal deleted inserted replaced
3:d8fccb2cd802 4:468f4c8d3d5b
       
     1 # Copyright (c) 2007-2010 Nokia Corporation and/or its subsidiary(-ies) All rights reserved.
       
     2 # This component and the accompanying materials are made available under the terms of the License 
       
     3 # "Eclipse Public License v1.0" which accompanies this distribution, 
       
     4 # and is available at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     5 #
       
     6 # Initial Contributors:
       
     7 # Nokia Corporation - initial contribution.
       
     8 #
       
     9 # Contributors:
       
    10 #
       
    11 # Description:
       
    12 # Checks links in DITA XML and reports issues.
       
    13 
       
    14 """
       
    15 Created on 24 May 2010
       
    16 
       
    17 @author: p2ross
       
    18 """
       
    19 import os
       
    20 import sys
       
    21 import logging
       
    22 #import pprint
       
    23 #import fnmatch
       
    24 #import re
       
    25 import string
       
    26 import time
       
    27 from optparse import OptionParser#, check_choice
       
    28 try:
       
    29     from xml.etree import cElementTree as etree
       
    30 except ImportError:
       
    31     from xml.etree import ElementTree as etree
       
    32 #import multiprocessing
       
    33 
       
    34 __version__ = "0.1.0"
       
    35 
       
    36 
       
    37 class XsdBase(object):
       
    38     PREFIX = '{http://www.w3.org/2001/XMLSchema}'
       
    39     def _addSchemaPrefix(self, theTag):
       
    40         return '%s%s' % (self.PREFIX, theTag)
       
    41     
       
    42     def _tagSchemaMatches(self, theN, theTag):
       
    43         return theN.tag == self._addSchemaPrefix(theTag)
       
    44     
       
    45     def _tagSchemaMatchesList(self, theN, theTagS):
       
    46         for aTag in theTagS:
       
    47             if theN.tag == self._addSchemaPrefix(aTag):
       
    48                 return True
       
    49         return False
       
    50     
       
    51     def _stripSchemaPrefix(self, theN):
       
    52         """Returns theNode.tag without the schema prefix."""
       
    53         return theN.tag[len(self.PREFIX):]
       
    54 
       
    55 class XsdNodeBase(XsdBase):
       
    56     PREFIX = '{http://www.w3.org/2001/XMLSchema}'
       
    57     def __init__(self, theN):
       
    58         super(XsdNodeBase, self).__init__()
       
    59         self._name = theN.get('name')
       
    60         self._ref = theN.get('ref')
       
    61         # This will be prefix+element
       
    62         self._tag = theN.tag
       
    63         self._min = int(theN.get('minOccurs') or 1)
       
    64         if theN.get('maxOccurs') == 'unbounded':
       
    65             self._max = -1
       
    66         else:
       
    67             self._max = int(theN.get('maxOccurs') or 1)
       
    68         logging.debug(
       
    69             'XsdNodeBase.__init__(): tag=%s, name=%s, ref=%s, min=%d, max=%d',
       
    70             self.tag,
       
    71             self._name,
       
    72             self._ref,
       
    73             self._min,
       
    74             self._max,
       
    75             )
       
    76     
       
    77     @property
       
    78     def tag(self):
       
    79         return self._tag[len(self.PREFIX):]
       
    80     
       
    81     @property
       
    82     def attrRef(self):
       
    83         return self._ref
       
    84     
       
    85     @property
       
    86     def attrName(self):
       
    87         return self._name
       
    88     
       
    89     @property
       
    90     def isUnbounded(self):
       
    91         return self._max == -1
       
    92     
       
    93     @property
       
    94     def minOccurs(self):
       
    95         return self._min
       
    96 
       
    97     @property
       
    98     def maxOccurs(self):
       
    99         return self._max
       
   100     
       
   101     @property
       
   102     def boundedString(self):
       
   103         """Returns a natural language description of bounds."""
       
   104         if self._min == 1 and self._max == 1:
       
   105             return ''
       
   106         elif self._min == 0:
       
   107             if self._max == 1:
       
   108                 # minOccurs="0" -> 'optional'
       
   109                 return 'optional'
       
   110             elif self.isUnbounded:
       
   111                 # minOccurs="0" maxOccurs="unbounded" -> 'any number'
       
   112                 return 'any number'
       
   113             else:
       
   114                 # minOccurs="0" maxOccurs="42" -> 'up to 42'
       
   115                 return 'up to %d' % self._max
       
   116         elif self.isUnbounded:
       
   117             # minOccurs="17" maxOccurs="unbounded" -> 'at least 17'
       
   118             return 'at least %d' % self._min
       
   119         # minOccurs="17" maxOccurs="42" -> 'at least 17 and up to 42'
       
   120         return 'at least %d, and up to %d' % (self._min, self._max)
       
   121 
       
   122 class Attribute(XsdNodeBase):
       
   123     """Represents an element describing an attribute."""
       
   124     def __init__(self, theN):
       
   125         super(Attribute, self).__init__(theN)
       
   126         assert(self._tagSchemaMatches(theN, 'attribute'))
       
   127         self._default = theN.get('default')
       
   128         self._use = theN.get('use')
       
   129         
       
   130     @property
       
   131     def default(self):
       
   132         return self._default
       
   133 
       
   134     @property
       
   135     def use(self):
       
   136         return self._use
       
   137 
       
   138 class RefBase(XsdNodeBase):
       
   139     """Represents a reference to a group or element"""
       
   140     def __init__(self, theN):
       
   141         super(RefBase, self).__init__(theN)
       
   142         assert(self._tagSchemaMatchesList(theN, ('group', 'element')))
       
   143         assert(self.attrRef is not None)
       
   144 
       
   145     @property
       
   146     def isSequence(self):
       
   147         return False
       
   148 
       
   149 class RefSequence(XsdNodeBase):
       
   150     """Holds information on an xs:all, xs:choice or xs:sequence."""
       
   151     def __init__(self, theN):
       
   152         super(RefSequence, self).__init__(theN)
       
   153         assert(self._tagSchemaMatchesList(theN, ('all', 'choice', 'sequence'))), 'Tag %s not in list' % theN.tag
       
   154         # List of class RefBase or, recursively, a class RefSequence
       
   155         self._refS = []
       
   156         self._type = self.tag
       
   157         for aChild in theN.getchildren():
       
   158             if not self._tagSchemaMatchesList(aChild, ('group', 'all', 'choice', 'sequence')):
       
   159                 continue
       
   160             #self._type = self._stripSchemaPrefix(aChild)
       
   161             if self._tagSchemaMatches(aChild, 'group'):
       
   162                 self._refS.append(RefBase(aChild))
       
   163             elif self._tagSchemaMatchesList(
       
   164                         aChild,
       
   165                         ('choice', 'sequence', 'any',)
       
   166                     ):
       
   167                 self._refS.append(RefSequence(aChild))
       
   168 
       
   169     def genRefs(self):
       
   170         """Generates the list of RefBase or RefSequence"""
       
   171         for aRef in self._refS:
       
   172             yield aRef
       
   173 
       
   174     @property
       
   175     def numRefs(self):
       
   176         return len(self._refS)
       
   177     
       
   178     @property
       
   179     def refs(self):
       
   180         return self._refS
       
   181     
       
   182     @property
       
   183     def isSequence(self):
       
   184         return True
       
   185 
       
   186     def qualifierString(self):
       
   187         if self._type == 'all':
       
   188             return '(any number, any order)'
       
   189         elif self._type == 'choice':
       
   190             return '(any number)'
       
   191         elif self._type == 'sequence':
       
   192             return ''
       
   193         return 'Unknown'
       
   194     
       
   195     def joinString(self):
       
   196         if self._type == 'all':
       
   197             return ' or '
       
   198         elif self._type == 'choice':
       
   199             return ' or '
       
   200         elif self._type == 'sequence':
       
   201             return ' then '
       
   202         return ''
       
   203     
       
   204     @property
       
   205     def seqType(self):
       
   206         return self._type
       
   207 
       
   208 class DefBase(XsdNodeBase):
       
   209     """Represents a definition of a group, element or complexType"""
       
   210     def __init__(self, theN):
       
   211         super(DefBase, self).__init__(theN)
       
   212         assert(self._tagSchemaMatchesList(theN, ('group', 'element', 'complexType'))), 'Tag %s not in list' % theN.tag
       
   213         assert(self.attrName is not None or self._tagSchemaMatches(theN, 'complexType'))
       
   214         # List of class RefBase of class RefSequence
       
   215         self._refS = []
       
   216 
       
   217     def genRefs(self):
       
   218         """Generates the list of RefBase"""
       
   219         for aRef in self._refS:
       
   220             yield aRef
       
   221 
       
   222     @property
       
   223     def numRefs(self):
       
   224         return len(self._refS)
       
   225     
       
   226     @property
       
   227     def refs(self):
       
   228         return self._refS
       
   229             
       
   230 class GroupDef(DefBase):
       
   231     """Represents the definition of a group i.e. <xs:group name="...">...</xs:group>.
       
   232     See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group"""
       
   233     def __init__(self, theN):
       
   234         super(GroupDef, self).__init__(theN)
       
   235         assert(theN.tag == self._addSchemaPrefix('group'))
       
   236         # http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group
       
   237         # Content: (annotation?, (all | choice | sequence)?)
       
   238         self._type = None
       
   239         for aChild in theN.getchildren():
       
   240             if self._tagSchemaMatchesList(aChild, ('all', 'choice', 'sequence')):
       
   241                 self._type = self._stripSchemaPrefix(aChild)
       
   242             else:
       
   243                 # Unrecognised element or annotation
       
   244                 continue
       
   245             if self._type is not None:
       
   246                 # Read grand children and break
       
   247                 for aGrandChild in aChild.getchildren():
       
   248                     if aGrandChild.get('ref') is not None \
       
   249                     and self._tagSchemaMatchesList(
       
   250                                 aGrandChild,
       
   251                                 (
       
   252                                     'element',
       
   253                                     'group',
       
   254                                     #'choice',
       
   255                                     #'sequence',
       
   256                                     #'any',
       
   257                                 )):
       
   258                         self._refS.append(RefBase(aGrandChild))
       
   259                 break
       
   260         
       
   261     def __str__(self):
       
   262         #print 'TRACE:', self._refS
       
   263         return self.joinString().join(['%s' % r.attrRef for r in self._refS]) \
       
   264             + ' ' \
       
   265             + self.qualifierString() 
       
   266     
       
   267     def qualifierString(self):
       
   268         if self._type == 'all':
       
   269             return '(any number, any order)'
       
   270         elif self._type == 'choice':
       
   271             return '(any number)'
       
   272         elif self._type == 'sequence':
       
   273             return ''
       
   274         return 'Unknown'
       
   275     
       
   276     def joinString(self):
       
   277         if self._type == 'all':
       
   278             return ' or '
       
   279         elif self._type == 'choice':
       
   280             return ' or '
       
   281         elif self._type == 'sequence':
       
   282             return ' then '
       
   283         return ''
       
   284     
       
   285     @property
       
   286     def groupType(self):
       
   287         return self._type
       
   288     
       
   289 class ComplexTypeDef(DefBase):#XsdNodeBase):
       
   290     """Represents the definition of a complexType definition i.e. <xs:complexType name="...">...</xs:group>.
       
   291     See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-complexType"""
       
   292     def __init__(self, theN):
       
   293         super(ComplexTypeDef, self).__init__(theN)
       
   294         assert(theN.tag == self._addSchemaPrefix('complexType'))
       
   295         # http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-complexType
       
   296         # Simplified content: ((group | all | choice | sequence)?
       
   297         self._type = None
       
   298         # Get xs:attribute
       
   299         self._attrS = []
       
   300         for aChild in theN.getchildren():
       
   301             # We only handle complexType
       
   302             if not self._tagSchemaMatchesList(
       
   303                     aChild,
       
   304                     ('group', 'all', 'choice', 'sequence', 'attribute')):
       
   305                 # Unrecognised element or annotation
       
   306                 continue
       
   307             self._type = self._stripSchemaPrefix(aChild)
       
   308             if self._tagSchemaMatches(aChild, 'group'):
       
   309                 self._refS.append(RefBase(aChild))
       
   310             elif self._tagSchemaMatchesList(
       
   311                                 aChild,
       
   312                                 ('choice', 'sequence', 'any',),
       
   313                                 ):
       
   314                 self._refS.append(RefSequence(aChild))
       
   315             elif self._tagSchemaMatches(aChild, 'attribute'):
       
   316                 self._attrS.append(Attribute(aChild))
       
   317 
       
   318     @property
       
   319     def groupType(self):
       
   320         return self._type
       
   321     
       
   322     def namedAttribute(self, theName):
       
   323         """Returns an Attribute object or None."""
       
   324         for anA in self._attrS:
       
   325             if anA.attrName == theName:
       
   326                 return anA
       
   327 
       
   328 class ElementDef(DefBase):
       
   329     """Represents the definition of a element definition i.e. <xs:element name="...">...</xs:group>.
       
   330     See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group"""
       
   331     def __init__(self, theN):
       
   332         super(ElementDef, self).__init__(theN)
       
   333         assert(theN.tag == self._addSchemaPrefix('element'))
       
   334         # http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-element
       
   335         # Simplified content: (annotation?, ((simpleType | complexType)?
       
   336         self._type = None
       
   337         self._complexType = None
       
   338         for aChild in theN.getchildren():
       
   339             # We only handle complexType
       
   340             if not self._tagSchemaMatchesList(aChild, ('complexType',)):
       
   341                 # Unrecognised element or annotation
       
   342                 continue
       
   343             # Process a <complexType>
       
   344             self._complexType = ComplexTypeDef(aChild)
       
   345             break
       
   346     
       
   347     @property
       
   348     def complexType(self):
       
   349         return self._complexType
       
   350 
       
   351 class GroupRef(RefBase):
       
   352     """Represents a reference of a group i.e. <xs:group ref="..." .../>.
       
   353     See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group"""
       
   354     def __init__(self, theN):
       
   355         super(GroupRef, self).__init__(theN)
       
   356         assert(theN.tag == self._addSchemaPrefix('group'))
       
   357 
       
   358 class ElementRef(RefBase):
       
   359     """Represents a reference to an element i.e. <xs:element ref="..." .../>.
       
   360     See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group"""
       
   361     def __init__(self, theN):
       
   362         super(ElementRef, self).__init__(theN)
       
   363         assert(theN.tag == self._addSchemaPrefix('element'))
       
   364 
       
   365 class XsdToPackageDef(XsdBase):
       
   366     """Create a PackageDef.xml file from a set of XSD files."""
       
   367     DUMP_PREFIX = '  '
       
   368     def __init__(self, thePrefix):#, theOutFile=None, theTitle=''):
       
   369         super(XsdToPackageDef, self).__init__()
       
   370         # Maps of group definitions and references
       
   371         self._groupDefMap = {}
       
   372         self._groupRefMap = {}
       
   373         # Maps of element definitions and references
       
   374         self._elemDefMap = {}
       
   375         self._elemRefMap = {}
       
   376         # Top level complex types
       
   377         self._complexTypeMap = {}
       
   378         # Element prefix
       
   379         self._prefix = thePrefix
       
   380             
       
   381     ##########################
       
   382     # Section: Add an XSD file
       
   383     ##########################
       
   384     def addXsdFile(self, thePath):
       
   385         """Read an XSD and add it to the representation."""
       
   386         t = etree.parse(thePath)
       
   387         r = t.getroot()
       
   388         self._addGroups(r)
       
   389         self._addElements(r)
       
   390         self._addComplexTypes(r)
       
   391     
       
   392     def addXsdPath(self, thePath):
       
   393         if os.path.isfile(thePath):
       
   394             if os.path.splitext(thePath)[1].lower() == '.xsd':
       
   395                 self.addXsdFile(thePath)
       
   396         elif os.path.isdir(thePath):
       
   397             for aName in os.listdir(thePath):
       
   398                 self.addXsdPath(os.path.join(thePath, aName))
       
   399 
       
   400     def _addGroups(self, root):
       
   401         """Adds to the group maps."""
       
   402         for anE in root.getchildren():
       
   403             if self._tagSchemaMatches(anE, 'group'):
       
   404                 if anE.get('name') is not None:
       
   405                     self._groupDefMap[anE.get('name')] = GroupDef(anE)
       
   406                 elif anE.get('ref') is not None:
       
   407                     self._groupRefMap[anE.get('ref')] = GroupRef(anE)
       
   408                 
       
   409     def _addElements(self, root):
       
   410         """Adds to the element maps."""
       
   411         for anE in root.getchildren():
       
   412             if self._tagSchemaMatches(anE, 'element'):
       
   413                 if anE.get('name') is not None:
       
   414                     self._elemDefMap[anE.get('name')] = ElementDef(anE)
       
   415                 elif anE.get('ref') is not None:
       
   416                     self._elemRefMap[anE.get('ref')] = ElementRef(anE)
       
   417     
       
   418     def _addComplexTypes(self, root):
       
   419         for anE in root.getchildren():
       
   420             if self._tagSchemaMatches(anE, 'complexType'):
       
   421                 self._complexTypeMap[anE.get('name')] = ComplexTypeDef(anE)
       
   422     ##########################
       
   423     # End: Add an XSD file
       
   424     ##########################
       
   425 
       
   426     ##########################
       
   427     # Section: Query the IR
       
   428     ##########################
       
   429     def elemContains(self, theElemName):
       
   430         """Returns a list of immediate child elements that this element contains."""
       
   431         logging.debug('elemContains(%s)' % theElemName)
       
   432         retVal = []
       
   433         if not self._elemDefMap.has_key(theElemName):
       
   434             return retVal
       
   435         myElem = self._elemDefMap[theElemName]
       
   436         if myElem.complexType is not None:
       
   437             logging.debug('elemContains(): complexType: %s', myElem.complexType)
       
   438             #print 'myElem', myElem
       
   439             for aRef in myElem.complexType.genRefs():
       
   440                 logging.debug('elemContains(): complexType tag: %s, attrRef: %s',
       
   441                               aRef.tag,
       
   442                               aRef.attrRef,
       
   443                         )
       
   444                 self._addFromComplexTypeSequence(aRef, retVal)
       
   445 #                myRef = aRef.attrRef
       
   446 #                #print 'myRef', myRef
       
   447 #                #if self._elemDefMap.has_key(myRef):
       
   448 #                #    retVal.append(myRef)
       
   449 #                if self._groupDefMap.has_key(myRef):
       
   450 #                    self._addContainsGroup(myRef, retVal)
       
   451 #                else:
       
   452 #                    retVal.append(myRef)
       
   453         logging.debug('elemContains(%s) returns %s', theElemName, str(retVal))
       
   454         return retVal
       
   455     
       
   456     def _addFromComplexTypeSequence(self, theRefObj, theList):
       
   457         """Recursively add containing elements."""
       
   458         myRef = theRefObj.attrRef
       
   459         logging.debug('_addFromComplexTypeSequence(%s):', myRef) 
       
   460         if theRefObj.isSequence:
       
   461             for aSubRef in theRefObj.refs:
       
   462                 logging.debug('_addFromComplexTypeSequence(): recursing on: %s',
       
   463                               myRef)
       
   464                 self._addFromComplexTypeSequence(aSubRef, theList)
       
   465         elif myRef is not None:
       
   466             if self._groupDefMap.has_key(myRef):
       
   467                 self._addContainsGroup(myRef, theList)
       
   468             else:
       
   469                 logging.debug('_addFromComplexTypeSequence(): adding: %s',
       
   470                               myRef)
       
   471                 theList.append(myRef)
       
   472     
       
   473     def _addContainsGroup(self, theRef, theList):
       
   474         logging.debug('_addContainsGroup(%s)' % theRef)
       
   475         assert(self._groupDefMap.has_key(theRef))
       
   476         for aRef in self._groupDefMap[theRef].genRefs():
       
   477             #print 'TRACE: aRef', aRef.tag
       
   478             logging.debug('_addContainsGroup(): aRef.tag: %s aRef.attrRef: %s' \
       
   479                           % (aRef.tag, aRef.attrRef))
       
   480             if aRef.tag == 'element':
       
   481                 theList.append(aRef.attrRef)
       
   482             elif aRef.tag == 'group' and self._groupDefMap.has_key(aRef.attrRef):
       
   483                 self._addContainsGroup(aRef.attrRef, theList)
       
   484 
       
   485     ##########################
       
   486     # End: Query the IR
       
   487     ##########################
       
   488     
       
   489     ##########################
       
   490     # Section: Debug/trace
       
   491     ##########################
       
   492     def dump(self, s=sys.stdout):
       
   493         s.write(' Group definitions '.center(75, '-')+'\n')
       
   494         keyS = self._groupDefMap.keys()
       
   495         keyS.sort()
       
   496         for k in keyS:
       
   497             s.write('%40s : %s\n' % (k, self._groupDefMap[k]))
       
   498         s.write(' EOL '.center(75, '-')+'\n\n') 
       
   499         s.write(' Group references '.center(75, '-')+'\n') 
       
   500         keyS = self._groupRefMap.keys()
       
   501         keyS.sort()
       
   502         for k in keyS:
       
   503             s.write('%40s : %s\n' % (k, self._groupRefMap[k]))
       
   504         s.write(' EOL '.center(75, '-')+'\n\n') 
       
   505         s.write(' Element definitions '.center(75, '-')+'\n') 
       
   506         keyS = self._elemDefMap.keys()
       
   507         keyS.sort()
       
   508         for k in keyS:
       
   509             s.write('%40s : %s\n' % (k, self._elemDefMap[k]))
       
   510         s.write(' EOL '.center(75, '-')+'\n\n') 
       
   511         s.write(' Element references '.center(75, '-')+'\n') 
       
   512         keyS = self._elemRefMap.keys()
       
   513         keyS.sort()
       
   514         for k in keyS:
       
   515             s.write('%40s : %s\n' % (k, self._elemRefMap[k]))
       
   516         s.write(' EOL '.center(75, '-')+'\n\n') 
       
   517         s.write(' Complex types '.center(75, '-')+'\n') 
       
   518         keyS = self._complexTypeMap.keys()
       
   519         keyS.sort()
       
   520         for k in keyS:
       
   521             s.write('%40s : %s\n' % (k, self._complexTypeMap[k]))
       
   522         s.write(' EOL '.center(75, '-')+'\n\n') 
       
   523         s.write(' Parent to child relationship '.center(75, '-')+'\n')
       
   524         # A map {element_name : [parent element, ...], ...}
       
   525         myPcMap = self._retContainedByMap()
       
   526         keyS = myPcMap.keys()
       
   527         keyS.sort()
       
   528         for k in keyS:
       
   529             s.write('Child: %s\n' % k)
       
   530             s.write('    Contained by: %s\n' % myPcMap[k])
       
   531         s.write(' EOL '.center(75, '-')+'\n\n') 
       
   532         # Run through the elements
       
   533         keyS = self._elemDefMap.keys()
       
   534         keyS.sort()
       
   535         for k in keyS:
       
   536             myElem = self._elemDefMap[k]
       
   537             s.write('Element: %s\n' % k)
       
   538             if myElem.complexType is not None:
       
   539                 for aRef in myElem.complexType.genRefs():
       
   540                     #print 'TRACE: aRef', aRef
       
   541                     if aRef.isSequence:
       
   542                         self._dumpSequenceRef(s, aRef, level=1)
       
   543                     else:
       
   544                         self._dumpRef(s, aRef, level=1)
       
   545             else:
       
   546                 s.write('  No complexType\n')
       
   547             s.write('\n')
       
   548             
       
   549     def _dumpRef(self, s, theRef, level=1):
       
   550         assert(not theRef.isSequence)
       
   551         if self._groupDefMap.has_key(theRef.attrRef):
       
   552             s.write('%s%s %s [%s]\n' \
       
   553                     % (self.DUMP_PREFIX*level,
       
   554                        theRef.attrRef,
       
   555                        '(group)',
       
   556                        theRef.boundedString,
       
   557                        #self._groupDefMap[aRef.attrRef].boundedString,
       
   558                     )
       
   559                 )
       
   560             self._dumpGroupRef(s, theRef.attrRef, level)
       
   561             #for aGm in self._groupDefMap[aRef.attrRef].genRefs():
       
   562             #    print '  <%s> %s %s %s' \
       
   563             #        % (aGm.tag, aGm.attrName, aGm.attrRef, aGm.boundedString)
       
   564         elif self._elemDefMap.has_key(theRef.attrRef):
       
   565             s.write('%s%s %s\n' % (self.DUMP_PREFIX*level, theRef.attrRef, '(element)'))
       
   566         elif self._complexTypeMap.has_key(theRef.attrRef):
       
   567             s.write('%s%s %s\n' % (self.DUMP_PREFIX*level, theRef.attrRef, '(complex)'))
       
   568         else:
       
   569             s.write('%s%s %s\n' % (self.DUMP_PREFIX*level, theRef.attrRef, '(not in my IR)'))
       
   570 
       
   571     def _dumpGroupRef(self, s, theR, level=1):
       
   572         """Dump out references to groups, recursively using self._groupDefMap."""
       
   573         assert(self._groupDefMap.has_key(theR))
       
   574         if self._groupDefMap[theR].numRefs == 1 \
       
   575         and self._groupDefMap[theR].refs[0].tag == 'element':
       
   576             s.write('%sELEMENT<%s> %s %s "%s" [%s]\n' \
       
   577                     % (
       
   578                        self.DUMP_PREFIX*level,
       
   579                        self._groupDefMap[theR].refs[0].tag,
       
   580                        self._groupDefMap[theR].refs[0].attrName,
       
   581                        self._groupDefMap[theR].refs[0].attrRef,
       
   582                        self._groupDefMap[theR].refs[0],
       
   583                        self._groupDefMap[theR].refs[0].boundedString,
       
   584                        )
       
   585                 )
       
   586         else:
       
   587             s.write('%sGROUP  <%s>%s\n' \
       
   588                 % (self.DUMP_PREFIX*level, self._groupDefMap[theR].tag, self._groupDefMap[theR]))
       
   589             for aGm in self._groupDefMap[theR].genRefs():
       
   590                 #print '%s<%s> %s %s "%s"' \
       
   591                 #    % (self.DUMP_PREFIX*level, aGm.tag, aGm.attrName, aGm.attrRef, aGm)#aGm.boundedString)
       
   592                 if aGm.tag == 'group' \
       
   593                 and aGm.attrRef \
       
   594                 and self._groupDefMap.has_key(aGm.attrRef):
       
   595                     self._dumpGroupRef(s, aGm.attrRef, level+1)
       
   596                 elif aGm.tag == 'element':
       
   597                     s.write('%sCHLDELE<%s> %s %s "%s"\n' \
       
   598                         % (self.DUMP_PREFIX*level, aGm.tag, aGm.attrName, aGm.attrRef, aGm))#aGm.boundedString)
       
   599 
       
   600     def _dumpSequenceRef(self, s, theR, level=1):
       
   601         """Dump out sequences recursively."""
       
   602         assert(theR.isSequence)
       
   603         for i, aRef in enumerate(theR.genRefs()):
       
   604             if i > 0:
       
   605                 s.write('%s[%s]\n' % (self.DUMP_PREFIX*level, theR.joinString()))
       
   606             #print '%sTRACE: _dumpSequenceRef aRef: %s' % (self.DUMP_PREFIX*level, aRef)
       
   607             if aRef.isSequence:
       
   608                 self._dumpSequenceRef(s, aRef, level=level+1)
       
   609             else:
       
   610                 self._dumpRef(s, aRef, level=level+1)
       
   611         s.write('%s[%s]\n' % (self.DUMP_PREFIX*level, theR.qualifierString()))
       
   612     ##########################
       
   613     # End: Debug/trace
       
   614     ##########################
       
   615         
       
   616     ##########################
       
   617     # Section: Output
       
   618     ##########################
       
   619     def _writeHeader(self, theS, theTitle):
       
   620         self._writeLine(theS, '<?xml version="1.0" encoding="UTF-8"?>')
       
   621         self._writeLine(theS, """<!DOCTYPE reference PUBLIC "-//OASIS//DTD DITA Reference//EN" "../../dtd/reference.dtd">""")
       
   622         self._writeLine(theS, """<reference id="%sapiref" xml:lang="en-us">""" % self._prefix)
       
   623         self._writeLine(theS, '<title>%s</title>' % theTitle)
       
   624         
       
   625     def _writeLine(self, theS, theL):
       
   626         theS.write(theL)
       
   627         theS.write('\n')
       
   628         
       
   629     def _write(self, theS, theStr):
       
   630         theS.write(theStr)
       
   631 
       
   632     def close(self, theS):
       
   633         self._writeLine(theS, '</reference>')
       
   634         theS.close()
       
   635     
       
   636     def _retContainedByMap(self):
       
   637         """Returns a map {element_name : [parent element, ...], ...}."""
       
   638         retMap = {}
       
   639         keyS = self._elemDefMap.keys()
       
   640         # Ensure that every element is represented
       
   641         for myName in keyS:
       
   642             retMap[myName] = []
       
   643         for myName in keyS:
       
   644             myContainS = self.elemContains(myName)
       
   645             for aChild in myContainS:
       
   646                 try:
       
   647                     retMap[aChild].append(myName)
       
   648                 except KeyError:
       
   649                     retMap[aChild] = [myName]
       
   650         return retMap
       
   651 
       
   652     def _retDirName(self, theName):
       
   653         assert(theName.startswith(self._prefix))
       
   654         # Slice the name to get the directory
       
   655         # e.g. 'cxxVariableDeclarationFile' becomes 'cxxVariable'
       
   656         # Special case where all ...Ref.dita are in cxxAPIMap/
       
   657         if theName.endswith('Ref') \
       
   658         or theName == 'cxxAPIMap':
       
   659             return 'cxxAPIMap'
       
   660         i = len(self._prefix) + 1
       
   661         while i < len(theName) and theName[i] in string.lowercase:
       
   662             i += 1
       
   663         retVal = theName[:i]
       
   664         # This is a bit clunky for these special cases
       
   665         if retVal in ('cxxEnumerator', 'cxxEnumerators'):
       
   666             retVal = 'cxxEnumeration'
       
   667         return retVal
       
   668 
       
   669     def _writeElementNameToFile(self, theS, theName):
       
   670         if self._elemDefMap.has_key(theName) \
       
   671         and theName.startswith(self._prefix):
       
   672             # Slice the name to get the directory
       
   673             theDir = self._retDirName(theName)
       
   674             self._writeLine(theS, '<xref href="%s/%s.dita">%s</xref> ' \
       
   675                             % (theDir, theName, theName)
       
   676                         )
       
   677         else:
       
   678             # Write out as a keyword
       
   679             self._writeLine(theS, '<keyword>%s</keyword>, ' % theName)
       
   680 
       
   681     def _writeContentModelToStream(self, theS, theName):
       
   682         assert(self._elemDefMap.has_key(theName))
       
   683         hasWritten = False
       
   684         myElem = self._elemDefMap[theName]
       
   685         if myElem.complexType is not None:
       
   686             for aRef in myElem.complexType.genRefs():
       
   687                 #print 'TRACE: aRef', aRef
       
   688                 if aRef.isSequence:
       
   689                     if self._writeContentModelSequenceRef(theS, aRef):
       
   690                         hasWritten = True
       
   691                 else:
       
   692                     if self._writeContentModelRef(theS, aRef):
       
   693                         hasWritten = True
       
   694         return hasWritten
       
   695 
       
   696     def _writeContentModelSequenceRef(self, theS, theR):
       
   697         assert(theR.isSequence)
       
   698         hasWritten = False
       
   699         if theR.numRefs > 0:
       
   700             self._write(theS, '(')
       
   701         for i, aRef in enumerate(theR.genRefs()):
       
   702             if i > 0:
       
   703                 self._writeLine(theS, ' %s ' % theR.joinString())
       
   704                 hasWritten = True
       
   705             if aRef.isSequence:
       
   706                 if self._writeContentModelSequenceRef(theS, aRef):
       
   707                     hasWritten = True
       
   708             else:
       
   709                 if self._writeContentModelRef(theS, aRef):
       
   710                     hasWritten = True
       
   711         if theR.qualifierString():
       
   712             self._writeLine(theS, '<i>%s</i>' % theR.qualifierString())
       
   713         if theR.numRefs > 0:
       
   714             self._write(theS, ')')
       
   715         return hasWritten
       
   716 
       
   717     def _writeContentModelRef(self, theS, theR):
       
   718         assert(not theR.isSequence)
       
   719         hasWritten = False
       
   720         if self._groupDefMap.has_key(theR.attrRef):
       
   721             #s.write('%s %s\n' % (theR.attrRef, '(group)'))
       
   722             self._writeContentModelGroupRefToStream(theS, theR)#.attrRef)
       
   723             hasWritten = True
       
   724         elif self._elemDefMap.has_key(theR.attrRef):
       
   725             pass#s.write('%s %s\n' % (theR.attrRef, '(element)'))
       
   726         elif self._complexTypeMap.has_key(theR.attrRef):
       
   727             pass#s.write('%s %s\n' % (theR.attrRef, '(complex)'))
       
   728         else:
       
   729             #s.write('%s %s\n' % (theR.attrRef, '(not in my IR)'))
       
   730             self._writeLine(theS, '<keyword>%s</keyword>, ' % theR.attrRef)
       
   731             hasWritten = True
       
   732         return hasWritten
       
   733 
       
   734     def _writeContentModelGroupRefToStream(self, theS, theR):
       
   735         assert(self._groupDefMap.has_key(theR.attrRef))
       
   736         myGroup = self._groupDefMap[theR.attrRef]
       
   737         if myGroup.numRefs == 1 \
       
   738         and myGroup.refs[0].tag == 'element':
       
   739 #            s.write('%sELEMENT<%s> %s %s "%s"\n' \
       
   740 #                    % (
       
   741 #                       self.DUMP_PREFIX*level,
       
   742 #                       self._groupDefMap[theR.attrRef].refs[0].tag,
       
   743 #                       self._groupDefMap[theR.attrRef].refs[0].attrName,
       
   744 #                       self._groupDefMap[theR.attrRef].refs[0].attrRef,
       
   745 #                       self._groupDefMap[theR.attrRef].refs[0],
       
   746 #                       )
       
   747 #                )
       
   748             #self._write(theS, '(')
       
   749             aN = myGroup.refs[0].attrRef
       
   750             self._writeElementNameToFile(theS, aN)
       
   751             #self._writeLine(theS, '<xref href="%s/%s.dita">%s</xref>, %s' \
       
   752             #                % (aN, aN, aN, theR.boundedString))
       
   753             if theR.boundedString:
       
   754                 self._writeLine(theS, '(<i>%s</i>)' % theR.boundedString)
       
   755             #self._write(theS, ')')
       
   756         else:
       
   757             #s.write('%sGROUP  <%s>%s\n' \
       
   758             #    % (self.DUMP_PREFIX*level, self._groupDefMap[theR.attrRef].tag, self._groupDefMap[theR.attrRef]))
       
   759             if myGroup.numRefs > 0:
       
   760                 self._write(theS, '(')
       
   761             for i, aGm in enumerate(myGroup.genRefs()):
       
   762                 #print '%s<%s> %s %s "%s"' \
       
   763                 #    % (self.DUMP_PREFIX*level, aGm.tag, aGm.attrName, aGm.attrRef, aGm)#aGm.boundedString)
       
   764                 if i > 0:
       
   765                     self._writeLine(theS, myGroup.joinString())
       
   766                 if aGm.tag == 'group' \
       
   767                 and aGm.attrRef \
       
   768                 and self._groupDefMap.has_key(aGm.attrRef):
       
   769                     self._writeContentModelGroupRefToStream(theS, aGm)#.attrRef)
       
   770                 elif aGm.tag == 'element' \
       
   771                 and aGm.attrRef:
       
   772                     self._writeElementNameToFile(theS, aGm.attrRef)
       
   773                     #if self._elemDefMap.has_key(aGm.attrRef):
       
   774                     #    aN = aGm.attrRef
       
   775                     #    self._writeLine(theS, '<xref href="%s/%s.dita">%s</xref>, ' % (aN, aN, aN))
       
   776                     #else:
       
   777                     #    self._writeLine(theS, '<keyword>%s</keyword>, ' % aGm.attrRef)
       
   778                 #elif aGm.tag == 'element':
       
   779                 #    s.write('%sCHLDELE<%s> %s %s "%s"\n' \
       
   780                 #        % (self.DUMP_PREFIX*level, aGm.tag, aGm.attrName, aGm.attrRef, aGm))#aGm.boundedString)
       
   781             self._writeLine(theS, self._groupDefMap[theR.attrRef].qualifierString())
       
   782             if myGroup.numRefs > 0:
       
   783                 self._write(theS, ')')
       
   784     
       
   785     def writeToFile(self, thePath, theTitle):
       
   786         myS = open(thePath, 'w')
       
   787         self._writeHeader(myS, theTitle)
       
   788         #self.dump()
       
   789         myChildParentMap = self._retContainedByMap()
       
   790         #print 'TRACE: myChildParentMap', myChildParentMap
       
   791         # Run through the elements
       
   792         keyS = self._elemDefMap.keys()
       
   793         keyS.sort()
       
   794         for myName in keyS:
       
   795             myElem = self._elemDefMap[myName]
       
   796             logging.info('Writing element: <%s>' % myName)
       
   797             self._writeLine(myS, '<reference id="%s-reference" xml:lang="en-us">' % myName)
       
   798             self._writeLine(myS, '<title>Element: %s</title>' % myName)
       
   799             self._writeLine(myS, '<refbody>')
       
   800             # Section: Contained by
       
   801             self._writeLine(myS, '<section id="%s-containedBy-section" outputclass="elementContainedBy">' % myName)
       
   802             self._writeLine(myS, '<title>Contained by</title>')
       
   803             self._writeLine(myS, '<p id="%s-containedBy-p">' % myName)
       
   804             for aParent in myChildParentMap[myName]:
       
   805                 self._writeElementNameToFile(myS, aParent)
       
   806                 #if self._elemDefMap.has_key(aParent):
       
   807                 #    self._writeLine(myS, '<xref href="%s/%s.dita">%s</xref>, ' \
       
   808                 #                    % (aParent, aParent, aParent))
       
   809                 #else:
       
   810                 #    # Write out as a keyword
       
   811                 #    self._writeLine(myS, '<keyword>%s</keyword>, ' % aParent)
       
   812             self._writeLine(myS, '</p>')
       
   813             self._writeLine(myS, '</section>')
       
   814             # Section: Contains
       
   815             self._writeLine(myS,
       
   816                 '<section id="%s-contains-section" outputclass="elementContains">' % myName)
       
   817             self._writeLine(myS, '<title>Contains</title>')
       
   818             self._writeLine(myS, '<p id="%s-contains-p">' % myName)
       
   819             myContainS = self.elemContains(myName)
       
   820             myContainS.sort()
       
   821             #print 'myContainS', myContainS
       
   822             for aN in myContainS:
       
   823                 self._writeElementNameToFile(myS, aN)
       
   824             self._writeLine(myS, '</p>')
       
   825             self._writeLine(myS, '</section>')
       
   826             # Section: Content Model
       
   827             self._writeLine(myS, '<section id="%s-contentModel-section" outputclass="elementContentModel">' % myName)
       
   828             self._writeLine(myS, '<title>Content Model</title>')
       
   829             self._writeLine(myS, '<p id="%s-contentModel-p">' % myName)
       
   830             # Content model contents
       
   831             if not self._writeContentModelToStream(myS, myName):
       
   832                 self._writeLine(myS, 'No content.')
       
   833             self._writeLine(myS, '</p>')
       
   834             self._writeLine(myS, '</section>')
       
   835             # Section: Attributes - empty
       
   836             self._writeLine(myS, '<section id="%s-attList-section" outputclass="elementAttList" />' % myName)
       
   837             # Section: classValue
       
   838             self._writeLine(myS, '<section id="%s-classValue-section" outputclass="elementClassValue">' % myName)
       
   839             self._writeLine(myS, '<title>Inheritance</title>')
       
   840             self._writeLine(myS, '<p id="%s-classValue-p">' % myName)
       
   841             if myElem.complexType is not None:
       
   842                 myClassAttr = myElem.complexType.namedAttribute('class')
       
   843                 if myClassAttr is not None \
       
   844                 and myClassAttr.default is not None:
       
   845                     #print 'TRACE: myClassAttr.default: "%s"' % myClassAttr.default
       
   846                     for aStr in myClassAttr.default[1:].strip().split():
       
   847                         if aStr.find('/') != -1:
       
   848                             a,b = aStr.split('/')
       
   849                             myS.write(' %s/' % a)
       
   850                             myS.write('<keyword>%s</keyword>' % b)
       
   851             self._writeLine(myS, '</p>')
       
   852             self._writeLine(myS, '</section>')
       
   853             self._writeLine(myS, '</refbody>')
       
   854             self._writeLine(myS, '</reference>')
       
   855         self.close(myS)
       
   856     
       
   857     ##########################
       
   858     # End: Output
       
   859     ##########################
       
   860 
       
   861 def main():
       
   862     usage = """usage: %prog [options] file_or_dir
       
   863 Takes a XSD file or directory and generates a packagedef.dita file with
       
   864 the documentation for the XSD file(s)."""
       
   865     print 'Cmd: %s' % ' '.join(sys.argv)
       
   866     optParser = OptionParser(usage, version='%prog ' + __version__)
       
   867     optParser.add_option("-d", action="store_true", dest="dump", default=False, 
       
   868                       help="Dump IR (for debugging). [default: %default]")
       
   869     optParser.add_option(
       
   870             "-l", "--loglevel",
       
   871             type="int",
       
   872             dest="loglevel",
       
   873             default=20,
       
   874             help="Log Level (debug=10, info=20, warning=30, error=40, critical=50) [default: %default]"
       
   875         )      
       
   876     optParser.add_option("-o", "--out",
       
   877                          type="string",
       
   878                          dest="output",
       
   879                          default=None, 
       
   880                          help="Output file. [default: %default]")
       
   881 #===============================================================================
       
   882 #    optParser.add_option("-u", "--unittest",
       
   883 #                         action="store_true",
       
   884 #                         dest="unit_test",
       
   885 #                         default=False, 
       
   886 #                         help="Execute unit tests. [default: %default]")
       
   887 #===============================================================================
       
   888     optParser.add_option("-t", "--title", type="string", dest="title",
       
   889                          default='C++ API Reference Content Model Definitions', 
       
   890                          help="Title for the packagedef.dita. [default: %default]")
       
   891     optParser.add_option("-p", "--prefix", type="string", dest="prefix",
       
   892                          default='cxx', 
       
   893                          help="Prefix, only the elements starting with this will be documented. Use '' for all. [default: %default]")
       
   894     opts, args = optParser.parse_args()
       
   895     clkStart = time.clock()
       
   896     # Initialise logging etc.
       
   897     logging.basicConfig(level=opts.loglevel,
       
   898                     format='%(asctime)s %(levelname)-8s %(message)s',
       
   899                     #datefmt='%y-%m-%d % %H:%M:%S',
       
   900                     stream=sys.stdout)
       
   901 #===============================================================================
       
   902 #    if opts.unit_test:
       
   903 #        unitTest()
       
   904 #===============================================================================
       
   905     if len(args) > 0:
       
   906         # Your code here
       
   907         myX = XsdToPackageDef(opts.prefix)
       
   908         for aFile in args:
       
   909             myX.addXsdPath(aFile)
       
   910         myX.writeToFile(opts.output, opts.title)
       
   911         if opts.dump:
       
   912             myX.dump()
       
   913     else:
       
   914         optParser.print_help()
       
   915         optParser.error("No arguments!")
       
   916         return 1
       
   917     clkExec = time.clock() - clkStart
       
   918     print 'CPU time = %8.3f (S)' % clkExec
       
   919     print 'Bye, bye!'
       
   920     return 0
       
   921 
       
   922 if __name__ == '__main__':
       
   923     #multiprocessing.freeze_support()
       
   924     sys.exit(main())
       
   925