#
# minixsv, Release 0.9.0
# file: xsvalSchema.py
#
# Derived validator class (for validation of schema files)
#
# history:
# 2004-10-07 rl   created
# 2006-08-18 rl   W3C testsuite passed for supported features
# 2007-05-24 rl   Features for release 0.8 added, several bugs fixed
#
# Copyright (c) 2004-2008 by Roland Leuthe.  All rights reserved.
#
# --------------------------------------------------------------------
# The minixsv XML schema validator is
#
# Copyright (c) 2004-2008 by Roland Leuthe
#
# By obtaining, using, and/or copying this software and/or its
# associated documentation, you agree that you have read, understood,
# and will comply with the following terms and conditions:
#
# Permission to use, copy, modify, and distribute this software and
# its associated documentation for any purpose and without fee is
# hereby granted, provided that the above copyright notice appears in
# all copies, and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of
# the author not be used in advertising or publicity
# pertaining to distribution of the software without specific, written
# prior permission.
#
# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
# ABILITY AND FITNESS.  IN NO EVENT SHALL THE AUTHOR
# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
# --------------------------------------------------------------------


import string
import re
import os
from decimal             import Decimal
from genxmlif.xmlifUtils import collapseString
from minixsv             import *
from xsvalBase           import XsValBase, TagException
from xsvalUtils          import substituteSpecialEscChars

_localFacetDict = {(XSD_NAMESPACE,"list"): ("length", "minLength", "maxLength", "enumeration", "pattern", "whiteSpace"),
                   (XSD_NAMESPACE,"union"): ("enumeration", "pattern", "whiteSpace"),
                   (XSD_NAMESPACE,"anySimpleType"): ("whiteSpace"),}
###########################################################
#  Derived validator class for validating one input schema file against the XML rules file

