# Copyright (c) 2003, The Regents of the University of California,
# through Lawrence Berkeley National Laboratory (subject to receipt of
# any required approvals from the U.S. Dept. of Energy).  All rights
# reserved. 
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.

ident = "$Id$"

import types, weakref, sys, warnings
from pysphere.ZSI.wstools.Namespaces import SCHEMA, XMLNS, SOAP, APACHE
from pysphere.ZSI.wstools.Utility import DOM, DOMException, Collection, SplitQName, basejoin
from StringIO import StringIO

# If we have no threading, this should be a no-op
from threading import RLock

# 
# Collections in XMLSchema class
# 
TYPES = 'types'
ATTRIBUTE_GROUPS = 'attr_groups'
ATTRIBUTES = 'attr_decl'
ELEMENTS = 'elements'
MODEL_GROUPS = 'model_groups'
BUILT_IN_NAMESPACES = [SOAP.ENC,] + SCHEMA.XSD_LIST + [APACHE.AXIS_NS]

def GetSchema(component):
    """convience function for finding the parent XMLSchema instance.
    """
    parent = component
    while not isinstance(parent, XMLSchema):
        parent = parent._parent()
    return parent
    
class SchemaReader:
    """A SchemaReader creates XMLSchema objects from urls and xml data.
    """
    
    namespaceToSchema = {}
    
    def __init__(self, domReader=None, base_url=None):
        """domReader -- class must implement DOMAdapterInterface
           base_url -- base url string
        """
        self.__base_url = base_url
        self.__readerClass = domReader
        if not self.__readerClass:
            self.__readerClass = DOMAdapter
        self._includes = {}
        self._imports = {}

    def __setImports(self, schema):
        """Add dictionary of imports to schema instance.
           schema -- XMLSchema instance
        """
        for ns in schema.imports.iterkeys(): 
            if ns in self._imports:
                schema.addImportSchema(self._imports[ns])

    def __setIncludes(self, schema):
        """Add dictionary of includes to schema instance.
           schema -- XMLSchema instance
        """
        for schemaLocation in schema.includes.iterkeys(): 
            if schemaLocation in self._includes:
                schema.addIncludeSchema(schemaLocation, self._imports[schemaLocation])

    def addSchemaByLocation(self, location, schema):
        """provide reader with schema document for a location.
        """
        self._includes[location] = schema

    def addSchemaByNamespace(self, schema):
        """provide reader with schema document for a targetNamespace.
        """
        self._imports[schema.targetNamespace] = schema

    def loadFromNode(self, parent, element):
        """element -- DOM node or document
           parent -- WSDLAdapter instance
        """
        reader = self.__readerClass(element)
        schema = XMLSchema(parent)
        #HACK to keep a reference
        schema.wsdl = parent
        schema.setBaseUrl(self.__base_url)
        schema.load(reader)
        return schema
        
    def loadFromStream(self, _file, url=None):
        """Return an XMLSchema instance loaded from a file object.
           _file -- file object
           url -- base location for resolving imports/includes.
        """
        reader = self.__readerClass()
        reader.loadDocument(_file)
        schema = XMLSchema()
        if url is not None:
            schema.setBaseUrl(url)
        schema.load(reader)
        self.__setIncludes(schema)
        self.__setImports(schema)
        return schema

    def loadFromString(self, data):
        """Return an XMLSchema instance loaded from an XML string.
           data -- XML string
        """
        return self.loadFromStream(StringIO(data))

    def loadFromURL(self, url, schema=None):
        """Return an XMLSchema instance loaded from the given url.
           url -- URL to dereference
           schema -- Optional XMLSchema instance.
        """
        reader = self.__readerClass()
        if self.__base_url:
            url = basejoin(self.__base_url,url)

        reader.loadFromURL(url)
        schema = schema or XMLSchema()
        schema.setBaseUrl(url)
        schema.load(reader)
        self.__setIncludes(schema)
        self.__setImports(schema)
        return schema

    def loadFromFile(self, filename):
        """Return an XMLSchema instance loaded from the given file.
           filename -- name of file to open
        """
        if self.__base_url:
            filename = basejoin(self.__base_url,filename)
        f = open(filename, 'rb')
        try:
            schema = self.loadFromStream(f, filename)
        finally:
            f.close()

        return schema


class SchemaError(Exception): 
    pass

class NoSchemaLocationWarning(Exception): 
    pass


###########################
# DOM Utility Adapters 
##########################
class DOMAdapterInterface:
    def hasattr(self, attr, ns=None):
        """return true if node has attribute 
           attr -- attribute to check for
           ns -- namespace of attribute, by default None
        """
        raise NotImplementedError('adapter method not implemented')

    def getContentList(self, *contents):
        """returns an ordered list of child nodes
           *contents -- list of node names to return
        """
        raise NotImplementedError('adapter method not implemented')

    def setAttributeDictionary(self, attributes):
        """set attribute dictionary
        """
        raise NotImplementedError('adapter method not implemented')

    def getAttributeDictionary(self):
        """returns a dict of node's attributes
        """
        raise NotImplementedError('adapter method not implemented')

    def getNamespace(self, prefix):
        """returns namespace referenced by prefix.
        """
        raise NotImplementedError('adapter method not implemented')

    def getTagName(self):
        """returns tagName of node
        """
        raise NotImplementedError('adapter method not implemented')


    def getParentNode(self):
        """returns parent element in DOMAdapter or None
        """
        raise NotImplementedError('adapter method not implemented')

    def loadDocument(self, _file):
        """load a Document from a file object
           file --
        """
        raise NotImplementedError('adapter method not implemented')

    def loadFromURL(self, url):
        """load a Document from an url
           url -- URL to dereference
        """
        raise NotImplementedError('adapter method not implemented')


class DOMAdapter(DOMAdapterInterface):
    """Adapter for ZSI.Utility.DOM
    """
    def __init__(self, node=None):
        """Reset all instance variables.
           element -- DOM document, node, or None
        """
        if hasattr(node, 'documentElement'):
            self.__node = node.documentElement
        else:
            self.__node = node
        self.__attributes = None

    def getNode(self):
        return self.__node
    
    def hasattr(self, attr, ns=None):
        """attr -- attribute 
           ns -- optional namespace, None means unprefixed attribute.
        """
        if not self.__attributes:
            self.setAttributeDictionary()
        if ns:
            return attr in self.__attributes.get(ns,{})
        return attr in self.__attributes

    def getContentList(self, *contents):
        nodes = []
        ELEMENT_NODE = self.__node.ELEMENT_NODE
        for child in DOM.getElements(self.__node, None):
            if child.nodeType == ELEMENT_NODE and\
               SplitQName(child.tagName)[1] in contents:
                nodes.append(child)
        return [self.__class__(x) for x in nodes]

    def setAttributeDictionary(self):
        self.__attributes = {}
        for v in self.__node._attrs.itervalues():
            self.__attributes[v.nodeName] = v.nodeValue

    def getAttributeDictionary(self):
        if not self.__attributes:
            self.setAttributeDictionary()
        return self.__attributes

    def getTagName(self):
        return self.__node.tagName

    def getParentNode(self):
        if self.__node.parentNode.nodeType == self.__node.ELEMENT_NODE:
            return DOMAdapter(self.__node.parentNode)
        return None

    def getNamespace(self, prefix):
        """prefix -- deference namespace prefix in node's context.
           Ascends parent nodes until found.
        """
        namespace = None
        if prefix == 'xmlns':
            namespace = DOM.findDefaultNS(prefix, self.__node)
        else:
            try:
                namespace = DOM.findNamespaceURI(prefix, self.__node)
            except DOMException:
                if prefix != 'xml':
                    raise SchemaError('%s namespace not declared for %s'
                        %(prefix, self.__node._get_tagName()))
                namespace = XMLNS.XML
        return namespace
           
    def loadDocument(self, _file):
        self.__node = DOM.loadDocument(_file)
        if hasattr(self.__node, 'documentElement'):
            self.__node = self.__node.documentElement

    def loadFromURL(self, url):
        self.__node = DOM.loadFromURL(url)
        if hasattr(self.__node, 'documentElement'):
            self.__node = self.__node.documentElement

 
class XMLBase: 
    """ These class variables are for string indentation.
    """ 
    tag = None
    __indent = 0
    __rlock = RLock()

    def __str__(self):
        XMLBase.__rlock.acquire()
        XMLBase.__indent += 1
        tmp = "<" + str(self.__class__) + '>\n'
        for k,v in self.__dict__.iteritems():
            tmp += "%s* %s = %s\n" %(XMLBase.__indent*'  ', k, v)
        XMLBase.__indent -= 1 
        XMLBase.__rlock.release()
        return tmp


"""Marker Interface:  can determine something about an instances properties by using 
        the provided convenience functions.

"""
class DefinitionMarker: 
    """marker for definitions
    """
    pass

class DeclarationMarker: 
    """marker for declarations
    """
    pass

class AttributeMarker: 
    """marker for attributes
    """
    pass

class AttributeGroupMarker: 
    """marker for attribute groups
    """
    pass

class WildCardMarker: 
    """marker for wildcards
    """
    pass

class ElementMarker: 
    """marker for wildcards
    """
    pass

class ReferenceMarker: 
    """marker for references
    """
    pass

class ModelGroupMarker: 
    """marker for model groups
    """
    pass

class AllMarker(ModelGroupMarker): 
    """marker for all model group
    """
    pass

class ChoiceMarker(ModelGroupMarker): 
    """marker for choice model group
    """
    pass

class SequenceMarker(ModelGroupMarker): 
    """marker for sequence model group
    """
    pass

class ExtensionMarker: 
    """marker for extensions
    """
    pass

class RestrictionMarker: 
    """marker for restrictions
    """
    facets = ['enumeration', 'length', 'maxExclusive', 'maxInclusive',\
        'maxLength', 'minExclusive', 'minInclusive', 'minLength',\
        'pattern', 'fractionDigits', 'totalDigits', 'whiteSpace']

class SimpleMarker: 
    """marker for simple type information
    """
    pass

class ListMarker: 
    """marker for simple type list
    """
    pass

class UnionMarker: 
    """marker for simple type Union
    """
    pass


class ComplexMarker: 
    """marker for complex type information
    """
    pass

class LocalMarker: 
    """marker for complex type information
    """
    pass


