Orb/python/orb/XSDToPackageDef.py
changeset 4 468f4c8d3d5b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orb/python/orb/XSDToPackageDef.py	Wed Aug 11 14:49:30 2010 +0100
@@ -0,0 +1,925 @@
+# Copyright (c) 2007-2010 Nokia Corporation and/or its subsidiary(-ies) All rights reserved.
+# This component and the accompanying materials are made available under the terms of the License 
+# "Eclipse Public License v1.0" which accompanies this distribution, 
+# and is available at the URL "http://www.eclipse.org/legal/epl-v10.html".
+#
+# Initial Contributors:
+# Nokia Corporation - initial contribution.
+#
+# Contributors:
+#
+# Description:
+# Checks links in DITA XML and reports issues.
+
+"""
+Created on 24 May 2010
+
+@author: p2ross
+"""
+import os
+import sys
+import logging
+#import pprint
+#import fnmatch
+#import re
+import string
+import time
+from optparse import OptionParser#, check_choice
+try:
+    from xml.etree import cElementTree as etree
+except ImportError:
+    from xml.etree import ElementTree as etree
+#import multiprocessing
+
+__version__ = "0.1.0"
+
+
+class XsdBase(object):
+    PREFIX = '{http://www.w3.org/2001/XMLSchema}'
+    def _addSchemaPrefix(self, theTag):
+        return '%s%s' % (self.PREFIX, theTag)
+    
+    def _tagSchemaMatches(self, theN, theTag):
+        return theN.tag == self._addSchemaPrefix(theTag)
+    
+    def _tagSchemaMatchesList(self, theN, theTagS):
+        for aTag in theTagS:
+            if theN.tag == self._addSchemaPrefix(aTag):
+                return True
+        return False
+    
+    def _stripSchemaPrefix(self, theN):
+        """Returns theNode.tag without the schema prefix."""
+        return theN.tag[len(self.PREFIX):]
+
+class XsdNodeBase(XsdBase):
+    PREFIX = '{http://www.w3.org/2001/XMLSchema}'
+    def __init__(self, theN):
+        super(XsdNodeBase, self).__init__()
+        self._name = theN.get('name')
+        self._ref = theN.get('ref')
+        # This will be prefix+element
+        self._tag = theN.tag
+        self._min = int(theN.get('minOccurs') or 1)
+        if theN.get('maxOccurs') == 'unbounded':
+            self._max = -1
+        else:
+            self._max = int(theN.get('maxOccurs') or 1)
+        logging.debug(
+            'XsdNodeBase.__init__(): tag=%s, name=%s, ref=%s, min=%d, max=%d',
+            self.tag,
+            self._name,
+            self._ref,
+            self._min,
+            self._max,
+            )
+    
+    @property
+    def tag(self):
+        return self._tag[len(self.PREFIX):]
+    
+    @property
+    def attrRef(self):
+        return self._ref
+    
+    @property
+    def attrName(self):
+        return self._name
+    
+    @property
+    def isUnbounded(self):
+        return self._max == -1
+    
+    @property
+    def minOccurs(self):
+        return self._min
+
+    @property
+    def maxOccurs(self):
+        return self._max
+    
+    @property
+    def boundedString(self):
+        """Returns a natural language description of bounds."""
+        if self._min == 1 and self._max == 1:
+            return ''
+        elif self._min == 0:
+            if self._max == 1:
+                # minOccurs="0" -> 'optional'
+                return 'optional'
+            elif self.isUnbounded:
+                # minOccurs="0" maxOccurs="unbounded" -> 'any number'
+                return 'any number'
+            else:
+                # minOccurs="0" maxOccurs="42" -> 'up to 42'
+                return 'up to %d' % self._max
+        elif self.isUnbounded:
+            # minOccurs="17" maxOccurs="unbounded" -> 'at least 17'
+            return 'at least %d' % self._min
+        # minOccurs="17" maxOccurs="42" -> 'at least 17 and up to 42'
+        return 'at least %d, and up to %d' % (self._min, self._max)
+
+class Attribute(XsdNodeBase):
+    """Represents an element describing an attribute."""
+    def __init__(self, theN):
+        super(Attribute, self).__init__(theN)
+        assert(self._tagSchemaMatches(theN, 'attribute'))
+        self._default = theN.get('default')
+        self._use = theN.get('use')
+        
+    @property
+    def default(self):
+        return self._default
+
+    @property
+    def use(self):
+        return self._use
+
+class RefBase(XsdNodeBase):
+    """Represents a reference to a group or element"""
+    def __init__(self, theN):
+        super(RefBase, self).__init__(theN)
+        assert(self._tagSchemaMatchesList(theN, ('group', 'element')))
+        assert(self.attrRef is not None)
+
+    @property
+    def isSequence(self):
+        return False
+
+class RefSequence(XsdNodeBase):
+    """Holds information on an xs:all, xs:choice or xs:sequence."""
+    def __init__(self, theN):
+        super(RefSequence, self).__init__(theN)
+        assert(self._tagSchemaMatchesList(theN, ('all', 'choice', 'sequence'))), 'Tag %s not in list' % theN.tag
+        # List of class RefBase or, recursively, a class RefSequence
+        self._refS = []
+        self._type = self.tag
+        for aChild in theN.getchildren():
+            if not self._tagSchemaMatchesList(aChild, ('group', 'all', 'choice', 'sequence')):
+                continue
+            #self._type = self._stripSchemaPrefix(aChild)
+            if self._tagSchemaMatches(aChild, 'group'):
+                self._refS.append(RefBase(aChild))
+            elif self._tagSchemaMatchesList(
+                        aChild,
+                        ('choice', 'sequence', 'any',)
+                    ):
+                self._refS.append(RefSequence(aChild))
+
+    def genRefs(self):
+        """Generates the list of RefBase or RefSequence"""
+        for aRef in self._refS:
+            yield aRef
+
+    @property
+    def numRefs(self):
+        return len(self._refS)
+    
+    @property
+    def refs(self):
+        return self._refS
+    
+    @property
+    def isSequence(self):
+        return True
+
+    def qualifierString(self):
+        if self._type == 'all':
+            return '(any number, any order)'
+        elif self._type == 'choice':
+            return '(any number)'
+        elif self._type == 'sequence':
+            return ''
+        return 'Unknown'
+    
+    def joinString(self):
+        if self._type == 'all':
+            return ' or '
+        elif self._type == 'choice':
+            return ' or '
+        elif self._type == 'sequence':
+            return ' then '
+        return ''
+    
+    @property
+    def seqType(self):
+        return self._type
+
+class DefBase(XsdNodeBase):
+    """Represents a definition of a group, element or complexType"""
+    def __init__(self, theN):
+        super(DefBase, self).__init__(theN)
+        assert(self._tagSchemaMatchesList(theN, ('group', 'element', 'complexType'))), 'Tag %s not in list' % theN.tag
+        assert(self.attrName is not None or self._tagSchemaMatches(theN, 'complexType'))
+        # List of class RefBase of class RefSequence
+        self._refS = []
+
+    def genRefs(self):
+        """Generates the list of RefBase"""
+        for aRef in self._refS:
+            yield aRef
+
+    @property
+    def numRefs(self):
+        return len(self._refS)
+    
+    @property
+    def refs(self):
+        return self._refS
+            
+class GroupDef(DefBase):
+    """Represents the definition of a group i.e. <xs:group name="...">...</xs:group>.
+    See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group"""
+    def __init__(self, theN):
+        super(GroupDef, self).__init__(theN)
+        assert(theN.tag == self._addSchemaPrefix('group'))
+        # http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group
+        # Content: (annotation?, (all | choice | sequence)?)
+        self._type = None
+        for aChild in theN.getchildren():
+            if self._tagSchemaMatchesList(aChild, ('all', 'choice', 'sequence')):
+                self._type = self._stripSchemaPrefix(aChild)
+            else:
+                # Unrecognised element or annotation
+                continue
+            if self._type is not None:
+                # Read grand children and break
+                for aGrandChild in aChild.getchildren():
+                    if aGrandChild.get('ref') is not None \
+                    and self._tagSchemaMatchesList(
+                                aGrandChild,
+                                (
+                                    'element',
+                                    'group',
+                                    #'choice',
+                                    #'sequence',
+                                    #'any',
+                                )):
+                        self._refS.append(RefBase(aGrandChild))
+                break
+        
+    def __str__(self):
+        #print 'TRACE:', self._refS
+        return self.joinString().join(['%s' % r.attrRef for r in self._refS]) \
+            + ' ' \
+            + self.qualifierString() 
+    
+    def qualifierString(self):
+        if self._type == 'all':
+            return '(any number, any order)'
+        elif self._type == 'choice':
+            return '(any number)'
+        elif self._type == 'sequence':
+            return ''
+        return 'Unknown'
+    
+    def joinString(self):
+        if self._type == 'all':
+            return ' or '
+        elif self._type == 'choice':
+            return ' or '
+        elif self._type == 'sequence':
+            return ' then '
+        return ''
+    
+    @property
+    def groupType(self):
+        return self._type
+    
+class ComplexTypeDef(DefBase):#XsdNodeBase):
+    """Represents the definition of a complexType definition i.e. <xs:complexType name="...">...</xs:group>.
+    See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-complexType"""
+    def __init__(self, theN):
+        super(ComplexTypeDef, self).__init__(theN)
+        assert(theN.tag == self._addSchemaPrefix('complexType'))
+        # http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-complexType
+        # Simplified content: ((group | all | choice | sequence)?
+        self._type = None
+        # Get xs:attribute
+        self._attrS = []
+        for aChild in theN.getchildren():
+            # We only handle complexType
+            if not self._tagSchemaMatchesList(
+                    aChild,
+                    ('group', 'all', 'choice', 'sequence', 'attribute')):
+                # Unrecognised element or annotation
+                continue
+            self._type = self._stripSchemaPrefix(aChild)
+            if self._tagSchemaMatches(aChild, 'group'):
+                self._refS.append(RefBase(aChild))
+            elif self._tagSchemaMatchesList(
+                                aChild,
+                                ('choice', 'sequence', 'any',),
+                                ):
+                self._refS.append(RefSequence(aChild))
+            elif self._tagSchemaMatches(aChild, 'attribute'):
+                self._attrS.append(Attribute(aChild))
+
+    @property
+    def groupType(self):
+        return self._type
+    
+    def namedAttribute(self, theName):
+        """Returns an Attribute object or None."""
+        for anA in self._attrS:
+            if anA.attrName == theName:
+                return anA
+
+class ElementDef(DefBase):
+    """Represents the definition of a element definition i.e. <xs:element name="...">...</xs:group>.
+    See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group"""
+    def __init__(self, theN):
+        super(ElementDef, self).__init__(theN)
+        assert(theN.tag == self._addSchemaPrefix('element'))
+        # http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-element
+        # Simplified content: (annotation?, ((simpleType | complexType)?
+        self._type = None
+        self._complexType = None
+        for aChild in theN.getchildren():
+            # We only handle complexType
+            if not self._tagSchemaMatchesList(aChild, ('complexType',)):
+                # Unrecognised element or annotation
+                continue
+            # Process a <complexType>
+            self._complexType = ComplexTypeDef(aChild)
+            break
+    
+    @property
+    def complexType(self):
+        return self._complexType
+
+class GroupRef(RefBase):
+    """Represents a reference of a group i.e. <xs:group ref="..." .../>.
+    See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group"""
+    def __init__(self, theN):
+        super(GroupRef, self).__init__(theN)
+        assert(theN.tag == self._addSchemaPrefix('group'))
+
+class ElementRef(RefBase):
+    """Represents a reference to an element i.e. <xs:element ref="..." .../>.
+    See: http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-group"""
+    def __init__(self, theN):
+        super(ElementRef, self).__init__(theN)
+        assert(theN.tag == self._addSchemaPrefix('element'))
+
+class XsdToPackageDef(XsdBase):
+    """Create a PackageDef.xml file from a set of XSD files."""
+    DUMP_PREFIX = '  '
+    def __init__(self, thePrefix):#, theOutFile=None, theTitle=''):
+        super(XsdToPackageDef, self).__init__()
+        # Maps of group definitions and references
+        self._groupDefMap = {}
+        self._groupRefMap = {}
+        # Maps of element definitions and references
+        self._elemDefMap = {}
+        self._elemRefMap = {}
+        # Top level complex types
+        self._complexTypeMap = {}
+        # Element prefix
+        self._prefix = thePrefix
+            
+    ##########################
+    # Section: Add an XSD file
+    ##########################
+    def addXsdFile(self, thePath):
+        """Read an XSD and add it to the representation."""
+        t = etree.parse(thePath)
+        r = t.getroot()
+        self._addGroups(r)
+        self._addElements(r)
+        self._addComplexTypes(r)
+    
+    def addXsdPath(self, thePath):
+        if os.path.isfile(thePath):
+            if os.path.splitext(thePath)[1].lower() == '.xsd':
+                self.addXsdFile(thePath)
+        elif os.path.isdir(thePath):
+            for aName in os.listdir(thePath):
+                self.addXsdPath(os.path.join(thePath, aName))
+
+    def _addGroups(self, root):
+        """Adds to the group maps."""
+        for anE in root.getchildren():
+            if self._tagSchemaMatches(anE, 'group'):
+                if anE.get('name') is not None:
+                    self._groupDefMap[anE.get('name')] = GroupDef(anE)
+                elif anE.get('ref') is not None:
+                    self._groupRefMap[anE.get('ref')] = GroupRef(anE)
+                
+    def _addElements(self, root):
+        """Adds to the element maps."""
+        for anE in root.getchildren():
+            if self._tagSchemaMatches(anE, 'element'):
+                if anE.get('name') is not None:
+                    self._elemDefMap[anE.get('name')] = ElementDef(anE)
+                elif anE.get('ref') is not None:
+                    self._elemRefMap[anE.get('ref')] = ElementRef(anE)
+    
+    def _addComplexTypes(self, root):
+        for anE in root.getchildren():
+            if self._tagSchemaMatches(anE, 'complexType'):
+                self._complexTypeMap[anE.get('name')] = ComplexTypeDef(anE)
+    ##########################
+    # End: Add an XSD file
+    ##########################
+
+    ##########################
+    # Section: Query the IR
+    ##########################
+    def elemContains(self, theElemName):
+        """Returns a list of immediate child elements that this element contains."""
+        logging.debug('elemContains(%s)' % theElemName)
+        retVal = []
+        if not self._elemDefMap.has_key(theElemName):
+            return retVal
+        myElem = self._elemDefMap[theElemName]
+        if myElem.complexType is not None:
+            logging.debug('elemContains(): complexType: %s', myElem.complexType)
+            #print 'myElem', myElem
+            for aRef in myElem.complexType.genRefs():
+                logging.debug('elemContains(): complexType tag: %s, attrRef: %s',
+                              aRef.tag,
+                              aRef.attrRef,
+                        )
+                self._addFromComplexTypeSequence(aRef, retVal)
+#                myRef = aRef.attrRef
+#                #print 'myRef', myRef
+#                #if self._elemDefMap.has_key(myRef):
+#                #    retVal.append(myRef)
+#                if self._groupDefMap.has_key(myRef):
+#                    self._addContainsGroup(myRef, retVal)
+#                else:
+#                    retVal.append(myRef)
+        logging.debug('elemContains(%s) returns %s', theElemName, str(retVal))
+        return retVal
+    
+    def _addFromComplexTypeSequence(self, theRefObj, theList):
+        """Recursively add containing elements."""
+        myRef = theRefObj.attrRef
+        logging.debug('_addFromComplexTypeSequence(%s):', myRef) 
+        if theRefObj.isSequence:
+            for aSubRef in theRefObj.refs:
+                logging.debug('_addFromComplexTypeSequence(): recursing on: %s',
+                              myRef)
+                self._addFromComplexTypeSequence(aSubRef, theList)
+        elif myRef is not None:
+            if self._groupDefMap.has_key(myRef):
+                self._addContainsGroup(myRef, theList)
+            else:
+                logging.debug('_addFromComplexTypeSequence(): adding: %s',
+                              myRef)
+                theList.append(myRef)
+    
+    def _addContainsGroup(self, theRef, theList):
+        logging.debug('_addContainsGroup(%s)' % theRef)
+        assert(self._groupDefMap.has_key(theRef))
+        for aRef in self._groupDefMap[theRef].genRefs():
+            #print 'TRACE: aRef', aRef.tag
+            logging.debug('_addContainsGroup(): aRef.tag: %s aRef.attrRef: %s' \
+                          % (aRef.tag, aRef.attrRef))
+            if aRef.tag == 'element':
+                theList.append(aRef.attrRef)
+            elif aRef.tag == 'group' and self._groupDefMap.has_key(aRef.attrRef):
+                self._addContainsGroup(aRef.attrRef, theList)
+
+    ##########################
+    # End: Query the IR
+    ##########################
+    
+    ##########################
+    # Section: Debug/trace
+    ##########################
+    def dump(self, s=sys.stdout):
+        s.write(' Group definitions '.center(75, '-')+'\n')
+        keyS = self._groupDefMap.keys()
+        keyS.sort()
+        for k in keyS:
+            s.write('%40s : %s\n' % (k, self._groupDefMap[k]))
+        s.write(' EOL '.center(75, '-')+'\n\n') 
+        s.write(' Group references '.center(75, '-')+'\n') 
+        keyS = self._groupRefMap.keys()
+        keyS.sort()
+        for k in keyS:
+            s.write('%40s : %s\n' % (k, self._groupRefMap[k]))
+        s.write(' EOL '.center(75, '-')+'\n\n') 
+        s.write(' Element definitions '.center(75, '-')+'\n') 
+        keyS = self._elemDefMap.keys()
+        keyS.sort()
+        for k in keyS:
+            s.write('%40s : %s\n' % (k, self._elemDefMap[k]))
+        s.write(' EOL '.center(75, '-')+'\n\n') 
+        s.write(' Element references '.center(75, '-')+'\n') 
+        keyS = self._elemRefMap.keys()
+        keyS.sort()
+        for k in keyS:
+            s.write('%40s : %s\n' % (k, self._elemRefMap[k]))
+        s.write(' EOL '.center(75, '-')+'\n\n') 
+        s.write(' Complex types '.center(75, '-')+'\n') 
+        keyS = self._complexTypeMap.keys()
+        keyS.sort()
+        for k in keyS:
+            s.write('%40s : %s\n' % (k, self._complexTypeMap[k]))
+        s.write(' EOL '.center(75, '-')+'\n\n') 
+        s.write(' Parent to child relationship '.center(75, '-')+'\n')
+        # A map {element_name : [parent element, ...], ...}
+        myPcMap = self._retContainedByMap()
+        keyS = myPcMap.keys()
+        keyS.sort()
+        for k in keyS:
+            s.write('Child: %s\n' % k)
+            s.write('    Contained by: %s\n' % myPcMap[k])
+        s.write(' EOL '.center(75, '-')+'\n\n') 
+        # Run through the elements
+        keyS = self._elemDefMap.keys()
+        keyS.sort()
+        for k in keyS:
+            myElem = self._elemDefMap[k]
+            s.write('Element: %s\n' % k)
+            if myElem.complexType is not None:
+                for aRef in myElem.complexType.genRefs():
+                    #print 'TRACE: aRef', aRef
+                    if aRef.isSequence:
+                        self._dumpSequenceRef(s, aRef, level=1)
+                    else:
+                        self._dumpRef(s, aRef, level=1)
+            else:
+                s.write('  No complexType\n')
+            s.write('\n')
+            
+    def _dumpRef(self, s, theRef, level=1):
+        assert(not theRef.isSequence)
+        if self._groupDefMap.has_key(theRef.attrRef):
+            s.write('%s%s %s [%s]\n' \
+                    % (self.DUMP_PREFIX*level,
+                       theRef.attrRef,
+                       '(group)',
+                       theRef.boundedString,
+                       #self._groupDefMap[aRef.attrRef].boundedString,
+                    )
+                )
+            self._dumpGroupRef(s, theRef.attrRef, level)
+            #for aGm in self._groupDefMap[aRef.attrRef].genRefs():
+            #    print '  <%s> %s %s %s' \
+            #        % (aGm.tag, aGm.attrName, aGm.attrRef, aGm.boundedString)
+        elif self._elemDefMap.has_key(theRef.attrRef):
+            s.write('%s%s %s\n' % (self.DUMP_PREFIX*level, theRef.attrRef, '(element)'))
+        elif self._complexTypeMap.has_key(theRef.attrRef):
+            s.write('%s%s %s\n' % (self.DUMP_PREFIX*level, theRef.attrRef, '(complex)'))
+        else:
+            s.write('%s%s %s\n' % (self.DUMP_PREFIX*level, theRef.attrRef, '(not in my IR)'))
+
+    def _dumpGroupRef(self, s, theR, level=1):
+        """Dump out references to groups, recursively using self._groupDefMap."""
+        assert(self._groupDefMap.has_key(theR))
+        if self._groupDefMap[theR].numRefs == 1 \
+        and self._groupDefMap[theR].refs[0].tag == 'element':
+            s.write('%sELEMENT<%s> %s %s "%s" [%s]\n' \
+                    % (
+                       self.DUMP_PREFIX*level,
+                       self._groupDefMap[theR].refs[0].tag,
+                       self._groupDefMap[theR].refs[0].attrName,
+                       self._groupDefMap[theR].refs[0].attrRef,
+                       self._groupDefMap[theR].refs[0],
+                       self._groupDefMap[theR].refs[0].boundedString,
+                       )
+                )
+        else:
+            s.write('%sGROUP  <%s>%s\n' \
+                % (self.DUMP_PREFIX*level, self._groupDefMap[theR].tag, self._groupDefMap[theR]))
+            for aGm in self._groupDefMap[theR].genRefs():
+                #print '%s<%s> %s %s "%s"' \
+                #    % (self.DUMP_PREFIX*level, aGm.tag, aGm.attrName, aGm.attrRef, aGm)#aGm.boundedString)
+                if aGm.tag == 'group' \
+                and aGm.attrRef \
+                and self._groupDefMap.has_key(aGm.attrRef):
+                    self._dumpGroupRef(s, aGm.attrRef, level+1)
+                elif aGm.tag == 'element':
+                    s.write('%sCHLDELE<%s> %s %s "%s"\n' \
+                        % (self.DUMP_PREFIX*level, aGm.tag, aGm.attrName, aGm.attrRef, aGm))#aGm.boundedString)
+
+    def _dumpSequenceRef(self, s, theR, level=1):
+        """Dump out sequences recursively."""
+        assert(theR.isSequence)
+        for i, aRef in enumerate(theR.genRefs()):
+            if i > 0:
+                s.write('%s[%s]\n' % (self.DUMP_PREFIX*level, theR.joinString()))
+            #print '%sTRACE: _dumpSequenceRef aRef: %s' % (self.DUMP_PREFIX*level, aRef)
+            if aRef.isSequence:
+                self._dumpSequenceRef(s, aRef, level=level+1)
+            else:
+                self._dumpRef(s, aRef, level=level+1)
+        s.write('%s[%s]\n' % (self.DUMP_PREFIX*level, theR.qualifierString()))
+    ##########################
+    # End: Debug/trace
+    ##########################
+        
+    ##########################
+    # Section: Output
+    ##########################
+    def _writeHeader(self, theS, theTitle):
+        self._writeLine(theS, '<?xml version="1.0" encoding="UTF-8"?>')
+        self._writeLine(theS, """<!DOCTYPE reference PUBLIC "-//OASIS//DTD DITA Reference//EN" "../../dtd/reference.dtd">""")
+        self._writeLine(theS, """<reference id="%sapiref" xml:lang="en-us">""" % self._prefix)
+        self._writeLine(theS, '<title>%s</title>' % theTitle)
+        
+    def _writeLine(self, theS, theL):
+        theS.write(theL)
+        theS.write('\n')
+        
+    def _write(self, theS, theStr):
+        theS.write(theStr)
+
+    def close(self, theS):
+        self._writeLine(theS, '</reference>')
+        theS.close()
+    
+    def _retContainedByMap(self):
+        """Returns a map {element_name : [parent element, ...], ...}."""
+        retMap = {}
+        keyS = self._elemDefMap.keys()
+        # Ensure that every element is represented
+        for myName in keyS:
+            retMap[myName] = []
+        for myName in keyS:
+            myContainS = self.elemContains(myName)
+            for aChild in myContainS:
+                try:
+                    retMap[aChild].append(myName)
+                except KeyError:
+                    retMap[aChild] = [myName]
+        return retMap
+
+    def _retDirName(self, theName):
+        assert(theName.startswith(self._prefix))
+        # Slice the name to get the directory
+        # e.g. 'cxxVariableDeclarationFile' becomes 'cxxVariable'
+        # Special case where all ...Ref.dita are in cxxAPIMap/
+        if theName.endswith('Ref') \
+        or theName == 'cxxAPIMap':
+            return 'cxxAPIMap'
+        i = len(self._prefix) + 1
+        while i < len(theName) and theName[i] in string.lowercase:
+            i += 1
+        retVal = theName[:i]
+        # This is a bit clunky for these special cases
+        if retVal in ('cxxEnumerator', 'cxxEnumerators'):
+            retVal = 'cxxEnumeration'
+        return retVal
+
+    def _writeElementNameToFile(self, theS, theName):
+        if self._elemDefMap.has_key(theName) \
+        and theName.startswith(self._prefix):
+            # Slice the name to get the directory
+            theDir = self._retDirName(theName)
+            self._writeLine(theS, '<xref href="%s/%s.dita">%s</xref> ' \
+                            % (theDir, theName, theName)
+                        )
+        else:
+            # Write out as a keyword
+            self._writeLine(theS, '<keyword>%s</keyword>, ' % theName)
+
+    def _writeContentModelToStream(self, theS, theName):
+        assert(self._elemDefMap.has_key(theName))
+        hasWritten = False
+        myElem = self._elemDefMap[theName]
+        if myElem.complexType is not None:
+            for aRef in myElem.complexType.genRefs():
+                #print 'TRACE: aRef', aRef
+                if aRef.isSequence:
+                    if self._writeContentModelSequenceRef(theS, aRef):
+                        hasWritten = True
+                else:
+                    if self._writeContentModelRef(theS, aRef):
+                        hasWritten = True
+        return hasWritten
+
+    def _writeContentModelSequenceRef(self, theS, theR):
+        assert(theR.isSequence)
+        hasWritten = False
+        if theR.numRefs > 0:
+            self._write(theS, '(')
+        for i, aRef in enumerate(theR.genRefs()):
+            if i > 0:
+                self._writeLine(theS, ' %s ' % theR.joinString())
+                hasWritten = True
+            if aRef.isSequence:
+                if self._writeContentModelSequenceRef(theS, aRef):
+                    hasWritten = True
+            else:
+                if self._writeContentModelRef(theS, aRef):
+                    hasWritten = True
+        if theR.qualifierString():
+            self._writeLine(theS, '<i>%s</i>' % theR.qualifierString())
+        if theR.numRefs > 0:
+            self._write(theS, ')')
+        return hasWritten
+
+    def _writeContentModelRef(self, theS, theR):
+        assert(not theR.isSequence)
+        hasWritten = False
+        if self._groupDefMap.has_key(theR.attrRef):
+            #s.write('%s %s\n' % (theR.attrRef, '(group)'))
+            self._writeContentModelGroupRefToStream(theS, theR)#.attrRef)
+            hasWritten = True
+        elif self._elemDefMap.has_key(theR.attrRef):
+            pass#s.write('%s %s\n' % (theR.attrRef, '(element)'))
+        elif self._complexTypeMap.has_key(theR.attrRef):
+            pass#s.write('%s %s\n' % (theR.attrRef, '(complex)'))
+        else:
+            #s.write('%s %s\n' % (theR.attrRef, '(not in my IR)'))
+            self._writeLine(theS, '<keyword>%s</keyword>, ' % theR.attrRef)
+            hasWritten = True
+        return hasWritten
+
+    def _writeContentModelGroupRefToStream(self, theS, theR):
+        assert(self._groupDefMap.has_key(theR.attrRef))
+        myGroup = self._groupDefMap[theR.attrRef]
+        if myGroup.numRefs == 1 \
+        and myGroup.refs[0].tag == 'element':
+#            s.write('%sELEMENT<%s> %s %s "%s"\n' \
+#                    % (
+#                       self.DUMP_PREFIX*level,
+#                       self._groupDefMap[theR.attrRef].refs[0].tag,
+#                       self._groupDefMap[theR.attrRef].refs[0].attrName,
+#                       self._groupDefMap[theR.attrRef].refs[0].attrRef,
+#                       self._groupDefMap[theR.attrRef].refs[0],
+#                       )
+#                )
+            #self._write(theS, '(')
+            aN = myGroup.refs[0].attrRef
+            self._writeElementNameToFile(theS, aN)
+            #self._writeLine(theS, '<xref href="%s/%s.dita">%s</xref>, %s' \
+            #                % (aN, aN, aN, theR.boundedString))
+            if theR.boundedString:
+                self._writeLine(theS, '(<i>%s</i>)' % theR.boundedString)
+            #self._write(theS, ')')
+        else:
+            #s.write('%sGROUP  <%s>%s\n' \
+            #    % (self.DUMP_PREFIX*level, self._groupDefMap[theR.attrRef].tag, self._groupDefMap[theR.attrRef]))
+            if myGroup.numRefs > 0:
+                self._write(theS, '(')
+            for i, aGm in enumerate(myGroup.genRefs()):
+                #print '%s<%s> %s %s "%s"' \
+                #    % (self.DUMP_PREFIX*level, aGm.tag, aGm.attrName, aGm.attrRef, aGm)#aGm.boundedString)
+                if i > 0:
+                    self._writeLine(theS, myGroup.joinString())
+                if aGm.tag == 'group' \
+                and aGm.attrRef \
+                and self._groupDefMap.has_key(aGm.attrRef):
+                    self._writeContentModelGroupRefToStream(theS, aGm)#.attrRef)
+                elif aGm.tag == 'element' \
+                and aGm.attrRef:
+                    self._writeElementNameToFile(theS, aGm.attrRef)
+                    #if self._elemDefMap.has_key(aGm.attrRef):
+                    #    aN = aGm.attrRef
+                    #    self._writeLine(theS, '<xref href="%s/%s.dita">%s</xref>, ' % (aN, aN, aN))
+                    #else:
+                    #    self._writeLine(theS, '<keyword>%s</keyword>, ' % aGm.attrRef)
+                #elif aGm.tag == 'element':
+                #    s.write('%sCHLDELE<%s> %s %s "%s"\n' \
+                #        % (self.DUMP_PREFIX*level, aGm.tag, aGm.attrName, aGm.attrRef, aGm))#aGm.boundedString)
+            self._writeLine(theS, self._groupDefMap[theR.attrRef].qualifierString())
+            if myGroup.numRefs > 0:
+                self._write(theS, ')')
+    
+    def writeToFile(self, thePath, theTitle):
+        myS = open(thePath, 'w')
+        self._writeHeader(myS, theTitle)
+        #self.dump()
+        myChildParentMap = self._retContainedByMap()
+        #print 'TRACE: myChildParentMap', myChildParentMap
+        # Run through the elements
+        keyS = self._elemDefMap.keys()
+        keyS.sort()
+        for myName in keyS:
+            myElem = self._elemDefMap[myName]
+            logging.info('Writing element: <%s>' % myName)
+            self._writeLine(myS, '<reference id="%s-reference" xml:lang="en-us">' % myName)
+            self._writeLine(myS, '<title>Element: %s</title>' % myName)
+            self._writeLine(myS, '<refbody>')
+            # Section: Contained by
+            self._writeLine(myS, '<section id="%s-containedBy-section" outputclass="elementContainedBy">' % myName)
+            self._writeLine(myS, '<title>Contained by</title>')
+            self._writeLine(myS, '<p id="%s-containedBy-p">' % myName)
+            for aParent in myChildParentMap[myName]:
+                self._writeElementNameToFile(myS, aParent)
+                #if self._elemDefMap.has_key(aParent):
+                #    self._writeLine(myS, '<xref href="%s/%s.dita">%s</xref>, ' \
+                #                    % (aParent, aParent, aParent))
+                #else:
+                #    # Write out as a keyword
+                #    self._writeLine(myS, '<keyword>%s</keyword>, ' % aParent)
+            self._writeLine(myS, '</p>')
+            self._writeLine(myS, '</section>')
+            # Section: Contains
+            self._writeLine(myS,
+                '<section id="%s-contains-section" outputclass="elementContains">' % myName)
+            self._writeLine(myS, '<title>Contains</title>')
+            self._writeLine(myS, '<p id="%s-contains-p">' % myName)
+            myContainS = self.elemContains(myName)
+            myContainS.sort()
+            #print 'myContainS', myContainS
+            for aN in myContainS:
+                self._writeElementNameToFile(myS, aN)
+            self._writeLine(myS, '</p>')
+            self._writeLine(myS, '</section>')
+            # Section: Content Model
+            self._writeLine(myS, '<section id="%s-contentModel-section" outputclass="elementContentModel">' % myName)
+            self._writeLine(myS, '<title>Content Model</title>')
+            self._writeLine(myS, '<p id="%s-contentModel-p">' % myName)
+            # Content model contents
+            if not self._writeContentModelToStream(myS, myName):
+                self._writeLine(myS, 'No content.')
+            self._writeLine(myS, '</p>')
+            self._writeLine(myS, '</section>')
+            # Section: Attributes - empty
+            self._writeLine(myS, '<section id="%s-attList-section" outputclass="elementAttList" />' % myName)
+            # Section: classValue
+            self._writeLine(myS, '<section id="%s-classValue-section" outputclass="elementClassValue">' % myName)
+            self._writeLine(myS, '<title>Inheritance</title>')
+            self._writeLine(myS, '<p id="%s-classValue-p">' % myName)
+            if myElem.complexType is not None:
+                myClassAttr = myElem.complexType.namedAttribute('class')
+                if myClassAttr is not None \
+                and myClassAttr.default is not None:
+                    #print 'TRACE: myClassAttr.default: "%s"' % myClassAttr.default
+                    for aStr in myClassAttr.default[1:].strip().split():
+                        if aStr.find('/') != -1:
+                            a,b = aStr.split('/')
+                            myS.write(' %s/' % a)
+                            myS.write('<keyword>%s</keyword>' % b)
+            self._writeLine(myS, '</p>')
+            self._writeLine(myS, '</section>')
+            self._writeLine(myS, '</refbody>')
+            self._writeLine(myS, '</reference>')
+        self.close(myS)
+    
+    ##########################
+    # End: Output
+    ##########################
+
+def main():
+    usage = """usage: %prog [options] file_or_dir
+Takes a XSD file or directory and generates a packagedef.dita file with
+the documentation for the XSD file(s)."""
+    print 'Cmd: %s' % ' '.join(sys.argv)
+    optParser = OptionParser(usage, version='%prog ' + __version__)
+    optParser.add_option("-d", action="store_true", dest="dump", default=False, 
+                      help="Dump IR (for debugging). [default: %default]")
+    optParser.add_option(
+            "-l", "--loglevel",
+            type="int",
+            dest="loglevel",
+            default=20,
+            help="Log Level (debug=10, info=20, warning=30, error=40, critical=50) [default: %default]"
+        )      
+    optParser.add_option("-o", "--out",
+                         type="string",
+                         dest="output",
+                         default=None, 
+                         help="Output file. [default: %default]")
+#===============================================================================
+#    optParser.add_option("-u", "--unittest",
+#                         action="store_true",
+#                         dest="unit_test",
+#                         default=False, 
+#                         help="Execute unit tests. [default: %default]")
+#===============================================================================
+    optParser.add_option("-t", "--title", type="string", dest="title",
+                         default='C++ API Reference Content Model Definitions', 
+                         help="Title for the packagedef.dita. [default: %default]")
+    optParser.add_option("-p", "--prefix", type="string", dest="prefix",
+                         default='cxx', 
+                         help="Prefix, only the elements starting with this will be documented. Use '' for all. [default: %default]")
+    opts, args = optParser.parse_args()
+    clkStart = time.clock()
+    # Initialise logging etc.
+    logging.basicConfig(level=opts.loglevel,
+                    format='%(asctime)s %(levelname)-8s %(message)s',
+                    #datefmt='%y-%m-%d % %H:%M:%S',
+                    stream=sys.stdout)
+#===============================================================================
+#    if opts.unit_test:
+#        unitTest()
+#===============================================================================
+    if len(args) > 0:
+        # Your code here
+        myX = XsdToPackageDef(opts.prefix)
+        for aFile in args:
+            myX.addXsdPath(aFile)
+        myX.writeToFile(opts.output, opts.title)
+        if opts.dump:
+            myX.dump()
+    else:
+        optParser.print_help()
+        optParser.error("No arguments!")
+        return 1
+    clkExec = time.clock() - clkStart
+    print 'CPU time = %8.3f (S)' % clkExec
+    print 'Bye, bye!'
+    return 0
+
+if __name__ == '__main__':
+    #multiprocessing.freeze_support()
+    sys.exit(main())
+