class XsValSchema (XsValBase):

    ########################################
    # overloaded validate method
    #
    def validate (self, inputTree, xsdTree):
        XsValBase.validate(self, inputTree, xsdTree)
    
        self._initInternalAttributes (self.inputRoot)
        self._updateLookupTables (self.inputRoot, self.xsdLookupDict)

        self._includeAndImport (self.inputTree, self.inputTree, self.xsdIncludeDict, self.xsdLookupDict)

        if not self.errorHandler.hasErrors():
            # IDs must be unique within a schema file
            self.xsdIdDict = {}

            self._checkSchemaSecondLevel()

        # FIXME: Wellknown schemas are not included in the input tree although the internal attribute has been set!
        #        Better solution required than this workaround!
        self.inputRoot["__WellknownSchemasImported__"] = "false"


    ########################################
    # additional checks for schema files which are not covered by "xsStructs.xsd"
    #
    def _checkSchemaSecondLevel(self):

        targetNamespace = self.inputRoot.getAttribute("targetNamespace")
        if targetNamespace == "":
            self.errorHandler.raiseError("Empty string not allowed for target namespace!", self.inputRoot)

        self._checkElementNodesSecondLevel()
        self._checkNotationNodesSecondLevel()
        self._checkAnyNodesSecondLevel()
        self._checkGroupNodesSecondLevel()
        self._checkAttrGroupNodesSecondLevel()
        self._checkAttributeNodesSecondLevel()
        self._checkAnyAttributesSecondLevel()

        if self.errorHandler.hasErrors():
            return

        self._checkComplexTypesSecondLevel()
        self._checkSimpleTypesSecondLevel()

        self._checkParticlesSecondLevel()

        self._checkIdentityConstraintsSecondLevel()
        self._checkKeysSecondLevel()
        self._checkKeyRefsSecondLevel()

    ########################################
    # additional checks for element nodes
    #
    def _checkElementNodesSecondLevel(self):
        elementNodes = self.inputRoot.getElementsByTagNameNS (self.inputNsURI, "element")
        for elementNode in elementNodes:
            if not elementNode.hasAttribute("name") and not elementNode.hasAttribute("ref"):
                self._addError ("Element must have 'name' or 'ref' attribute!", elementNode)
                continue

            if elementNode.hasAttribute("ref"):
                for attrName in ("name", "type", "form"):
                    if elementNode.hasAttribute(attrName):
                        self._addError ("Element with 'ref' attribute must not have %s attribute!" %repr(attrName), elementNode)
                        continue

            complexTypeNode = elementNode.getFirstChildNS (self.inputNsURI, "complexType")
            simpleTypeNode = elementNode.getFirstChildNS (self.inputNsURI, "simpleType")
            if elementNode.hasAttribute("ref") and (complexTypeNode != None or simpleTypeNode != None):
                self._addError ("Element with 'ref' attribute must not have type definition!", elementNode)
                continue
            if elementNode.hasAttribute("type") and (complexTypeNode != None or simpleTypeNode != None):
                self._addError ("Element with 'type' attribute must not have type definition!", elementNode)
                continue
            
            if elementNode.hasAttribute("ref"):
                for forbiddenAttr in ("block", "nillable", "default", "fixed"):
                    if elementNode.hasAttribute(forbiddenAttr):
                        self._addError ("Element with 'ref' attribute must not have %s attribute!" %repr(forbiddenAttr), elementNode)

                self._checkReference (elementNode, self.xsdElementDict)

            if elementNode.hasAttribute("type"):
                self._checkType (elementNode, "type", self.xsdTypeDict)

            self._checkNodeId(elementNode)
            self._checkOccurs (elementNode)
            self._checkFixedDefault(elementNode)


    ########################################
    # additional checks for notation nodes
    #
    def _checkNotationNodesSecondLevel(self):
        notationNodes = self.inputRoot.getElementsByTagNameNS (self.inputNsURI, "notation")
        for notationNode in notationNodes:
            if not notationNode.hasAttribute("public") and not notationNode.hasAttribute("system"):
                self._addError ("Notation must have 'public' or 'system' attribute!", notationNode)
    
    
    ########################################
    # additional checks for anyNodes
    #
    def _checkAnyNodesSecondLevel(self):
        anyNodes = self.inputRoot.getElementsByTagNameNS (self.inputNsURI, "any")
        for anyNode in anyNodes:
            self._checkOccurs (anyNode)
            # check for unique ID
            self._checkNodeId (anyNode)


    ########################################
    # additional checks for group nodes
    #
    def _checkGroupNodesSecondLevel(self):
        groupNodes = self.inputRoot.getElementsByTagNameNS (self.inputNsURI, "group")
        for groupNode in groupNodes:
            self._checkNodeId(groupNode)
            if groupNode.hasAttribute("ref"):
                self._checkReference (groupNode, self.xsdGroupDict)
                self._checkOccurs (groupNode)
        if self.errorHandler.hasErrors():
            return