class MarkerInterface:
    def isDefinition(self):
        return isinstance(self, DefinitionMarker)

    def isDeclaration(self):
        return isinstance(self, DeclarationMarker)

    def isAttribute(self):
        return isinstance(self, AttributeMarker)

    def isAttributeGroup(self):
        return isinstance(self, AttributeGroupMarker)

    def isElement(self):
        return isinstance(self, ElementMarker)

    def isReference(self):
        return isinstance(self, ReferenceMarker)

    def isWildCard(self):
        return isinstance(self, WildCardMarker)

    def isModelGroup(self):
        return isinstance(self, ModelGroupMarker)

    def isAll(self):
        return isinstance(self, AllMarker)

    def isChoice(self):
        return isinstance(self, ChoiceMarker)

    def isSequence(self):
        return isinstance(self, SequenceMarker)

    def isExtension(self):
        return isinstance(self, ExtensionMarker)

    def isRestriction(self):
        return isinstance(self, RestrictionMarker)

    def isSimple(self):
        return isinstance(self, SimpleMarker)

    def isComplex(self):
        return isinstance(self, ComplexMarker)

    def isLocal(self):
        return isinstance(self, LocalMarker)

    def isList(self):
        return isinstance(self, ListMarker)

    def isUnion(self):
        return isinstance(self, UnionMarker)


##########################################################
# Schema Components
#########################################################
class XMLSchemaComponent(XMLBase, MarkerInterface):
    """
       class variables: 
           required -- list of required attributes
           attributes -- dict of default attribute values, including None.
               Value can be a function for runtime dependencies.
           contents -- dict of namespace keyed content lists.
               'xsd' content of xsd namespace.
           xmlns_key -- key for declared xmlns namespace.
           xmlns -- xmlns is special prefix for namespace dictionary
           xml -- special xml prefix for xml namespace.
    """
    required = []
    attributes = {}
    contents = {}
    xmlns_key = ''
    xmlns = 'xmlns'
    xml = 'xml'

    def __init__(self, parent=None):
        """parent -- parent instance
           instance variables:
               attributes -- dictionary of node's attributes
        """
        self.attributes = None
        self._parent = parent
        if self._parent:
            self._parent = weakref.ref(parent)

        if not self.__class__ == XMLSchemaComponent\
           and not (type(self.__class__.required) == type(XMLSchemaComponent.required)\
           and type(self.__class__.attributes) == type(XMLSchemaComponent.attributes)\
           and type(self.__class__.contents) == type(XMLSchemaComponent.contents)):
            raise RuntimeError('Bad type for a class variable in %s' %self.__class__)

    def getItemTrace(self):
        """Returns a node trace up to the <schema> item.
        """
        item, path, name, ref = self, [], 'name', 'ref'
        while not isinstance(item,XMLSchema) and not isinstance(item,WSDLToolsAdapter):
            attr = item.getAttribute(name)
            if not attr:
                attr = item.getAttribute(ref)
                if not attr:
                    path.append('<%s>' %(item.tag))
                else: 
                    path.append('<%s ref="%s">' %(item.tag, attr))
            else:
                path.append('<%s name="%s">' %(item.tag,attr))

            item = item._parent()
        try:
            tns = item.getTargetNamespace()
        except: 
            tns = ''
        path.append('<%s targetNamespace="%s">' %(item.tag, tns))
        path.reverse()
        return ''.join(path)

    def getTargetNamespace(self):
        """return targetNamespace
        """
        parent = self
        targetNamespace = 'targetNamespace'
        tns = self.attributes.get(targetNamespace)
        while not tns and parent and parent._parent is not None:
            parent = parent._parent()
            tns = parent.attributes.get(targetNamespace)
        return tns or ''

    def getAttributeDeclaration(self, attribute):
        """attribute -- attribute with a QName value (eg. type).
           collection -- check types collection in parent Schema instance
        """
        return self.getQNameAttribute(ATTRIBUTES, attribute)

    def getAttributeGroup(self, attribute):
        """attribute -- attribute with a QName value (eg. type).
           collection -- check types collection in parent Schema instance
        """
        return self.getQNameAttribute(ATTRIBUTE_GROUPS, attribute)

    def getTypeDefinition(self, attribute):
        """attribute -- attribute with a QName value (eg. type).
           collection -- check types collection in parent Schema instance
        """
        return self.getQNameAttribute(TYPES, attribute)

    def getElementDeclaration(self, attribute):
        """attribute -- attribute with a QName value (eg. element).
           collection -- check elements collection in parent Schema instance.
        """
        return self.getQNameAttribute(ELEMENTS, attribute)

    def getModelGroup(self, attribute):
        """attribute -- attribute with a QName value (eg. ref).
           collection -- check model_group collection in parent Schema instance.
        """
        return self.getQNameAttribute(MODEL_GROUPS, attribute)

    def getQNameAttribute(self, collection, attribute):
        """returns object instance representing QName --> (namespace,name),
           or if does not exist return None.
           attribute -- an information item attribute, with a QName value.
           collection -- collection in parent Schema instance to search.
        """
        tdc = self.getAttributeQName(attribute)
        if not tdc:
            return

        obj = self.getSchemaItem(collection, tdc.getTargetNamespace(), tdc.getName())
        if obj: 
            return obj

#        raise SchemaError, 'No schema item "%s" in collection %s' %(tdc, collection)
        return

    def getSchemaItem(self, collection, namespace, name):
        """returns object instance representing namespace, name,
           or if does not exist return None if built-in, else
           raise SchemaError.
           
           namespace -- namespace item defined in.
           name -- name of item.
           collection -- collection in parent Schema instance to search.
        """
        parent = GetSchema(self)
        if parent.targetNamespace == namespace:
            try:
                obj = getattr(parent, collection)[name]
            except KeyError:
                raise KeyError('targetNamespace(%s) collection(%s) has no item(%s)'
                    %(namespace, collection, name))
                    
            return obj
        
        if namespace not in parent.imports:
            if namespace in BUILT_IN_NAMESPACES:            
                # built-in just return
                # WARNING: expecting import if "redefine" or add to built-in namespace.
                return
            
            raise SchemaError('schema "%s" does not import namespace "%s"' %(
                parent.targetNamespace, namespace))
            
        # Lazy Eval
        schema = parent.imports[namespace]
        if not isinstance(schema, XMLSchema):
            schema = schema.getSchema()    
            if schema is not None:
                parent.imports[namespace] = schema
            
        if schema is None:
            if namespace in BUILT_IN_NAMESPACES:
                # built-in just return
                return
            
            raise SchemaError('no schema instance for imported namespace (%s).'
                %(namespace))
                
        if not isinstance(schema, XMLSchema):
            raise TypeError('expecting XMLSchema instance not "%r"' %schema)
                
        try:
            obj = getattr(schema, collection)[name]
        except KeyError:
            raise KeyError('targetNamespace(%s) collection(%s) has no item(%s)'
                %(namespace, collection, name))
                    
        return obj

    def getXMLNS(self, prefix=None):
        """deference prefix or by default xmlns, returns namespace. 
        """
        if prefix == XMLSchemaComponent.xml:
            return XMLNS.XML
        parent = self
        ns = self.attributes[XMLSchemaComponent.xmlns].get(prefix or\
                XMLSchemaComponent.xmlns_key)
        while not ns:
            parent = parent._parent()
            ns = parent.attributes[XMLSchemaComponent.xmlns].get(prefix or\
                    XMLSchemaComponent.xmlns_key)
            if not ns and isinstance(parent, WSDLToolsAdapter):
                if prefix is None:
                    return ''
                raise SchemaError('unknown prefix %s' % prefix)
        return ns

    def getAttribute(self, attribute):
        """return requested attribute value or None
        """
        if isinstance(attribute, (list, tuple)):
            if len(attribute) != 2:
                raise LookupError('To access attributes must use name or (namespace,name)')

            ns_dict = self.attributes.get(attribute[0])
            if ns_dict is None:
                return None

            return ns_dict.get(attribute[1])

        return self.attributes.get(attribute)

    def getAttributeQName(self, attribute):
        """return requested attribute value as (namespace,name) or None 
        """
        qname = self.getAttribute(attribute)
        if isinstance(qname, TypeDescriptionComponent) is True:
            return qname
        if qname is None:
            return None

        prefix,ncname = SplitQName(qname)
        namespace = self.getXMLNS(prefix)
        return TypeDescriptionComponent((namespace,ncname))

    def getAttributeName(self):
        """return attribute name or None
        """
        return self.getAttribute('name')
 
    def setAttributes(self, node):
        """Sets up attribute dictionary, checks for required attributes and 
           sets default attribute values. attr is for default attribute values 
           determined at runtime.
           
           structure of attributes dictionary
               ['xmlns'][xmlns_key] --  xmlns namespace
               ['xmlns'][prefix] --  declared namespace prefix 
               [namespace][prefix] -- attributes declared in a namespace
               [attribute] -- attributes w/o prefix, default namespaces do
                   not directly apply to attributes, ie Name can't collide 
                   with QName.
        """
        self.attributes = {XMLSchemaComponent.xmlns:{}}
        for k,v in node.getAttributeDictionary().iteritems():
            prefix,value = SplitQName(k)
            if value == XMLSchemaComponent.xmlns:
                self.attributes[value][prefix or XMLSchemaComponent.xmlns_key] = v
            elif prefix:
                ns = node.getNamespace(prefix)
                if not ns: 
                    raise SchemaError('no namespace for attribute prefix %s'
                        %prefix)
                if ns not in self.attributes:
                    self.attributes[ns] = {}
                elif value in self.attributes[ns]:
                    raise SchemaError('attribute %s declared multiple times in %s'
                        %(value, ns))
                self.attributes[ns][value] = v
            elif value not in self.attributes:
                self.attributes[value] = v
            else:
                raise SchemaError('attribute %s declared multiple times' %value)

        if not isinstance(self, WSDLToolsAdapter):
            self.__checkAttributes()
        self.__setAttributeDefaults()

        #set QNames
        for k in ['type', 'element', 'base', 'ref', 'substitutionGroup', 'itemType']:
            if k in self.attributes:
                prefix, value = SplitQName(self.attributes.get(k))
                self.attributes[k] = \
                    TypeDescriptionComponent((self.getXMLNS(prefix), value))

        #Union, memberTypes is a whitespace separated list of QNames 
        for k in ['memberTypes']:
            if k in self.attributes:
                qnames = self.attributes[k]
                self.attributes[k] = []
                for qname in qnames.split():
                    prefix, value = SplitQName(qname)
                    self.attributes['memberTypes'].append(\
                        TypeDescriptionComponent(\
                            (self.getXMLNS(prefix), value)))

    def getContents(self, node):
        """retrieve xsd contents
        """
        return node.getContentList(*self.__class__.contents['xsd'])

    def __setAttributeDefaults(self):
        """Looks for default values for unset attributes.  If
           class variable representing attribute is None, then
           it must be defined as an instance variable.
        """
        for k,v in list(self.__class__.attributes.items()):
            if v is not None and k not in self.attributes:
                if isinstance(v, types.FunctionType):
                    self.attributes[k] = v(self)
                else:
                    self.attributes[k] = v

    def __checkAttributes(self):
        """Checks that required attributes have been defined,
           attributes w/default cannot be required.   Checks
           all defined attributes are legal, attribute 
           references are not subject to this test.
        """
        for a in self.__class__.required:
            if a not in self.attributes:
                raise SchemaError(
                    'class instance %s, missing required attribute %s'
                    %(self.__class__, a))
        for a,v in self.attributes.iteritems():
            # attribute #other, ie. not in empty namespace
            if isinstance(v, dict):
                continue
            
            # predefined prefixes xmlns, xml
            if a in (XMLSchemaComponent.xmlns, XMLNS.XML):
                continue
            
            if (a not in self.__class__.attributes.keys() and not 
                (self.isAttribute() and self.isReference())):
                raise SchemaError('%s, unknown attribute(%s,%s)' 
                    %(self.getItemTrace(), a, self.attributes[a]))


