Orb/python/orb/XSDToPackageDef.py
author Jonathan Harrington <jonathan.harrington@nokia.com>
Wed, 11 Aug 2010 14:49:30 +0100
changeset 4 468f4c8d3d5b
permissions -rw-r--r--
Orb version 0.2.0

# 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())