#        for groupNode in groupNodes:
#            if groupNode.hasAttribute("name"):
#                self._checkGroupNodeCircularDef(groupNode, {groupNode["name"]:1})
    
    def _checkGroupNodeCircularDef(self, groupNode, groupNameDict):
        childGroupsRefNodes, dummy, dummy = groupNode.getXPathList (".//%sgroup" %(self.inputNsPrefixString))
        for childGroupRefNode in childGroupsRefNodes:
            if childGroupRefNode.hasAttribute("ref"):
                childGroupNode = self.xsdGroupDict[childGroupRefNode.getQNameAttribute("ref")]
                if not groupNameDict.has_key(childGroupNode["name"]):
                    groupNameDict[childGroupNode["name"]] = 1
                    self._checkGroupNodeCircularDef(childGroupNode, groupNameDict)
                else:
                    self._addError ("Circular definition of group %s!" %repr(childGroupNode["name"]), childGroupNode)
                

    ########################################
    # additional checks for attributeGroup nodes
    #
    def _checkAttrGroupNodesSecondLevel(self):
        attributeGroupNodes = self.inputRoot.getElementsByTagNameNS (self.inputNsURI, "attributeGroup")
        for attributeGroupNode in attributeGroupNodes:
            if attributeGroupNode.hasAttribute("ref"):
                self._checkReference (attributeGroupNode, self.xsdAttrGroupDict)

            self._checkNodeId(attributeGroupNode)

    ########################################
    # additional checks for attribute nodes
    #
    def _checkAttributeNodesSecondLevel(self):
        attributeNodes = self.inputRoot.getElementsByTagNameNS (XSD_NAMESPACE, "attribute")
        for attributeNode in attributeNodes:
            if os.path.basename(attributeNode.getFilePath()) != "XMLSchema-instance.xsd":
                # global attributes must always be "qualified"
                if (attributeNode.getParentNode() == self.inputRoot or
                    self._getAttributeFormDefault(attributeNode) == "qualified"):
                    if self._getTargetNamespace(attributeNode) == XSI_NAMESPACE:
                        self._addError ("Target namespace of an attribute must not match '%s'!" %XSI_NAMESPACE, attributeNode)
                
            if not attributeNode.hasAttribute("name") and not attributeNode.hasAttribute("ref"):
                self._addError ("Attribute must have 'name' or 'ref' attribute!", attributeNode)
                continue

            if attributeNode.getAttribute("name") == "xmlns":
                self._addError ("Attribute must not match 'xmlns'!", attributeNode)

            if attributeNode.hasAttribute("ref"):
                if attributeNode.hasAttribute("name"):
                    self._addError ("Attribute may have 'name' OR 'ref' attribute!", attributeNode)
                if attributeNode.hasAttribute("type"):
                    self._addError ("Attribute may have 'type' OR 'ref' attribute!", attributeNode)
                if attributeNode.hasAttribute("form"):
                    self._addError ("Attribute 'form' is not allowed in this context!", attributeNode)

                if attributeNode.getFirstChildNS(XSD_NAMESPACE, "simpleType") != None:
                    self._addError ("Attribute may only have 'ref' attribute OR 'simpleType' child!", attributeNode)
                
                self._checkReference (attributeNode, self.xsdAttributeDict)

            if attributeNode.hasAttribute("type"):
                if attributeNode.getFirstChildNS(XSD_NAMESPACE, "simpleType") != None:
                    self._addError ("Attribute may only have 'type' attribute OR 'simpleType' child!", attributeNode)

                self._checkType (attributeNode, "type", self.xsdTypeDict, (XSD_NAMESPACE, "simpleType"))

            use = attributeNode.getAttribute("use")
            if use in ("required", "prohibited") and attributeNode.hasAttribute("default"):
                self._addError ("Attribute 'default' is not allowed, because 'use' is '%s'!" %(use), attributeNode)

            self._checkNodeId(attributeNode, unambiguousPerFile=0)

            self._checkFixedDefault(attributeNode)


    ########################################
    # additional checks for attribute wildcards
    #
    def _checkAnyAttributesSecondLevel(self):
        anyAttributeNodes, dummy, dummy = self.inputRoot.getXPathList (".//%sanyAttribute" %(self.inputNsPrefixString))
        for anyAttributeNode in anyAttributeNodes:
            # check for unique ID
            self._checkNodeId (anyAttributeNode)


    ########################################
    # additional checks for complex types
    #
    def _checkComplexTypesSecondLevel(self):
        prefix = self.inputNsPrefixString
        contentNodes, dummy, dummy = self.inputRoot.getXPathList (".//%(prefix)scomplexContent/%(prefix)srestriction | .//%(prefix)scomplexContent/%(prefix)sextension" % vars())
        for contentNode in contentNodes:
            self._checkType(contentNode, "base", self.xsdTypeDict, (XSD_NAMESPACE, "complexType"))

        contentNodes, dummy, dummy = self.inputRoot.getXPathList (".//%(prefix)ssimpleContent/%(prefix)srestriction | .//%(prefix)ssimpleContent/%(prefix)sextension" % vars())
        for contentNode in contentNodes:
            baseNsName = contentNode.getQNameAttribute("base")
            if baseNsName != (XSD_NAMESPACE, "anyType"):
                typeNsName = contentNode.getParentNode().getNsName()
                self._checkBaseType(contentNode, baseNsName, self.xsdTypeDict, typeNsName)
            else:
                self._addError ("Referred type must not be 'anyType'!", contentNode)
            # check for unique ID
            self._checkNodeId (contentNode)

        complexTypeNodes, dummy, dummy = self.inputRoot.getXPathList (".//%(prefix)scomplexType | .//%(prefix)sextension" % vars())
        for complexTypeNode in complexTypeNodes:
            validAttrDict = {}
            # check for duplicate attributes
            self._updateAttributeDict (complexTypeNode, validAttrDict, 1)
            # check for duplicate ID attributes
            idAttrNode = None
            for key, val in validAttrDict.items():
                attrType = val["RefNode"].getQNameAttribute("type")
                if attrType == (XSD_NAMESPACE, "ID"):
                    if not idAttrNode:
                        idAttrNode = val["Node"]
                    else:
                        # TODO: check also if attribute has a type which is derived from ID!
                        self._addError ("Two attribute declarations of complex type are IDs!", val["Node"])
                        
            # check for unique ID
            self._checkNodeId (complexTypeNode)

        contentNodes, dummy, dummy = self.inputRoot.getXPathList (".//%(prefix)scomplexType/%(prefix)s*" % vars())
        for contentNode in contentNodes:
            self._checkOccurs (contentNode)

        contentNodes, dummy, dummy = self.inputRoot.getXPathList (".//%(prefix)scomplexContent | .//%(prefix)ssimpleContent" % vars())
        for contentNode in contentNodes:
            # check for unique ID
            self._checkNodeId (contentNode)


    ########################################
    # additional checks for simple types
    #
    def _checkParticlesSecondLevel(self):
        prefix = self.inputNsPrefixString
        # check for duplicate element names
        particleNodes, dummy, dummy = self.inputRoot.getXPathList (".//%(prefix)sall | .//%(prefix)schoice | .//%(prefix)ssequence" % vars())
        for particleNode in particleNodes:
            elementTypeDict = {}
            elementNameDict = {}
            groupNameDict = {}
            self._checkContainedElements (particleNode, particleNode.getLocalName(), elementNameDict, elementTypeDict, groupNameDict)
            self._checkOccurs (particleNode)
            # check for unique ID
            self._checkNodeId (particleNode)
                

    def _checkContainedElements (self, node, particleType, elementNameDict, elementTypeDict, groupNameDict):
        prefix = self.inputNsPrefixString
        for childNode in node.getChildren():
            childParticleType = childNode.getLocalName()
            if childParticleType in ("sequence", "choice", "all"):
                dummy = {}
                self._checkContainedElements (childNode, childParticleType, dummy, elementTypeDict, groupNameDict)
            elif childParticleType in ("group"):
                if childNode["ref"] != None:
                    childGroupNode = self.xsdGroupDict[childNode.getQNameAttribute("ref")]
                    if not groupNameDict.has_key(childGroupNode["name"]):
                        groupNameDict[childGroupNode["name"]] = 1
                        for cChildNode in childGroupNode.getChildren():
                            if cChildNode.getLocalName() != "annotation":
                                self._checkContainedElements (cChildNode, particleType, elementNameDict, elementTypeDict, groupNameDict)
                    else:
                        self._addError ("Circular definition of group %s!" %repr(childGroupNode["name"]), childNode)
                else:
                    for cChildNode in childNode.getChildren():
                        if cChildNode.getLocalName() != "annotation":
                            self._checkContainedElements (cChildNode, particleType, elementNameDict, elementTypeDict, groupNameDict)
            else:
                if childNode.getLocalName() == "any":
                    elementName = childNode.getAttribute("namespace")
                else:
                    elementName = childNode.getAttributeOrDefault("name", childNode.getAttribute("ref"))

                if childNode.hasAttribute("type"):
                    if not elementTypeDict.has_key(elementName):
                        elementTypeDict[elementName] = childNode["type"]
                    elif childNode["type"] != elementTypeDict[elementName]:
                        self._addError ("Element %s has identical name and different types within %s!" %(repr(elementName), repr(particleType)), childNode)
                if particleType != "sequence":
                    if not elementNameDict.has_key(elementName):
                        elementNameDict[elementName] = 1
                    else:
                        self._addError ("Element %s is not unique within %s!" %(repr(elementName), repr(particleType)), childNode)


    ########################################
    # additional checks for simple types
    #
    def _checkSimpleTypesSecondLevel(self):
        prefix = self.inputNsPrefixString

        simpleTypeNodes, dummy, dummy = self.inputRoot.getXPathList (".//%(prefix)ssimpleType" % vars())
        for simpleTypeNode in simpleTypeNodes:
            # check for unique ID
            self._checkNodeId (simpleTypeNode)
        
        restrictionNodes, dummy, dummy = self.inputRoot.getXPathList (".//%(prefix)ssimpleType/%(prefix)srestriction" % vars())
        for restrictionNode in restrictionNodes:

            # check for unique ID
            self._checkNodeId (restrictionNode)

            if not restrictionNode.hasAttribute("base") and restrictionNode.getFirstChildNS (self.inputNsURI, "simpleType") == None:
                self._addError ("Simple type restriction must have 'base' attribute or 'simpleType' child tag!", restrictionNode)

            if restrictionNode.hasAttribute("base") and restrictionNode.getFirstChildNS (self.inputNsURI, "simpleType") != None:
                self._addError ("Simple type restriction must not have 'base' attribute and 'simpleType' child tag!", restrictionNode)

            if restrictionNode.hasAttribute("base"):
                self._checkType(restrictionNode, "base", self.xsdTypeDict)

            minExcl = restrictionNode.getFirstChildNS(self.inputNsURI, "minExclusive")
            minIncl = restrictionNode.getFirstChildNS(self.inputNsURI, "minInclusive")
            if minExcl != None and minIncl != None:
                self._addError ("Restriction attributes 'minExclusive' and 'minInclusive' cannot be defined together!", restrictionNode)
            maxExcl = restrictionNode.getFirstChildNS(self.inputNsURI, "maxExclusive")
            maxIncl = restrictionNode.getFirstChildNS(self.inputNsURI, "maxInclusive")
            if maxExcl != None and maxIncl != None:
                self._addError ("Restriction attributes 'maxExclusive' and 'maxInclusive' cannot be defined together!", restrictionNode)

        # check facets of associated primitive type
        for restrictionNode in restrictionNodes:
            try:
                if restrictionNode.hasAttribute("base"):
                    facetNsName = self._getFacetType (restrictionNode, [restrictionNode.getParentNode(),], self.xsdTypeDict)
                    if not facetNsName:
                        continue
                    if _localFacetDict.has_key(facetNsName):
                        suppFacets = _localFacetDict[facetNsName]
                    else:
                        suppFacets, dummy, dummy = self.xsdTypeDict[facetNsName].getXPathList (".//hfp:hasFacet/@name" % vars())

                    specifiedFacets = {"length":None, "minLength":None, "maxLength":None,
                                       "minExclusive":None, "minInclusive":None, "maxExclusive":None, "maxInclusive":None,
                                       "totalDigits": None, "fractionDigits":None}
                    for childNode in restrictionNode.getChildren():
                        if childNode.getLocalName() in suppFacets:
                            if specifiedFacets.has_key(childNode.getLocalName()):
                                specifiedFacets[childNode.getLocalName()] = childNode["value"]
                            facetElementNode = self.xsdElementDict[childNode.getNsName()]
                            try:
                                self._checkElementTag (facetElementNode, restrictionNode, (childNode,), 0)
                            except TagException, errInst:
                                self._addError (errInst.errstr, errInst.node, errInst.endTag)
                            if childNode.getLocalName() in ("enumeration", "minExclusive", "minInclusive", "maxExclusive", "maxInclusive"):
                                simpleTypeReturnDict = self._checkSimpleType (restrictionNode, "base", childNode, "value", childNode["value"], None, checkAttribute=1)
                                if simpleTypeReturnDict != None and simpleTypeReturnDict.has_key("orderedValue"):
                                    if childNode.getLocalName() != "enumeration":
                                        specifiedFacets[childNode.getLocalName()] = simpleTypeReturnDict["orderedValue"]
                        elif childNode.getLocalName() == "enumeration":
                            self._checkSimpleType (restrictionNode, "base", childNode, "value", childNode["value"], None, checkAttribute=1)
                        elif childNode.getLocalName() != "annotation":
                            self._addError ("Facet %s not allowed for base type %s!" %(childNode.getLocalName(), repr(restrictionNode["base"])), childNode)
                    if specifiedFacets["length"] != None:
                        if specifiedFacets["minLength"] != None or specifiedFacets["maxLength"] != None:
                            self._addError ("Facet 'minLength' and 'maxLength' not allowed if facet 'length' is specified!", restrictionNode)
                    else:
                        if specifiedFacets["maxLength"] != None and specifiedFacets["minLength"] != None:
                            if int(specifiedFacets["maxLength"]) < int(specifiedFacets["minLength"]):
                                self._addError ("Facet 'maxLength' < facet 'minLength'!", restrictionNode)

                    if specifiedFacets["totalDigits"] != None and specifiedFacets["fractionDigits"] != None:
                        if int(specifiedFacets["totalDigits"]) < int(specifiedFacets["fractionDigits"]):
                            self._addError ("Facet 'totalDigits' must be >= 'fractionDigits'!", restrictionNode)

                    if specifiedFacets["minExclusive"] != None and specifiedFacets["minInclusive"] != None:
                        self._addError ("Facets 'minExclusive' and 'minInclusive' are mutually exclusive!", restrictionNode)
                    if specifiedFacets["maxExclusive"] != None and specifiedFacets["maxInclusive"] != None:
                        self._addError ("Facets 'maxExclusive' and 'maxInclusive' are mutually exclusive!", restrictionNode)

                    minValue = specifiedFacets["minExclusive"]
                    if specifiedFacets["minInclusive"] != None:
                        minValue = specifiedFacets["minInclusive"]
                    maxValue = specifiedFacets["maxExclusive"]
                    if specifiedFacets["maxInclusive"] != None:
                        maxValue = specifiedFacets["maxInclusive"]
                    # TODO: use orderedValue for '<' check!!
                    if minValue != None and maxValue != None and maxValue < minValue:
                        self._addError ("maxValue facet < minValue facet!", restrictionNode)

            except TagException:
                self._addError ("Primitive type for base type not found!", restrictionNode)

        listNodes, dummy, dummy = self.inputRoot.getXPathList (".//%(prefix)slist" % vars())
        for listNode in listNodes:
            # check for unique ID
            self._checkNodeId (listNode)

            if not listNode.hasAttribute("itemType") and listNode.getFirstChildNS (self.inputNsURI, "simpleType") == None:
                self._addError ("List type must have 'itemType' attribute or 'simpleType' child tag!", listNode)
            elif listNode.hasAttribute("itemType") and listNode.getFirstChildNS (self.inputNsURI, "simpleType") != None:
                self._addError ("List type must not have 'itemType' attribute and 'simpleType' child tag!", listNode)
            elif listNode.hasAttribute("itemType"):
                itemType = self._checkType(listNode, "itemType", self.xsdTypeDict)
                if self.xsdTypeDict.has_key(itemType):
                    if self.xsdTypeDict[itemType].getLocalName() != "simpleType":
                        self._addError ("ItemType %s must be a simple type!" %(repr(itemType)), listNode)
                    elif self.xsdTypeDict[itemType].getFirstChild().getLocalName() == "list":
                        self._addError ("ItemType %s must not be a list type!" %(repr(itemType)), listNode)

        unionNodes, dummy, dummy = self.inputRoot.getXPathList (".//%(prefix)ssimpleType/%(prefix)sunion" % vars())
        for unionNode in unionNodes:
            # check for unique ID
            self._checkNodeId (unionNode)

            if not unionNode.hasAttribute("memberTypes"):
                for childNode in unionNode.getChildren():
                    if childNode.getLocalName() != "annotation":
                        break
                else:
                    self._addError ("Union must not be empty!", unionNode)
            else:
                for memberType in string.split(unionNode["memberTypes"]):
                    memberNsName = unionNode.qName2NsName(memberType, 1)
                    self._checkBaseType(unionNode, memberNsName, self.xsdTypeDict)
                    if self.xsdTypeDict.has_key(memberNsName):
                        if self.xsdTypeDict[memberNsName].getLocalName() != "simpleType":
                            self._addError ("MemberType %s must be a simple type!" %(repr(memberNsName)), unionNode)

        patternNodes, dummy, dummy = self.inputRoot.getXPathList (".//%(prefix)spattern" % vars())
        for patternNode in patternNodes:
            pattern = patternNode["value"]
            try:
                pattern = substituteSpecialEscChars (pattern)
                try:
                    test = re.compile(pattern)
                except Exception, errstr:
                    self._addError (str(errstr), patternNode)
                    self._addError ("%s is not a valid regular expression!" %(repr(patternNode["value"])), patternNode)
            except SyntaxError, errInst:
                    self._addError (repr(errInst[0]), patternNode)


    ########################################
    # additional checks for keyrefs
    #
    def _checkIdentityConstraintsSecondLevel(self):
        identityConstraintNodes, dummy, dummy = self.inputRoot.getXPathList (".//%sunique" %(self.inputNsPrefixString))
        for identityConstraintNode in identityConstraintNodes:
            # check for unique ID
            self._checkNodeId (identityConstraintNode)

            selectorNode = identityConstraintNode.getFirstChildNS(XSD_NAMESPACE, "selector")
            self._checkNodeId (selectorNode)
            try:
                completeChildList, attrNodeList, attrNsNameFirst = identityConstraintNode.getParentNode().getXPathList (selectorNode["xpath"], selectorNode)
                if attrNsNameFirst != None:
                    self._addError ("Selection of attributes is not allowed for selector!", selectorNode)
            except Exception, errstr:
                self._addError (errstr, selectorNode)

            try:
                fieldNode = identityConstraintNode.getFirstChildNS(XSD_NAMESPACE, "field")
                identityConstraintNode.getParentNode().getXPathList (fieldNode["xpath"], fieldNode)
                self._checkNodeId (fieldNode)
            except Exception, errstr:
                self._addError (errstr, fieldNode)


    ########################################
    # additional checks for keyrefs
    #
    def _checkKeysSecondLevel(self):
        keyNodes, dummy, dummy = self.inputRoot.getXPathList (".//%skey" %(self.inputNsPrefixString))
        for keyNode in keyNodes:
            # check for unique ID
            self._checkNodeId (keyNode)

            fieldNode = keyNode.getFirstChildNS(XSD_NAMESPACE, "field")
            if fieldNode != None:
                self._checkNodeId (fieldNode)
                

    ########################################
    # additional checks for keyrefs
    #
    def _checkKeyRefsSecondLevel(self):
        keyrefNodes, dummy, dummy = self.inputRoot.getXPathList (".//%skeyref" %(self.inputNsPrefixString))
        for keyrefNode in keyrefNodes:
            # check for unique ID
            self._checkNodeId (keyrefNode)

            self._checkKeyRef(keyrefNode, self.xsdIdentityConstrDict)
                

    ########################################
    # helper methods
    #

    def _checkFixedDefault(self, node):
        if node.hasAttribute("default") and node.hasAttribute("fixed"):
            self._addError ("%s may have 'default' OR 'fixed' attribute!" %repr(node.getLocalName()), node)
        if  node.hasAttribute("default"):
            self._checkSimpleType (node, "type", node, "default", node["default"], None, checkAttribute=1)
        if  node.hasAttribute("fixed"):
            self._checkSimpleType (node, "type", node, "fixed", node["fixed"], None, checkAttribute=1)
    
    
    def _checkReference(self, node, dict):
        baseNsName = node.getQNameAttribute("ref")
        if dict.has_key(baseNsName):
            refNode = dict[baseNsName]
            fixedValue = node.getAttribute("fixed")
            fixedRefValue = refNode.getAttribute("fixed")
            if fixedValue != None and fixedRefValue != None and fixedValue != fixedRefValue:
                self._addError ("Fixed value %s of attribute does not match fixed value %s of reference!" %(repr(fixedValue), repr(fixedRefValue)), node)
                
        else:
            self._addError ("Reference %s not found!" %(repr(baseNsName)), node)

    def _checkType(self, node, typeAttrName, dict, typeNsName=None):
        baseNsName = node.getQNameAttribute(typeAttrName)
        self._checkBaseType(node, baseNsName, dict, typeNsName)
        return baseNsName
    
    def _checkBaseType(self, node, baseNsName, dict, typeNsName=None):
        if not dict.has_key(baseNsName) and baseNsName != (XSD_NAMESPACE, "anySimpleType"):
            self._addError ("Definition of type %s not found!" %(repr(baseNsName)), node)
        elif typeNsName != None:
            if typeNsName == (XSD_NAMESPACE, "simpleContent"):
                if node.getNsName() == (XSD_NAMESPACE, "restriction"):
                    if (baseNsName != (XSD_NAMESPACE, "anySimpleType") and
                        dict[baseNsName].getNsName() == (XSD_NAMESPACE, "complexType") and
                        dict[baseNsName].getFirstChild().getNsName() == typeNsName):
                        pass
                    else:
                        self._addError ("Referred type %s must be a complex type with simple content!" %(repr(baseNsName)), node)
                else: # extension
                    if (baseNsName == (XSD_NAMESPACE, "anySimpleType") or
                        dict[baseNsName].getNsName() == (XSD_NAMESPACE, "simpleType") or
                       (dict[baseNsName].getNsName() == (XSD_NAMESPACE, "complexType") and
                        dict[baseNsName].getFirstChild().getNsName() == typeNsName)):
                        pass
                    else:
                        self._addError ("Referred type %s must be a simple type or a complex type with simple content!" %(repr(baseNsName)), node)
            else:
                if typeNsName == (XSD_NAMESPACE, "simpleType") and baseNsName == (XSD_NAMESPACE, "anySimpleType"):
                    pass
                elif dict[baseNsName].getNsName() != typeNsName:
                    self._addError ("Referred type %s must be a %s!" %(repr(baseNsName), repr(typeNsName)), node)


    def _checkKeyRef(self, keyrefNode, dict):
        baseNsName = keyrefNode.getQNameAttribute("refer")
        if not dict.has_key(baseNsName):
            self._addError ("keyref refers unknown key %s!" %(repr(baseNsName)), keyrefNode)
        else:
            keyNode = dict[baseNsName]["Node"]
            if keyNode.getNsName() not in ((XSD_NAMESPACE, "key"), (XSD_NAMESPACE, "unique")):
                self._addError ("reference to non-key constraint %s!" %(repr(baseNsName)), keyrefNode)
            if len(keyrefNode.getChildrenNS(XSD_NAMESPACE, "field")) != len(keyNode.getChildrenNS(XSD_NAMESPACE, "field")):
                self._addError ("key/keyref field size mismatch!", keyrefNode)
                
            
    def _checkOccurs (self, node):
        minOccurs = node.getAttributeOrDefault("minOccurs", "1")
        maxOccurs = node.getAttributeOrDefault("maxOccurs", "1")
        if maxOccurs != "unbounded":
            if string.atoi(minOccurs) > string.atoi(maxOccurs):
                self._addError ("Attribute minOccurs > maxOccurs!", node)


    def _checkNodeId (self, node, unambiguousPerFile=1):
        if node.hasAttribute("id"):
            # id must only be unambiguous within one file
            if unambiguousPerFile:
                nodeId = (node.getAbsUrl(), collapseString(node["id"]))
            else:
                nodeId = collapseString(node["id"])
            if not self.xsdIdDict.has_key(nodeId):
                self.xsdIdDict[nodeId] = node
            else:
                self._addError ("There are multiple occurences of ID value %s!" %repr(nodeId), node)


    def _getFacetType(self, node, parentNodeList, xsdTypeDict):
            baseNsName = node.getQNameAttribute("base")
            try:
                baseNode = xsdTypeDict[baseNsName]
            except:
                self._addError ("Base type %s must be an atomic simple type definition or a builtin type!" %repr(baseNsName), node)
                return None

            if baseNode in parentNodeList:
                self._addError ("Circular type definition (type is contained in its own type hierarchy)!", node)
                return None
                
            if baseNode.getNsName() == (XSD_NAMESPACE, "simpleType"):
                if baseNode.getAttribute("facetType") != None:
                    facetType = baseNode.qName2NsName(baseNode["facetType"], 1)
                    node.getParentNode()["facetType"] = node.nsName2QName(facetType)
                    return facetType
                else:
                    for baseNodeType in ("list", "union"):
                        if baseNode.getFirstChildNS (XSD_NAMESPACE, baseNodeType) != None:
                            return (XSD_NAMESPACE, baseNodeType)
                    else:
                        parentNodeList.append(node)
                        return self._getFacetType(baseNode.getFirstChildNS(XSD_NAMESPACE, "restriction"), parentNodeList, xsdTypeDict)    
            else:
                self._addError ("Base type %s must be an atomic simple type definition or a builtin type!" %repr(baseNsName), node)
                return None
            