class WSDLToolsAdapter(XMLSchemaComponent):
    """WSDL Adapter to grab the attributes from the wsdl document node.
    """
    attributes = {'name':None, 'targetNamespace':None}
    tag = 'definitions'

    def __init__(self, wsdl):
        XMLSchemaComponent.__init__(self, parent=wsdl)
        self.setAttributes(DOMAdapter(wsdl.document))

    def getImportSchemas(self):
        """returns WSDLTools.WSDL types Collection
        """
        return self._parent().types


class Notation(XMLSchemaComponent):
    """<notation>
       parent:
           schema
       attributes:
           id -- ID
           name -- NCName, Required
           public -- token, Required
           system -- anyURI
       contents:
           annotation?
    """
    required = ['name', 'public']
    attributes = {'id':None, 'name':None, 'public':None, 'system':None}
    contents = {'xsd':('annotation')}
    tag = 'notation'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)

        for i in contents:
            component = SplitQName(i.getTagName())[1]
            if component == 'annotation' and not self.annotation:
                self.annotation = Annotation(self)
                self.annotation.fromDom(i)
            else:
                raise SchemaError('Unknown component (%s)' %(i.getTagName()))


class Annotation(XMLSchemaComponent):
    """<annotation>
       parent:
           all,any,anyAttribute,attribute,attributeGroup,choice,complexContent,
           complexType,element,extension,field,group,import,include,key,keyref,
           list,notation,redefine,restriction,schema,selector,simpleContent,
           simpleType,union,unique
       attributes:
           id -- ID
       contents:
           (documentation | appinfo)*
    """
    attributes = {'id':None}
    contents = {'xsd':('documentation', 'appinfo')}
    tag = 'annotation'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.content = None

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)
        content = []

        for i in contents:
            component = SplitQName(i.getTagName())[1]
            if component == 'documentation':
                #print_debug('class %s, documentation skipped' %self.__class__, 5)
                continue
            elif component == 'appinfo':
                #print_debug('class %s, appinfo skipped' %self.__class__, 5)
                continue
            else:
                raise SchemaError('Unknown component (%s)' %(i.getTagName()))
        self.content = tuple(content)


    class Documentation(XMLSchemaComponent):
        """<documentation>
           parent:
               annotation
           attributes:
               source, anyURI
               xml:lang, language
           contents:
               mixed, any
        """
        attributes = {'source':None, 'xml:lang':None}
        contents = {'xsd':('mixed', 'any')}
        tag = 'documentation'

        def __init__(self, parent):
            XMLSchemaComponent.__init__(self, parent)
            self.content = None

        def fromDom(self, node):
            self.setAttributes(node)
            contents = self.getContents(node)
            content = []

            for i in contents:
                component = SplitQName(i.getTagName())[1]
                if component == 'mixed':
                    #print_debug('class %s, mixed skipped' %self.__class__, 5)
                    continue
                elif component == 'any':
                    #print_debug('class %s, any skipped' %self.__class__, 5)
                    continue
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))
            self.content = tuple(content)


    class Appinfo(XMLSchemaComponent):
        """<appinfo>
           parent:
               annotation
           attributes:
               source, anyURI
           contents:
               mixed, any
        """
        attributes = {'source':None, 'anyURI':None}
        contents = {'xsd':('mixed', 'any')}
        tag = 'appinfo'

        def __init__(self, parent):
            XMLSchemaComponent.__init__(self, parent)
            self.content = None

        def fromDom(self, node):
            self.setAttributes(node)
            contents = self.getContents(node)
            content = []

            for i in contents:
                component = SplitQName(i.getTagName())[1]
                if component == 'mixed':
                    #print_debug('class %s, mixed skipped' %self.__class__, 5)
                    continue
                elif component == 'any':
                    #print_debug('class %s, any skipped' %self.__class__, 5)
                    continue
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))
            self.content = tuple(content)


class XMLSchemaFake:
    # This is temporary, for the benefit of WSDL until the real thing works.
    def __init__(self, element):
        self.targetNamespace = DOM.getAttr(element, 'targetNamespace')
        self.element = element

class XMLSchema(XMLSchemaComponent):
    """A schema is a collection of schema components derived from one
       or more schema documents, that is, one or more <schema> element
       information items. It represents the abstract notion of a schema
       rather than a single schema document (or other representation).

       <schema>
       parent:
           ROOT
       attributes:
           id -- ID
           version -- token
           xml:lang -- language
           targetNamespace -- anyURI
           attributeFormDefault -- 'qualified' | 'unqualified', 'unqualified'
           elementFormDefault -- 'qualified' | 'unqualified', 'unqualified'
           blockDefault -- '#all' | list of 
               ('substitution | 'extension' | 'restriction')
           finalDefault -- '#all' | list of 
               ('extension' | 'restriction' | 'list' | 'union')
        
       contents:
           ((include | import | redefine | annotation)*, 
            (attribute, attributeGroup, complexType, element, group, 
             notation, simpleType)*, annotation*)*


        attributes -- schema attributes
        imports -- import statements
        includes -- include statements
        redefines -- 
        types    -- global simpleType, complexType definitions
        elements -- global element declarations
        attr_decl -- global attribute declarations
        attr_groups -- attribute Groups
        model_groups -- model Groups
        notations -- global notations
    """
    attributes = {'id':None, 
        'version':None, 
        'xml:lang':None, 
        'targetNamespace':None,
        'attributeFormDefault':'unqualified',
        'elementFormDefault':'unqualified',
        'blockDefault':None,
        'finalDefault':None}
    contents = {'xsd':('include', 'import', 'redefine', 'annotation',
                       'attribute', 'attributeGroup', 'complexType',
                       'element', 'group', 'notation', 'simpleType',
                       'annotation')}
    empty_namespace = ''
    tag = 'schema'

    def __init__(self, parent=None): 
        """parent -- 
           instance variables:
           targetNamespace -- schema's declared targetNamespace, or empty string.
           _imported_schemas -- namespace keyed dict of schema dependencies, if 
              a schema is provided instance will not resolve import statement.
           _included_schemas -- schemaLocation keyed dict of component schemas, 
              if schema is provided instance will not resolve include statement.
           _base_url -- needed for relative URLs support, only works with URLs
               relative to initial document.
           includes -- collection of include statements
           imports -- collection of import statements
           elements -- collection of global element declarations
           types -- collection of global type definitions
           attr_decl -- collection of global attribute declarations
           attr_groups -- collection of global attribute group definitions
           model_groups -- collection of model group definitions
           notations -- collection of notations

        """
        self.__node = None
        self.targetNamespace = None
        XMLSchemaComponent.__init__(self, parent)
        f = lambda k: k.attributes['name']
        ns = lambda k: k.attributes['namespace']
        sl = lambda k: k.attributes['schemaLocation']
        self.includes = Collection(self, key=sl)
        self.imports = Collection(self, key=ns)
        self.elements = Collection(self, key=f)
        self.types = Collection(self, key=f)
        self.attr_decl = Collection(self, key=f)
        self.attr_groups = Collection(self, key=f)
        self.model_groups = Collection(self, key=f)
        self.notations = Collection(self, key=f)

        self._imported_schemas = {}
        self._included_schemas = {}
        self._base_url = None

    def getNode(self):
        """
        Interacting with the underlying DOM tree.
        """
        return self.__node
    
    def addImportSchema(self, schema):
        """for resolving import statements in Schema instance
           schema -- schema instance
           _imported_schemas 
        """
        if not isinstance(schema, XMLSchema):
            raise TypeError('expecting a Schema instance')
        if schema.targetNamespace != self.targetNamespace:
            self._imported_schemas[schema.targetNamespace] = schema
        else:
            raise SchemaError('import schema bad targetNamespace')

    def addIncludeSchema(self, schemaLocation, schema):
        """for resolving include statements in Schema instance
           schemaLocation -- schema location
           schema -- schema instance
           _included_schemas 
        """
        if not isinstance(schema, XMLSchema):
            raise TypeError('expecting a Schema instance')
        if not schema.targetNamespace or\
             schema.targetNamespace == self.targetNamespace:
            self._included_schemas[schemaLocation] = schema
        else:
            raise SchemaError('include schema bad targetNamespace')
        
    def setImportSchemas(self, schema_dict):
        """set the import schema dictionary, which is used to 
           reference depedent schemas.
        """
        self._imported_schemas = schema_dict

    def getImportSchemas(self):
        """get the import schema dictionary, which is used to 
           reference depedent schemas.
        """
        return self._imported_schemas

    def getSchemaNamespacesToImport(self):
        """returns tuple of namespaces the schema instance has declared
           itself to be depedent upon.
        """
        return tuple(self.includes.keys())

    def setIncludeSchemas(self, schema_dict):
        """set the include schema dictionary, which is keyed with 
           schemaLocation (uri).  
           This is a means of providing 
           schemas to the current schema for content inclusion.
        """
        self._included_schemas = schema_dict

    def getIncludeSchemas(self):
        """get the include schema dictionary, which is keyed with 
           schemaLocation (uri). 
        """
        return self._included_schemas

    def getBaseUrl(self):
        """get base url, used for normalizing all relative uri's 
        """
        return self._base_url

    def setBaseUrl(self, url):
        """set base url, used for normalizing all relative uri's 
        """
        self._base_url = url

    def getElementFormDefault(self):
        """return elementFormDefault attribute
        """
        return self.attributes.get('elementFormDefault')

    def isElementFormDefaultQualified(self):
        return self.attributes.get('elementFormDefault') == 'qualified'

    def getAttributeFormDefault(self):
        """return attributeFormDefault attribute
        """
        return self.attributes.get('attributeFormDefault')

    def getBlockDefault(self):
        """return blockDefault attribute
        """
        return self.attributes.get('blockDefault')

    def getFinalDefault(self):
        """return finalDefault attribute 
        """
        return self.attributes.get('finalDefault')

    def load(self, node, location=None):
        self.__node = node

        pnode = node.getParentNode()
        if pnode:
            pname = SplitQName(pnode.getTagName())[1]
            if pname == 'types':
                attributes = {}
                self.setAttributes(pnode)
                attributes.update(self.attributes)
                self.setAttributes(node)
                for k,v in attributes['xmlns'].iteritems():
                    if k not in self.attributes['xmlns']:
                        self.attributes['xmlns'][k] = v
            else:
                self.setAttributes(node)
        else:
            self.setAttributes(node)

        self.targetNamespace = self.getTargetNamespace()
        for childNode in self.getContents(node):
            component = SplitQName(childNode.getTagName())[1]
                
            if component == 'include':
                tp = self.__class__.Include(self)
                tp.fromDom(childNode)

                sl = tp.attributes['schemaLocation']
                schema = tp.getSchema()

                if sl not in self.getIncludeSchemas():
                    self.addIncludeSchema(sl, schema)

                self.includes[sl] = tp

                pn = childNode.getParentNode().getNode()
                pn.removeChild(childNode.getNode())
                for child in schema.getNode().getNode().childNodes:
                    pn.appendChild(child.cloneNode(1))

                for collection in ['imports','elements','types',
                                   'attr_decl','attr_groups','model_groups',
                                   'notations']:
                    for k,v in getattr(schema,collection).iteritems():
                        if k not in getattr(self,collection):
                            v._parent = weakref.ref(self)
                            getattr(self,collection)[k] = v
                        else:
                            warnings.warn("Not keeping schema component.")
      
            elif component == 'import':
                slocd = SchemaReader.namespaceToSchema
                tp = self.__class__.Import(self)
                tp.fromDom(childNode)
                import_ns = tp.getAttribute('namespace') or\
                    self.__class__.empty_namespace
                schema = slocd.get(import_ns)
                if schema is None:
                    schema = XMLSchema()
                    slocd[import_ns] = schema
                    try:
                        tp.loadSchema(schema)
                    except NoSchemaLocationWarning, ex:
                        # Dependency declaration, hopefully implementation
                        # is aware of this namespace (eg. SOAP,WSDL,?)
                        print "IMPORT: ", import_ns
                        print ex
                        del slocd[import_ns]
                        continue
                    except SchemaError, ex:
                        #warnings.warn(\
                        #    '<import namespace="%s" schemaLocation=?>, %s'\
                        #    %(import_ns, 'failed to load schema instance')
                        #)
                        print ex
                        del slocd[import_ns]
                        class _LazyEvalImport(str):
                            '''Lazy evaluation of import, replace entry in self.imports.'''
                            #attributes = dict(namespace=import_ns)
                            def getSchema(self, namespace):
                                schema = slocd.get(namespace)
                                if schema is None:
                                    parent = self._parent()
                                    wstypes = parent
                                    if isinstance(parent, WSDLToolsAdapter):
                                        wstypes = parent.getImportSchemas()
                                    schema = wstypes.get(namespace)
                                if isinstance(schema, XMLSchema):
                                    self.imports[namespace] = schema
                                    return schema

                                return None

                        self.imports[import_ns] = _LazyEvalImport(import_ns)
                        continue
                else:           
                    tp._schema = schema
            
                if import_ns in self.getImportSchemas():
                    warnings.warn(\
                        'Detected multiple imports of the namespace "%s" '\
                        %import_ns)
            
                self.addImportSchema(schema)
                # spec says can have multiple imports of same namespace
                # but purpose of import is just dependency declaration.
                self.imports[import_ns] = tp
                
            elif component == 'redefine':
                warnings.warn('redefine is ignored')
            elif component == 'annotation':
                warnings.warn('annotation is ignored')
            elif component == 'attribute':
                tp = AttributeDeclaration(self)
                tp.fromDom(childNode)
                self.attr_decl[tp.getAttribute('name')] = tp
            elif component == 'attributeGroup':
                tp = AttributeGroupDefinition(self)
                tp.fromDom(childNode)
                self.attr_groups[tp.getAttribute('name')] = tp
            elif component == 'element':
                tp = ElementDeclaration(self)
                tp.fromDom(childNode)
                self.elements[tp.getAttribute('name')] = tp
            elif component == 'group':
                tp = ModelGroupDefinition(self)
                tp.fromDom(childNode)
                self.model_groups[tp.getAttribute('name')] = tp
            elif component == 'notation':
                tp = Notation(self)
                tp.fromDom(childNode)
                self.notations[tp.getAttribute('name')] = tp
            elif component == 'complexType':
                tp = ComplexType(self)
                tp.fromDom(childNode)
                self.types[tp.getAttribute('name')] = tp
            elif component == 'simpleType':
                tp = SimpleType(self)
                tp.fromDom(childNode)
                self.types[tp.getAttribute('name')] = tp
            else:
                break

    class Import(XMLSchemaComponent):
        """<import> 
           parent:
               schema
           attributes:
               id -- ID
               namespace -- anyURI
               schemaLocation -- anyURI
           contents:
               annotation?
        """
        attributes = {'id':None,
                      'namespace':None,
                      'schemaLocation':None}
        contents = {'xsd':['annotation']}
        tag = 'import'

        def __init__(self, parent):
            XMLSchemaComponent.__init__(self, parent)
            self.annotation = None
            self._schema = None

        def fromDom(self, node):
            self.setAttributes(node)
            contents = self.getContents(node)

            if self.attributes['namespace'] == self.getTargetNamespace():
                raise SchemaError('namespace of schema and import match')

            for i in contents:
                component = SplitQName(i.getTagName())[1]
                if component == 'annotation' and not self.annotation:
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(i)
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))

        def getSchema(self):
            """if schema is not defined, first look for a Schema class instance
               in parent Schema.  Else if not defined resolve schemaLocation
               and create a new Schema class instance, and keep a hard reference. 
            """
            if not self._schema:
                ns = self.attributes['namespace']
                schema = self._parent().getImportSchemas().get(ns)
                if not schema and self._parent()._parent:
                    schema = self._parent()._parent().getImportSchemas().get(ns)

                if not schema:
                    url = self.attributes.get('schemaLocation')
                    if not url:
                        raise SchemaError('namespace(%s) is unknown' % ns)
                    base_url = self._parent().getBaseUrl()
                    reader = SchemaReader(base_url=base_url)
                    reader._imports = self._parent().getImportSchemas()
                    reader._includes = self._parent().getIncludeSchemas()
                    self._schema = reader.loadFromURL(url)
            return self._schema or schema
            
        def loadSchema(self, schema):
            """
            """
            base_url = self._parent().getBaseUrl()
            reader = SchemaReader(base_url=base_url)
            reader._imports = self._parent().getImportSchemas()
            reader._includes = self._parent().getIncludeSchemas()
            self._schema = schema

            if 'schemaLocation' not in self.attributes:
                raise NoSchemaLocationWarning('no schemaLocation attribute in import')

            reader.loadFromURL(self.attributes.get('schemaLocation'), schema)


    class Include(XMLSchemaComponent):
        """<include schemaLocation>
           parent:
               schema
           attributes:
               id -- ID
               schemaLocation -- anyURI, required
           contents:
               annotation?
        """
        required = ['schemaLocation']
        attributes = {'id':None,
            'schemaLocation':None}
        contents = {'xsd':['annotation']}
        tag = 'include'

        def __init__(self, parent):
            XMLSchemaComponent.__init__(self, parent)
            self.annotation = None
            self._schema = None

        def fromDom(self, node):
            self.setAttributes(node)
            contents = self.getContents(node)

            for i in contents:
                component = SplitQName(i.getTagName())[1]
                if component == 'annotation' and not self.annotation:
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(i)
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))

        def getSchema(self):
            """if schema is not defined, first look for a Schema class instance
               in parent Schema.  Else if not defined resolve schemaLocation
               and create a new Schema class instance.  
            """
            if not self._schema:
                schema = self._parent()
                self._schema = schema.getIncludeSchemas().get(\
                                   self.attributes['schemaLocation']
                                   )
                if not self._schema:
                    url = self.attributes['schemaLocation']
                    reader = SchemaReader(base_url=schema.getBaseUrl())
                    reader._imports = schema.getImportSchemas()
                    reader._includes = schema.getIncludeSchemas()
                    
                    # create schema before loading so chameleon include 
                    # will evalute targetNamespace correctly.
                    self._schema = XMLSchema(schema)
                    reader.loadFromURL(url, self._schema)

            return self._schema


class AttributeDeclaration(XMLSchemaComponent,\
                           AttributeMarker,\
                           DeclarationMarker):
    """<attribute name>
       parent: 
           schema
       attributes:
           id -- ID
           name -- NCName, required
           type -- QName
           default -- string
           fixed -- string
       contents:
           annotation?, simpleType?
    """
    required = ['name']
    attributes = {'id':None,
        'name':None,
        'type':None,
        'default':None,
        'fixed':None}
    contents = {'xsd':['annotation','simpleType']}
    tag = 'attribute'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None
        self.content = None

    def fromDom(self, node):
        """ No list or union support
        """
        self.setAttributes(node)
        contents = self.getContents(node)

        for i in contents:
            component = SplitQName(i.getTagName())[1]
            if component == 'annotation' and not self.annotation:
                self.annotation = Annotation(self)
                self.annotation.fromDom(i)
            elif component == 'simpleType':
                self.content = AnonymousSimpleType(self)
                self.content.fromDom(i)
            else:
                raise SchemaError('Unknown component (%s)' %(i.getTagName()))


class LocalAttributeDeclaration(AttributeDeclaration,\
                                AttributeMarker,\
                                LocalMarker,\
                                DeclarationMarker):
    """<attribute name>
       parent: 
           complexType, restriction, extension, attributeGroup
       attributes:
           id -- ID
           name -- NCName,  required
           type -- QName
           form -- ('qualified' | 'unqualified'), schema.attributeFormDefault
           use -- ('optional' | 'prohibited' | 'required'), optional
           default -- string
           fixed -- string
       contents:
           annotation?, simpleType?
    """
    required = ['name']
    attributes = {'id':None, 
        'name':None,
        'type':None,
        'form':lambda self: GetSchema(self).getAttributeFormDefault(),
        'use':'optional',
        'default':None,
        'fixed':None}
    contents = {'xsd':['annotation','simpleType']}

    def __init__(self, parent):
        AttributeDeclaration.__init__(self, parent)
        self.annotation = None
        self.content = None

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)

        for i in contents:
            component = SplitQName(i.getTagName())[1]
            if component == 'annotation' and not self.annotation:
                self.annotation = Annotation(self)
                self.annotation.fromDom(i)
            elif component == 'simpleType':
                self.content = AnonymousSimpleType(self)
                self.content.fromDom(i)
            else:
                raise SchemaError('Unknown component (%s)' %(i.getTagName()))


class AttributeWildCard(XMLSchemaComponent,\
                        AttributeMarker,\
                        DeclarationMarker,\
                        WildCardMarker):
    """<anyAttribute>
       parents: 
           complexType, restriction, extension, attributeGroup
       attributes:
           id -- ID
           namespace -- '##any' | '##other' | 
                        (anyURI* | '##targetNamespace' | '##local'), ##any
           processContents -- 'lax' | 'skip' | 'strict', strict
       contents:
           annotation?
    """
    attributes = {'id':None, 
        'namespace':'##any',
        'processContents':'strict'}
    contents = {'xsd':['annotation']}
    tag = 'anyAttribute'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)

        for i in contents:
            component = SplitQName(i.getTagName())[1]
            if component == 'annotation' and not self.annotation:
                self.annotation = Annotation(self)
                self.annotation.fromDom(i)
            else:
                raise SchemaError('Unknown component (%s)' %(i.getTagName()))


class AttributeReference(XMLSchemaComponent,\
                         AttributeMarker,\
                         ReferenceMarker):
    """<attribute ref>
       parents: 
           complexType, restriction, extension, attributeGroup
       attributes:
           id -- ID
           ref -- QName, required
           use -- ('optional' | 'prohibited' | 'required'), optional
           default -- string
           fixed -- string
       contents:
           annotation?
    """
    required = ['ref']
    attributes = {'id':None, 
        'ref':None,
        'use':'optional',
        'default':None,
        'fixed':None}
    contents = {'xsd':['annotation']}
    tag = 'attribute'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None

    def getAttributeDeclaration(self, attribute='ref'):
        return XMLSchemaComponent.getAttributeDeclaration(self, attribute)

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)

        for i in contents:
            component = SplitQName(i.getTagName())[1]
            if component == 'annotation' and not self.annotation:
                self.annotation = Annotation(self)
                self.annotation.fromDom(i)
            else:
                raise SchemaError('Unknown component (%s)' %(i.getTagName()))


class AttributeGroupDefinition(XMLSchemaComponent,\
                               AttributeGroupMarker,\
                               DefinitionMarker):
    """<attributeGroup name>
       parents: 
           schema, redefine
       attributes:
           id -- ID
           name -- NCName,  required
       contents:
           annotation?, (attribute | attributeGroup)*, anyAttribute?
    """
    required = ['name']
    attributes = {'id':None, 
        'name':None}
    contents = {'xsd':['annotation', 'attribute', 'attributeGroup', 'anyAttribute']}
    tag = 'attributeGroup'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None
        self.attr_content = None

    def getAttributeContent(self):
        return self.attr_content

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)
        content = []

        for indx in range(len(contents)):
            component = SplitQName(contents[indx].getTagName())[1]
            if (component == 'annotation') and (not indx):
                self.annotation = Annotation(self)
                self.annotation.fromDom(contents[indx])
            elif component == 'attribute':
                if contents[indx].hasattr('name'):
                    content.append(LocalAttributeDeclaration(self))
                elif contents[indx].hasattr('ref'):
                    content.append(AttributeReference(self))
                else:
                    raise SchemaError('Unknown attribute type')
                content[-1].fromDom(contents[indx])
            elif component == 'attributeGroup':
                content.append(AttributeGroupReference(self))
                content[-1].fromDom(contents[indx])
            elif component == 'anyAttribute':
                if len(contents) != indx+1: 
                    raise SchemaError('anyAttribute is out of order in %s' %self.getItemTrace())
                content.append(AttributeWildCard(self))
                content[-1].fromDom(contents[indx])
            else:
                raise SchemaError('Unknown component (%s)' %(contents[indx].getTagName()))

        self.attr_content = tuple(content)

class AttributeGroupReference(XMLSchemaComponent,\
                              AttributeGroupMarker,\
                              ReferenceMarker):
    """<attributeGroup ref>
       parents: 
           complexType, restriction, extension, attributeGroup
       attributes:
           id -- ID
           ref -- QName, required
       contents:
           annotation?
    """
    required = ['ref']
    attributes = {'id':None, 
        'ref':None}
    contents = {'xsd':['annotation']}
    tag = 'attributeGroup'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None

    def getAttributeGroup(self, attribute='ref'):
        """attribute -- attribute with a QName value (eg. type).
           collection -- check types collection in parent Schema instance
        """
        return XMLSchemaComponent.getAttributeGroup(self, attribute)

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)

        for i in contents:
            component = SplitQName(i.getTagName())[1]
            if component == 'annotation' and not self.annotation:
                self.annotation = Annotation(self)
                self.annotation.fromDom(i)
            else:
                raise SchemaError('Unknown component (%s)' %(i.getTagName()))



######################################################
# Elements
#####################################################
class IdentityConstrants(XMLSchemaComponent):
    """Allow one to uniquely identify nodes in a document and ensure the 
       integrity of references between them.

       attributes -- dictionary of attributes
       selector -- XPath to selected nodes
       fields -- list of XPath to key field
    """
    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.selector = None
        self.fields = None
        self.annotation = None

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)
        fields = []

        for i in contents:
            component = SplitQName(i.getTagName())[1]
            if component in self.__class__.contents['xsd']:
                if component == 'annotation' and not self.annotation:
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(i)
                elif component == 'selector':
                    self.selector = self.Selector(self)
                    self.selector.fromDom(i)
                    continue
                elif component == 'field':
                    fields.append(self.Field(self))
                    fields[-1].fromDom(i)
                    continue
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))
            else:
                raise SchemaError('Unknown component (%s)' %(i.getTagName()))
            self.fields = tuple(fields)


    class Constraint(XMLSchemaComponent):
        def __init__(self, parent):
            XMLSchemaComponent.__init__(self, parent)
            self.annotation = None

        def fromDom(self, node):
            self.setAttributes(node)
            contents = self.getContents(node)

            for i in contents:
                component = SplitQName(i.getTagName())[1]
                if component in self.__class__.contents['xsd']:
                    if component == 'annotation' and not self.annotation:
                        self.annotation = Annotation(self)
                        self.annotation.fromDom(i)
                    else:
                        raise SchemaError('Unknown component (%s)' %(i.getTagName()))
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))

    class Selector(Constraint):
        """<selector xpath>
           parent: 
               unique, key, keyref
           attributes:
               id -- ID
               xpath -- XPath subset,  required
           contents:
               annotation?
        """
        required = ['xpath']
        attributes = {'id':None, 
            'xpath':None}
        contents = {'xsd':['annotation']}
        tag = 'selector'

    class Field(Constraint): 
        """<field xpath>
           parent: 
               unique, key, keyref
           attributes:
               id -- ID
               xpath -- XPath subset,  required
           contents:
               annotation?
        """
        required = ['xpath']
        attributes = {'id':None, 
            'xpath':None}
        contents = {'xsd':['annotation']}
        tag = 'field'


class Unique(IdentityConstrants):
    """<unique name> Enforce fields are unique w/i a specified scope.

       parent: 
           element
       attributes:
           id -- ID
           name -- NCName,  required
       contents:
           annotation?, selector, field+
    """
    required = ['name']
    attributes = {'id':None, 
        'name':None}
    contents = {'xsd':['annotation', 'selector', 'field']}
    tag = 'unique'


class Key(IdentityConstrants):
    """<key name> Enforce fields are unique w/i a specified scope, and all
           field values are present w/i document.  Fields cannot
           be nillable.

       parent: 
           element
       attributes:
           id -- ID
           name -- NCName,  required
       contents:
           annotation?, selector, field+
    """
    required = ['name']
    attributes = {'id':None, 
        'name':None}
    contents = {'xsd':['annotation', 'selector', 'field']}
    tag = 'key'


class KeyRef(IdentityConstrants):
    """<keyref name refer> Ensure a match between two sets of values in an 
           instance.
       parent: 
           element
       attributes:
           id -- ID
           name -- NCName,  required
           refer -- QName,  required
       contents:
           annotation?, selector, field+
    """
    required = ['name', 'refer']
    attributes = {'id':None, 
        'name':None,
        'refer':None}
    contents = {'xsd':['annotation', 'selector', 'field']}
    tag = 'keyref'


class ElementDeclaration(XMLSchemaComponent,\
                         ElementMarker,\
                         DeclarationMarker):
    """<element name>
       parents:
           schema
       attributes:
           id -- ID
           name -- NCName,  required
           type -- QName
           default -- string
           fixed -- string
           nillable -- boolean,  false
           abstract -- boolean,  false
           substitutionGroup -- QName
           block -- ('#all' | ('substition' | 'extension' | 'restriction')*), 
               schema.blockDefault 
           final -- ('#all' | ('extension' | 'restriction')*), 
               schema.finalDefault 
       contents:
           annotation?, (simpleType,complexType)?, (key | keyref | unique)*
           
    """
    required = ['name']
    attributes = {'id':None, 
        'name':None,
        'type':None,
        'default':None,
        'fixed':None,
        'nillable':0,
        'abstract':0,
        'substitutionGroup':None,
        'block':lambda self: self._parent().getBlockDefault(),
        'final':lambda self: self._parent().getFinalDefault()}
    contents = {'xsd':['annotation', 'simpleType', 'complexType', 'key',\
        'keyref', 'unique']}
    tag = 'element'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None
        self.content = None
        self.constraints = ()

    def isQualified(self):
        """Global elements are always qualified.
        """
        return True
    
    def getAttribute(self, attribute):
        """return attribute.
        If attribute is type and it's None, and no simple or complex content, 
        return the default type "xsd:anyType"
        """
        value = XMLSchemaComponent.getAttribute(self, attribute)
        if attribute != 'type' or value is not None:
            return value
        
        if self.content is not None:
            return None
        
        parent = self
        while 1:
            nsdict = parent.attributes[XMLSchemaComponent.xmlns]
            for v in nsdict.itervalues():
                if v not in SCHEMA.XSD_LIST: continue
                return TypeDescriptionComponent((v, 'anyType'))
            
            if isinstance(parent, WSDLToolsAdapter)\
                or not hasattr(parent, '_parent'):
                break
            
            parent = parent._parent()
            
        raise SchemaError('failed to locate the XSD namespace')
    
    def getElementDeclaration(self, attribute):
        raise Warning('invalid operation for <%s>' %self.tag)

    def getTypeDefinition(self, attribute=None):
        """If attribute is None, "type" is assumed, return the corresponding
        representation of the global type definition (TypeDefinition),
        or the local definition if don't find "type".  To maintain backwards
        compat, if attribute is provided call base class method.
        """
        if attribute:
            return XMLSchemaComponent.getTypeDefinition(self, attribute)
        gt = XMLSchemaComponent.getTypeDefinition(self, 'type')
        if gt:
            return gt
        return self.content

    def getConstraints(self):
        return self._constraints
    def setConstraints(self, constraints):
        self._constraints = tuple(constraints)
    constraints = property(getConstraints, setConstraints, None, "tuple of key, keyref, unique constraints")

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)
        constraints = []
        for i in contents:
            component = SplitQName(i.getTagName())[1]
            if component in self.__class__.contents['xsd']:
                if component == 'annotation' and not self.annotation:
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(i)
                elif component == 'simpleType' and not self.content:
                    self.content = AnonymousSimpleType(self)
                    self.content.fromDom(i)
                elif component == 'complexType' and not self.content:
                    self.content = LocalComplexType(self)
                    self.content.fromDom(i)
                elif component == 'key':
                    constraints.append(Key(self))
                    constraints[-1].fromDom(i)
                elif component == 'keyref':
                    constraints.append(KeyRef(self))
                    constraints[-1].fromDom(i)
                elif component == 'unique':
                    constraints.append(Unique(self))
                    constraints[-1].fromDom(i)
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))
            else:
                raise SchemaError('Unknown component (%s)' %(i.getTagName()))

        self.constraints = constraints


class LocalElementDeclaration(ElementDeclaration,\
                              LocalMarker):
    """<element>
       parents:
           all, choice, sequence
       attributes:
           id -- ID
           name -- NCName,  required
           form -- ('qualified' | 'unqualified'), schema.elementFormDefault
           type -- QName
           minOccurs -- Whole Number, 1
           maxOccurs -- (Whole Number | 'unbounded'), 1
           default -- string
           fixed -- string
           nillable -- boolean,  false
           block -- ('#all' | ('extension' | 'restriction')*), schema.blockDefault 
       contents:
           annotation?, (simpleType,complexType)?, (key | keyref | unique)*
    """
    required = ['name']
    attributes = {'id':None, 
        'name':None,
        'form':lambda self: GetSchema(self).getElementFormDefault(),
        'type':None,
        'minOccurs':'1',
        'maxOccurs':'1',
        'default':None,
        'fixed':None,
        'nillable':0,
        'abstract':0,
        'block':lambda self: GetSchema(self).getBlockDefault()}
    contents = {'xsd':['annotation', 'simpleType', 'complexType', 'key',\
        'keyref', 'unique']}

    def isQualified(self):
        """
Local elements can be qualified or unqualifed according
        to the attribute form, or the elementFormDefault.  By default
        local elements are unqualified.
        """
        form = self.getAttribute('form')
        if form == 'qualified':
            return True
        if form == 'unqualified':
            return False
        raise SchemaError('Bad form (%s) for element: %s' %(form, self.getItemTrace()))


class ElementReference(XMLSchemaComponent,\
                       ElementMarker,\
                       ReferenceMarker):
    """<element ref>
       parents: 
           all, choice, sequence
       attributes:
           id -- ID
           ref -- QName, required
           minOccurs -- Whole Number, 1
           maxOccurs -- (Whole Number | 'unbounded'), 1
       contents:
           annotation?
    """
    required = ['ref']
    attributes = {'id':None, 
        'ref':None,
        'minOccurs':'1',
        'maxOccurs':'1'}
    contents = {'xsd':['annotation']}
    tag = 'element'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None

    def getElementDeclaration(self, attribute=None):
        """If attribute is None, "ref" is assumed, return the corresponding
        representation of the global element declaration (ElementDeclaration),
        To maintain backwards compat, if attribute is provided call base class method.
        """
        if attribute:
            return XMLSchemaComponent.getElementDeclaration(self, attribute)
        return XMLSchemaComponent.getElementDeclaration(self, 'ref')
 
    def fromDom(self, node):
        self.annotation = None
        self.setAttributes(node)
        for i in self.getContents(node):
            component = SplitQName(i.getTagName())[1]
            if component in self.__class__.contents['xsd']:
                if component == 'annotation' and not self.annotation:
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(i)
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))


class ElementWildCard(LocalElementDeclaration, WildCardMarker):
    """<any>
       parents: 
           choice, sequence
       attributes:
           id -- ID
           minOccurs -- Whole Number, 1
           maxOccurs -- (Whole Number | 'unbounded'), 1
           namespace -- '##any' | '##other' | 
                        (anyURI* | '##targetNamespace' | '##local'), ##any
           processContents -- 'lax' | 'skip' | 'strict', strict
       contents:
           annotation?
    """
    required = []
    attributes = {'id':None, 
        'minOccurs':'1',
        'maxOccurs':'1',
        'namespace':'##any',
        'processContents':'strict'}
    contents = {'xsd':['annotation']}
    tag = 'any'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None

    def isQualified(self):
        """
        Global elements are always qualified, but if processContents
        are not strict could have dynamically generated local elements.
        """
        return GetSchema(self).isElementFormDefaultQualified()

    def getAttribute(self, attribute):
        """return attribute.
        """
        return XMLSchemaComponent.getAttribute(self, attribute)

    def getTypeDefinition(self, attribute):
        raise Warning('invalid operation for <%s>' % self.tag)

    def fromDom(self, node):
        self.annotation = None
        self.setAttributes(node)
        for i in self.getContents(node):
            component = SplitQName(i.getTagName())[1]
            if component in self.__class__.contents['xsd']:
                if component == 'annotation' and not self.annotation:
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(i)
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))


######################################################
# Model Groups
#####################################################
class Sequence(XMLSchemaComponent,\
               SequenceMarker):
    """<sequence>
       parents: 
           complexType, extension, restriction, group, choice, sequence
       attributes:
           id -- ID
           minOccurs -- Whole Number, 1
           maxOccurs -- (Whole Number | 'unbounded'), 1

       contents:
           annotation?, (element | group | choice | sequence | any)*
    """
    attributes = {'id':None, 
        'minOccurs':'1',
        'maxOccurs':'1'}
    contents = {'xsd':['annotation', 'element', 'group', 'choice', 'sequence',\
         'any']}
    tag = 'sequence'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None
        self.content = None

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)
        content = []

        for i in contents:
            component = SplitQName(i.getTagName())[1]
            if component in self.__class__.contents['xsd']:
                if component == 'annotation' and not self.annotation:
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(i)
                    continue
                elif component == 'element':
                    if i.hasattr('ref'):
                        content.append(ElementReference(self))
                    else:
                        content.append(LocalElementDeclaration(self))
                elif component == 'group':
                    content.append(ModelGroupReference(self))
                elif component == 'choice':
                    content.append(Choice(self))
                elif component == 'sequence':
                    content.append(Sequence(self))
                elif component == 'any':
                    content.append(ElementWildCard(self))
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))
                content[-1].fromDom(i)
            else:
                raise SchemaError('Unknown component (%s)' %(i.getTagName()))
        self.content = tuple(content)


class All(XMLSchemaComponent,\
          AllMarker):
    """<all>
       parents: 
           complexType, extension, restriction, group
       attributes:
           id -- ID
           minOccurs -- '0' | '1', 1
           maxOccurs -- '1', 1

       contents:
           annotation?, element*
    """
    attributes = {'id':None, 
        'minOccurs':'1',
        'maxOccurs':'1'}
    contents = {'xsd':['annotation', 'element']}
    tag = 'all'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None
        self.content = None

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)
        content = []

        for i in contents:
            component = SplitQName(i.getTagName())[1]
            if component in self.__class__.contents['xsd']:
                if component == 'annotation' and not self.annotation:
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(i)
                    continue
                elif component == 'element':
                    if i.hasattr('ref'):
                        content.append(ElementReference(self))
                    else:
                        content.append(LocalElementDeclaration(self))
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))
                content[-1].fromDom(i)
            else:
                raise SchemaError('Unknown component (%s)' %(i.getTagName()))
        self.content = tuple(content)


class Choice(XMLSchemaComponent,\
             ChoiceMarker):
    """<choice>
       parents: 
           complexType, extension, restriction, group, choice, sequence
       attributes:
           id -- ID
           minOccurs -- Whole Number, 1
           maxOccurs -- (Whole Number | 'unbounded'), 1

       contents:
           annotation?, (element | group | choice | sequence | any)*
    """
    attributes = {'id':None, 
        'minOccurs':'1',
        'maxOccurs':'1'}
    contents = {'xsd':['annotation', 'element', 'group', 'choice', 'sequence',\
         'any']}
    tag = 'choice'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None
        self.content = None

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)
        content = []

        for i in contents:
            component = SplitQName(i.getTagName())[1]
            if component in self.__class__.contents['xsd']:
                if component == 'annotation' and not self.annotation:
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(i)
                    continue
                elif component == 'element':
                    if i.hasattr('ref'):
                        content.append(ElementReference(self))
                    else:
                        content.append(LocalElementDeclaration(self))
                elif component == 'group':
                    content.append(ModelGroupReference(self))
                elif component == 'choice':
                    content.append(Choice(self))
                elif component == 'sequence':
                    content.append(Sequence(self))
                elif component == 'any':
                    content.append(ElementWildCard(self))
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))
                content[-1].fromDom(i)
            else:
                raise SchemaError('Unknown component (%s)' %(i.getTagName()))
        self.content = tuple(content)


class ModelGroupDefinition(XMLSchemaComponent,\
                           ModelGroupMarker,\
                           DefinitionMarker):
    """<group name>
       parents:
           redefine, schema
       attributes:
           id -- ID
           name -- NCName,  required

       contents:
           annotation?, (all | choice | sequence)?
    """
    required = ['name']
    attributes = {'id':None, 
        'name':None}
    contents = {'xsd':['annotation', 'all', 'choice', 'sequence']}
    tag = 'group'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None
        self.content = None

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)

        for i in contents:
            component = SplitQName(i.getTagName())[1]
            if component in self.__class__.contents['xsd']:
                if component == 'annotation' and not self.annotation:
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(i)
                    continue
                elif component == 'all' and not self.content:
                    self.content = All(self)
                elif component == 'choice' and not self.content:
                    self.content = Choice(self)
                elif component == 'sequence' and not self.content:
                    self.content = Sequence(self)
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))
                self.content.fromDom(i)
            else:
                raise SchemaError('Unknown component (%s)' %(i.getTagName()))


class ModelGroupReference(XMLSchemaComponent,\
                          ModelGroupMarker,\
                          ReferenceMarker):
    """<group ref>
       parents:
           choice, complexType, extension, restriction, sequence
       attributes:
           id -- ID
           ref -- NCName,  required
           minOccurs -- Whole Number, 1
           maxOccurs -- (Whole Number | 'unbounded'), 1

       contents:
           annotation?
    """
    required = ['ref']
    attributes = {'id':None, 
        'ref':None,
        'minOccurs':'1',
        'maxOccurs':'1'}
    contents = {'xsd':['annotation']}
    tag = 'group'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None

    def getModelGroupReference(self):
        return self.getModelGroup('ref')

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)

        for i in contents:
            component = SplitQName(i.getTagName())[1]
            if component in self.__class__.contents['xsd']:
                if component == 'annotation' and not self.annotation:
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(i)
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))
            else:
                raise SchemaError('Unknown component (%s)' %(i.getTagName()))



class ComplexType(XMLSchemaComponent,\
                  DefinitionMarker,\
                  ComplexMarker):
    """<complexType name>
       parents:
           redefine, schema
       attributes:
           id -- ID
           name -- NCName,  required
           mixed -- boolean, false
           abstract -- boolean,  false
           block -- ('#all' | ('extension' | 'restriction')*), schema.blockDefault 
           final -- ('#all' | ('extension' | 'restriction')*), schema.finalDefault 

       contents:
           annotation?, (simpleContent | complexContent | 
           ((group | all | choice | sequence)?, (attribute | attributeGroup)*, anyAttribute?))
    """
    required = ['name']
    attributes = {'id':None, 
        'name':None,
        'mixed':0,
        'abstract':0,
        'block':lambda self: self._parent().getBlockDefault(),
        'final':lambda self: self._parent().getFinalDefault()}
    contents = {'xsd':['annotation', 'simpleContent', 'complexContent',\
        'group', 'all', 'choice', 'sequence', 'attribute', 'attributeGroup',\
        'anyAttribute', 'any']}
    tag = 'complexType'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None
        self.content = None
        self.attr_content = None

    def isMixed(self):
        m = self.getAttribute('mixed')
        if m == 0 or m == False:
            return False
        if isinstance(m, basestring):
            if m in ('false', '0'):
                return False
            if m in ('true', '1'):
                return True

        raise SchemaError('invalid value for attribute mixed(%s): %s'
            %(m, self.getItemTrace()))

    def getAttributeContent(self):
        return self.attr_content

    def getElementDeclaration(self, attribute):
        raise Warning('invalid operation for <%s>' %self.tag)

    def getTypeDefinition(self, attribute):
        raise Warning('invalid operation for <%s>' %self.tag)

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)
      
        indx = 0
        num = len(contents)
        if not num:
            return

        component = SplitQName(contents[indx].getTagName())[1]
        if component == 'annotation':
            self.annotation = Annotation(self)
            self.annotation.fromDom(contents[indx])
            indx += 1
            if indx < num:
                component = SplitQName(contents[indx].getTagName())[1]

        self.content = None
        if component == 'simpleContent':
            self.content = self.__class__.SimpleContent(self)
            self.content.fromDom(contents[indx])
        elif component == 'complexContent':
            self.content = self.__class__.ComplexContent(self)
            self.content.fromDom(contents[indx])
        else:
            if component == 'all':
                self.content = All(self)
            elif component == 'choice':
                self.content = Choice(self)
            elif component == 'sequence':
                self.content = Sequence(self)
            elif component == 'group':
                self.content = ModelGroupReference(self)

            if self.content:
                self.content.fromDom(contents[indx])
                indx += 1

            self.attr_content = []
            while indx < num:
                component = SplitQName(contents[indx].getTagName())[1]
                if component == 'attribute':
                    if contents[indx].hasattr('ref'):
                        self.attr_content.append(AttributeReference(self))
                    else:
                        self.attr_content.append(LocalAttributeDeclaration(self))
                elif component == 'attributeGroup':
                    self.attr_content.append(AttributeGroupReference(self))
                elif component == 'anyAttribute':
                    self.attr_content.append(AttributeWildCard(self))
                else:
                    raise SchemaError('Unknown component (%s): %s' 
                        %(contents[indx].getTagName(),self.getItemTrace()))
                self.attr_content[-1].fromDom(contents[indx])
                indx += 1

    class _DerivedType(XMLSchemaComponent):
        def __init__(self, parent):
            XMLSchemaComponent.__init__(self, parent)
            self.annotation = None
            # XXX remove attribute derivation, inconsistent
            self.derivation = None
            self.content = None

        def fromDom(self, node):
            self.setAttributes(node)
            contents = self.getContents(node)

            for i in contents:
                component = SplitQName(i.getTagName())[1]
                if component in self.__class__.contents['xsd']:
                    if component == 'annotation' and not self.annotation:
                        self.annotation = Annotation(self)
                        self.annotation.fromDom(i)
                        continue
                    elif component == 'restriction' and not self.derivation:
                        self.derivation = self.__class__.Restriction(self)
                    elif component == 'extension' and not self.derivation:
                        self.derivation = self.__class__.Extension(self)
                    else:
                        raise SchemaError('Unknown component (%s)' %(i.getTagName()))
                else:
                    raise SchemaError('Unknown component (%s)' %(i.getTagName()))
                self.derivation.fromDom(i)
            self.content = self.derivation

    class ComplexContent(_DerivedType,\
                         ComplexMarker):
        """<complexContent>
           parents:
               complexType
           attributes:
               id -- ID
               mixed -- boolean, false

           contents:
               annotation?, (restriction | extension)
        """
        attributes = {'id':None, 
            'mixed':0}
        contents = {'xsd':['annotation', 'restriction', 'extension']}
        tag = 'complexContent'

        def isMixed(self):
            m = self.getAttribute('mixed')
            if m == 0 or m == False:
                return False
            if isinstance(m, basestring):
                if m in ('false', '0'):
                    return False
                if m in ('true', '1'):
                    return True
            raise SchemaError('invalid value for attribute mixed(%s): %s'
                %(m, self.getItemTrace()))

        class _DerivationBase(XMLSchemaComponent):
            """<extension>,<restriction>
               parents:
                   complexContent
               attributes:
                   id -- ID
                   base -- QName, required

               contents:
                   annotation?, (group | all | choice | sequence)?, 
                       (attribute | attributeGroup)*, anyAttribute?
            """
            required = ['base']
            attributes = {'id':None, 
                'base':None }
            contents = {'xsd':['annotation', 'group', 'all', 'choice',\
                'sequence', 'attribute', 'attributeGroup', 'anyAttribute']}

            def __init__(self, parent):
                XMLSchemaComponent.__init__(self, parent)
                self.annotation = None
                self.content = None
                self.attr_content = None

            def getAttributeContent(self):
                return self.attr_content

            def fromDom(self, node):
                self.setAttributes(node)
                contents = self.getContents(node)

                indx = 0
                num = len(contents)
                #XXX ugly
                if not num:
                    return
                component = SplitQName(contents[indx].getTagName())[1]
                if component == 'annotation':
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(contents[indx])
                    indx += 1
                    component = SplitQName(contents[indx].getTagName())[1]

                if component == 'all':
                    self.content = All(self)
                    self.content.fromDom(contents[indx])
                    indx += 1
                elif component == 'choice':
                    self.content = Choice(self)
                    self.content.fromDom(contents[indx])
                    indx += 1
                elif component == 'sequence':
                    self.content = Sequence(self)
                    self.content.fromDom(contents[indx])
                    indx += 1
                elif component == 'group':
                    self.content = ModelGroupReference(self)
                    self.content.fromDom(contents[indx])
                    indx += 1
                else:
                    self.content = None

                self.attr_content = []
                while indx < num:
                    component = SplitQName(contents[indx].getTagName())[1]
                    if component == 'attribute':
                        if contents[indx].hasattr('ref'):
                            self.attr_content.append(AttributeReference(self))
                        else:
                            self.attr_content.append(LocalAttributeDeclaration(self))
                    elif component == 'attributeGroup':
                        if contents[indx].hasattr('ref'):
                            self.attr_content.append(AttributeGroupReference(self))
                        else:
                            self.attr_content.append(AttributeGroupDefinition(self))
                    elif component == 'anyAttribute':
                        self.attr_content.append(AttributeWildCard(self))
                    else:
                        raise SchemaError('Unknown component (%s)' %(contents[indx].getTagName()))
                    self.attr_content[-1].fromDom(contents[indx])
                    indx += 1

        class Extension(_DerivationBase, 
                        ExtensionMarker):
            """<extension base>
               parents:
                   complexContent
               attributes:
                   id -- ID
                   base -- QName, required

               contents:
                   annotation?, (group | all | choice | sequence)?, 
                       (attribute | attributeGroup)*, anyAttribute?
            """
            tag = 'extension'

        class Restriction(_DerivationBase,\
                          RestrictionMarker):
            """<restriction base>
               parents:
                   complexContent
               attributes:
                   id -- ID
                   base -- QName, required

               contents:
                   annotation?, (group | all | choice | sequence)?, 
                       (attribute | attributeGroup)*, anyAttribute?
            """
            tag = 'restriction'


    class SimpleContent(_DerivedType,\
                        SimpleMarker):
        """<simpleContent>
           parents:
               complexType
           attributes:
               id -- ID

           contents:
               annotation?, (restriction | extension)
        """
        attributes = {'id':None}
        contents = {'xsd':['annotation', 'restriction', 'extension']}
        tag = 'simpleContent'

        class Extension(XMLSchemaComponent,\
                        ExtensionMarker):
            """<extension base>
               parents:
                   simpleContent
               attributes:
                   id -- ID
                   base -- QName, required

               contents:
                   annotation?, (attribute | attributeGroup)*, anyAttribute?
            """
            required = ['base']
            attributes = {'id':None, 
                'base':None }
            contents = {'xsd':['annotation', 'attribute', 'attributeGroup', 
                'anyAttribute']}
            tag = 'extension'

            def __init__(self, parent):
                XMLSchemaComponent.__init__(self, parent)
                self.annotation = None
                self.attr_content = None
 
            def getAttributeContent(self):
                return self.attr_content

            def fromDom(self, node):
                self.setAttributes(node)
                contents = self.getContents(node)

                indx = 0
                num = len(contents)

                if num:
                    component = SplitQName(contents[indx].getTagName())[1]
                    if component == 'annotation':
                        self.annotation = Annotation(self)
                        self.annotation.fromDom(contents[indx])
                        indx += 1
                        component = SplitQName(contents[indx].getTagName())[1]
    
                content = []
                while indx < num:
                    component = SplitQName(contents[indx].getTagName())[1]
                    if component == 'attribute':
                        if contents[indx].hasattr('ref'):
                            content.append(AttributeReference(self))
                        else:
                            content.append(LocalAttributeDeclaration(self))
                    elif component == 'attributeGroup':
                        content.append(AttributeGroupReference(self))
                    elif component == 'anyAttribute':
                        content.append(AttributeWildCard(self))
                    else:
                        raise SchemaError('Unknown component (%s)'
                            %(contents[indx].getTagName()))
                    content[-1].fromDom(contents[indx])
                    indx += 1
                self.attr_content = tuple(content)


        class Restriction(XMLSchemaComponent,\
                          RestrictionMarker):
            """<restriction base>
               parents:
                   simpleContent
               attributes:
                   id -- ID
                   base -- QName, required

               contents:
                   annotation?, simpleType?, (enumeration | length | 
                   maxExclusive | maxInclusive | maxLength | minExclusive | 
                   minInclusive | minLength | pattern | fractionDigits | 
                   totalDigits | whiteSpace)*, (attribute | attributeGroup)*, 
                   anyAttribute?
            """
            required = ['base']
            attributes = {'id':None, 
                'base':None }
            contents = {'xsd':['annotation', 'simpleType', 'attribute',\
                'attributeGroup', 'anyAttribute'] + RestrictionMarker.facets}
            tag = 'restriction'

            def __init__(self, parent):
                XMLSchemaComponent.__init__(self, parent)
                self.annotation = None
                self.content = None
                self.attr_content = None
 
            def getAttributeContent(self):
                return self.attr_content

            def fromDom(self, node):
                self.content = []
                self.setAttributes(node)
                contents = self.getContents(node)

                indx = 0
                num = len(contents)
                component = SplitQName(contents[indx].getTagName())[1]
                if component == 'annotation':
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(contents[indx])
                    indx += 1
                    component = SplitQName(contents[indx].getTagName())[1]

                content = []
                while indx < num:
                    component = SplitQName(contents[indx].getTagName())[1]
                    if component == 'attribute':
                        if contents[indx].hasattr('ref'):
                            content.append(AttributeReference(self))
                        else:
                            content.append(LocalAttributeDeclaration(self))
                    elif component == 'attributeGroup':
                        content.append(AttributeGroupReference(self))
                    elif component == 'anyAttribute':
                        content.append(AttributeWildCard(self))
                    elif component == 'simpleType':
                        self.content.append(AnonymousSimpleType(self))
                        self.content[-1].fromDom(contents[indx])
                    else:
                        raise SchemaError('Unknown component (%s)'
                            %(contents[indx].getTagName()))
                    content[-1].fromDom(contents[indx])
                    indx += 1
                self.attr_content = tuple(content)


class LocalComplexType(ComplexType,\
                       LocalMarker):
    """<complexType>
       parents:
           element
       attributes:
           id -- ID
           mixed -- boolean, false

       contents:
           annotation?, (simpleContent | complexContent | 
           ((group | all | choice | sequence)?, (attribute | attributeGroup)*, anyAttribute?))
    """
    required = []
    attributes = {'id':None, 
        'mixed':0}
    tag = 'complexType'
    

class SimpleType(XMLSchemaComponent,\
                 DefinitionMarker,\
                 SimpleMarker):
    """<simpleType name>
       parents:
           redefine, schema
       attributes:
           id -- ID
           name -- NCName, required
           final -- ('#all' | ('extension' | 'restriction' | 'list' | 'union')*), 
               schema.finalDefault 

       contents:
           annotation?, (restriction | list | union)
    """
    required = ['name']
    attributes = {'id':None,
        'name':None,
        'final':lambda self: self._parent().getFinalDefault()}
    contents = {'xsd':['annotation', 'restriction', 'list', 'union']}
    tag = 'simpleType'

    def __init__(self, parent):
        XMLSchemaComponent.__init__(self, parent)
        self.annotation = None
        self.content = None

    def getElementDeclaration(self, attribute):
        raise Warning('invalid operation for <%s>' %self.tag)

    def getTypeDefinition(self, attribute):
        raise Warning('invalid operation for <%s>' %self.tag)

    def fromDom(self, node):
        self.setAttributes(node)
        contents = self.getContents(node)
        for child in contents:
            component = SplitQName(child.getTagName())[1]
            if component == 'annotation':
                self.annotation = Annotation(self)
                self.annotation.fromDom(child)
                continue
            break
        else:
            return
        if component == 'restriction':
            self.content = self.__class__.Restriction(self)
        elif component == 'list':
            self.content = self.__class__.List(self)
        elif component == 'union':
            self.content = self.__class__.Union(self)
        else:
            raise SchemaError('Unknown component (%s)' %(component))
        self.content.fromDom(child)

    class Restriction(XMLSchemaComponent,\
                      RestrictionMarker):
        """<restriction base>
           parents:
               simpleType
           attributes:
               id -- ID
               base -- QName, required or simpleType child

           contents:
               annotation?, simpleType?, (enumeration | length | 
               maxExclusive | maxInclusive | maxLength | minExclusive | 
               minInclusive | minLength | pattern | fractionDigits | 
               totalDigits | whiteSpace)*
        """
        attributes = {'id':None, 
            'base':None }
        contents = {'xsd':['annotation', 'simpleType']+RestrictionMarker.facets}
        tag = 'restriction'

        def __init__(self, parent):
            XMLSchemaComponent.__init__(self, parent)
            self.annotation = None
            self.content = None
            self.facets = None

        def getAttributeBase(self):
            return XMLSchemaComponent.getAttribute(self, 'base')

        def getTypeDefinition(self, attribute='base'):
            return XMLSchemaComponent.getTypeDefinition(self, attribute)

        def getSimpleTypeContent(self):
            for el in self.content:
                if el.isSimple(): return el
            return None

        def fromDom(self, node):
            self.facets = []
            self.setAttributes(node)
            contents = self.getContents(node)
            content = []

            for indx in range(len(contents)):
                component = SplitQName(contents[indx].getTagName())[1]
                if (component == 'annotation') and (not indx):
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(contents[indx])
                    continue
                elif (component == 'simpleType') and (not indx or indx == 1):
                    content.append(AnonymousSimpleType(self))
                    content[-1].fromDom(contents[indx])
                elif component in RestrictionMarker.facets:
                    self.facets.append(contents[indx])
                else:
                    raise SchemaError('Unknown component (%s)' %(component))
            self.content = tuple(content)


    class Union(XMLSchemaComponent,
                UnionMarker):
        """<union>
           parents:
               simpleType
           attributes:
               id -- ID
               memberTypes -- list of QNames, required or simpleType child.

           contents:
               annotation?, simpleType*
        """
        attributes = {'id':None, 
            'memberTypes':None }
        contents = {'xsd':['annotation', 'simpleType']}
        tag = 'union'

        def __init__(self, parent):
            XMLSchemaComponent.__init__(self, parent)
            self.annotation = None
            self.content = None

        def fromDom(self, node):
            self.setAttributes(node)
            contents = self.getContents(node)
            content = []

            for indx in range(len(contents)):
                component = SplitQName(contents[indx].getTagName())[1]
                if (component == 'annotation') and (not indx):
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(contents[indx])
                elif (component == 'simpleType'):
                    content.append(AnonymousSimpleType(self))
                    content[-1].fromDom(contents[indx])
                else:
                    raise SchemaError('Unknown component (%s)' %(component))
            self.content = tuple(content)

    class List(XMLSchemaComponent, 
               ListMarker):
        """<list>
           parents:
               simpleType
           attributes:
               id -- ID
               itemType -- QName, required or simpleType child.

           contents:
               annotation?, simpleType?
        """
        attributes = {'id':None, 
            'itemType':None }
        contents = {'xsd':['annotation', 'simpleType']}
        tag = 'list'

        def __init__(self, parent):
            XMLSchemaComponent.__init__(self, parent)
            self.annotation = None
            self.content = None

        def getItemType(self):
            return self.attributes.get('itemType')

        def getTypeDefinition(self, attribute='itemType'):
            """
            return the type refered to by itemType attribute or
            the simpleType content.  If returns None, then the 
            type refered to by itemType is primitive.
            """
            tp = XMLSchemaComponent.getTypeDefinition(self, attribute)
            return tp or self.content

        def fromDom(self, node):
            self.annotation = None
            self.content = None
            self.setAttributes(node)
            contents = self.getContents(node)
            for indx in range(len(contents)):
                component = SplitQName(contents[indx].getTagName())[1]
                if (component == 'annotation') and (not indx):
                    self.annotation = Annotation(self)
                    self.annotation.fromDom(contents[indx])
                elif (component == 'simpleType'):
                    self.content = AnonymousSimpleType(self)
                    self.content.fromDom(contents[indx])
                    break
                else:
                    raise SchemaError('Unknown component (%s)' %(component))

                 
class AnonymousSimpleType(SimpleType,\
                          SimpleMarker,\
                          LocalMarker):
    """<simpleType>
       parents:
           attribute, element, list, restriction, union
       attributes:
           id -- ID

       contents:
           annotation?, (restriction | list | union)
    """
    required = []
    attributes = {'id':None}
    tag = 'simpleType'


class Redefine:
    """<redefine>
       parents:
       attributes:

       contents:
    """
    tag = 'redefine'


###########################
###########################


if sys.version_info[:2] >= (2, 2):
    tupleClass = tuple
else:
    from pysphere.ZSI.wstools import UserTuple
    tupleClass = UserTuple.UserTuple

class TypeDescriptionComponent(tupleClass):
    """Tuple of length 2, consisting of
       a namespace and unprefixed name.
    """
    def __init__(self, args):
        """args -- (namespace, name)
           Remove the name's prefix, irrelevant.
        """
        if len(args) != 2:
            raise TypeError('expecting tuple (namespace, name), got %s' %args)
        elif args[1].find(':') >= 0:
            args = (args[0], SplitQName(args[1])[1])
        tuple.__init__(self, args)
        return

    def getTargetNamespace(self):
        return self[0]

    def getName(self):
        return self[1]


