xmlclass/xmlclass.py
author laurent
Sun, 08 Jan 2012 19:16:58 +0100
changeset 616 8a60ffcfd70b
parent 607 9c133d675b69
child 626 ac0a8f6462c3
permissions -rwxr-xr-x
Adding support for drag'n dropping variable from global defined in configurations and resources to POU variable panel or body editor for declaring external variables
Adding support for drag'n dropping located variables from topology panel to configurations and resources variable panel for declaring global located variables
#!/usr/bin/env python
# -*- coding: utf-8 -*-

#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
#based on the plcopen standard. 
#
#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
#
#See COPYING file for copyrights details.
#
#This library is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public
#License as published by the Free Software Foundation; either
#version 2.1 of the License, or (at your option) any later version.
#
#This library is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#General Public License for more details.
#
#You should have received a copy of the GNU General Public
#License along with this library; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import os, sys
import re
import datetime
from types import *
from xml.dom import minidom
from xml.sax.saxutils import escape, unescape, quoteattr
from new import classobj

def CreateNode(name):
    node = minidom.Node()
    node.nodeName = name
    node._attrs = {}
    node.childNodes = []
    return node

def NodeRenameAttr(node, old_name, new_name):
    node._attrs[new_name] = node._attrs.pop(old_name)

def NodeSetAttr(node, name, value):
    attr = minidom.Attr(name)
    text = minidom.Text()
    text.data = value
    attr.childNodes[0] = text
    node._attrs[name] = attr

"""
Regular expression models for checking all kind of string values defined in XML
standard
"""
Name_model = re.compile('([a-zA-Z_\:][\w\.\-\:]*)$')
Names_model = re.compile('([a-zA-Z_\:][\w\.\-\:]*(?: [a-zA-Z_\:][\w\.\-\:]*)*)$')
NMToken_model = re.compile('([\w\.\-\:]*)$')
NMTokens_model = re.compile('([\w\.\-\:]*(?: [\w\.\-\:]*)*)$')
QName_model = re.compile('((?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*)$')
QNames_model = re.compile('((?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*(?: (?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*)*)$')
NCName_model = re.compile('([a-zA-Z_][\w]*)$')
URI_model = re.compile('((?:http://|/)?(?:[\w.-]*/?)*)$')
LANGUAGE_model = re.compile('([a-zA-Z]{1,8}(?:-[a-zA-Z0-9]{1,8})*)$')

ONLY_ANNOTATION = re.compile("((?:annotation )?)")

"""
Regular expression models for extracting dates and times from a string
"""
time_model = re.compile('([0-9]{2}):([0-9]{2}):([0-9]{2}(?:\.[0-9]*)?)(?:Z)?$')
date_model = re.compile('([0-9]{4})-([0-9]{2})-([0-9]{2})((?:[\-\+][0-9]{2}:[0-9]{2})|Z)?$')
datetime_model = re.compile('([0-9]{4})-([0-9]{2})-([0-9]{2})[ T]([0-9]{2}):([0-9]{2}):([0-9]{2}(?:\.[0-9]*)?)((?:[\-\+][0-9]{2}:[0-9]{2})|Z)?$')

class xml_timezone(datetime.tzinfo):

    def SetOffset(self, offset):
        if offset == "Z":
            self.__offset = timedelta(minutes = 0)
            self.__name = "UTC"
        else:
            sign = {"-" : -1, "+" : 1}[offset[0]]
            hours, minutes = [int(val) for val in offset[1:].split(":")]
            self.__offset = timedelta(minutes=sign * (hours * 60 + minutes))
            self.__name = ""

    def utcoffset(self, dt):
        return self.__offset

    def tzname(self, dt):
        return self.__name

    def dst(self, dt):
        return ZERO

[SYNTAXELEMENT, SYNTAXATTRIBUTE, SIMPLETYPE, COMPLEXTYPE, COMPILEDCOMPLEXTYPE, 
 ATTRIBUTESGROUP, ELEMENTSGROUP, ATTRIBUTE, ELEMENT, CHOICE, ANY, TAG, CONSTRAINT,
] = range(13)

def NotSupportedYet(type):
    """
    Function that generates a function that point out to user that datatype
    used is not supported by xmlclass yet
    @param type: data type
    @return: function generated
    """
    def GetUnknownValue(attr):
        raise ValueError("\"%s\" type isn't supported by \"xmlclass\" yet!" % \
                         type)
    return GetUnknownValue

"""
This function calculates the number of whitespace for indentation
"""
def getIndent(indent, balise):
    first = indent * 2
    second = first + len(balise) + 1
    return u'\t'.expandtabs(first), u'\t'.expandtabs(second)


def GetAttributeValue(attr, extract=True):
    """
    Function that extracts data from a tree node
    @param attr: tree node containing data to extract
    @param extract: attr is a tree node or not
    @return: data extracted as string
    """
    if not extract:
        return attr
    if len(attr.childNodes) == 1:
        return unescape(attr.childNodes[0].data.encode("utf-8"))
    else:
        # content is a CDATA
        text = ""
        for node in attr.childNodes:
            if not (node.nodeName == "#text" and node.data.strip() == ""):
                text += unescape(node.data.encode("utf-8"))
        return text


def GetNormalizedString(attr, extract=True):
    """
    Function that normalizes a string according to XML 1.0. Replace  
    tabulations, line feed and carriage return by white space
    @param attr: tree node containing data to extract or data to normalize
    @param extract: attr is a tree node or not
    @return: data normalized as string
    """
    if extract:
        value = GetAttributeValue(attr)
    else:
        value = attr
    return value.replace("\t", " ").replace("\r", " ").replace("\n", " ")


def GetToken(attr, extract=True):
    """
    Function that tokenizes a string according to XML 1.0. Remove any leading  
    and trailing white space and replace internal sequence of two or more 
    spaces by only one white space
    @param attr: tree node containing data to extract or data to tokenize
    @param extract: attr is a tree node or not
    @return: data tokenized as string
    """
    return " ".join([part for part in 
                     GetNormalizedString(attr, extract).split(" ")
                     if part])


def GetHexInteger(attr, extract=True):
    """
    Function that extracts an hexadecimal integer from a tree node or a string
    @param attr: tree node containing data to extract or data as a string
    @param extract: attr is a tree node or not
    @return: data as an integer
    """
    if extract:
        value = GetAttributeValue(attr)
    else:
        value = attr
    if len(value) % 2 != 0:
        raise ValueError("\"%s\" isn't a valid hexadecimal integer!" % value)
    try:
        return int(value, 16)
    except:
        raise ValueError("\"%s\" isn't a valid hexadecimal integer!" % value)


def GenerateIntegerExtraction(minInclusive=None, maxInclusive=None, 
                              minExclusive=None, maxExclusive=None):
    """
    Function that generates an extraction function for integer defining min and
    max of integer value
    @param minInclusive: inclusive minimum
    @param maxInclusive: inclusive maximum
    @param minExclusive: exclusive minimum
    @param maxExclusive: exclusive maximum
    @return: function generated
    """
    def GetInteger(attr, extract=True):
        """
        Function that extracts an integer from a tree node or a string
        @param attr: tree node containing data to extract or data as a string
        @param extract: attr is a tree node or not
        @return: data as an integer
        """

        if extract:
            value = GetAttributeValue(attr)
        else:
            value = attr
        try:
            # TODO: permit to write value like 1E2
            value = int(value)
        except:
            raise ValueError("\"%s\" isn't a valid integer!" % value)
        if minInclusive is not None and value < minInclusive:
            raise ValueError("\"%d\" isn't greater or equal to %d!" % \
                             (value, minInclusive))
        if maxInclusive is not None and value > maxInclusive:
            raise ValueError("\"%d\" isn't lesser or equal to %d!" % \
                             (value, maxInclusive))
        if minExclusive is not None and value <= minExclusive:
            raise ValueError("\"%d\" isn't greater than %d!" % \
                             (value, minExclusive))
        if maxExclusive is not None and value >= maxExclusive:
            raise ValueError("\"%d\" isn't lesser than %d!" % \
                             (value, maxExclusive))
        return value
    return GetInteger


def GenerateFloatExtraction(type, extra_values=[]):
    """
    Function that generates an extraction function for float
    @param type: name of the type of float
    @return: function generated
    """
    def GetFloat(attr, extract = True):
        """
        Function that extracts a float from a tree node or a string
        @param attr: tree node containing data to extract or data as a string
        @param extract: attr is a tree node or not
        @return: data as a float
        """
        if extract:
            value = GetAttributeValue(attr)
        else:
            value = attr
        if value in extra_values:
            return value
        try:
            return float(value)
        except:
            raise ValueError("\"%s\" isn't a valid %s!" % (value, type))
    return GetFloat


def GetBoolean(attr, extract=True):
    """
    Function that extracts a boolean from a tree node or a string
    @param attr: tree node containing data to extract or data as a string
    @param extract: attr is a tree node or not
    @return: data as a boolean
    """
    if extract:
        value = GetAttributeValue(attr)
    else:
        value = attr
    if value == "true" or value == "1":
        return True
    elif value == "false" or value == "0":
        return False
    else:
        raise ValueError("\"%s\" isn't a valid boolean!" % value)


def GetTime(attr, extract=True):
    """
    Function that extracts a time from a tree node or a string
    @param attr: tree node containing data to extract or data as a string
    @param extract: attr is a tree node or not
    @return: data as a time
    """
    if extract:
        value = GetAttributeValue(attr)
    else:
        value = attr
    result = time_model.match(value)
    if result:
        values = result.groups()
        time_values = [int(v) for v in values[:2]]
        seconds = float(values[2])
        time_values.extend([int(seconds), int((seconds % 1) * 1000000)])
        return datetime.time(*time_values)
    else:
        raise ValueError("\"%s\" isn't a valid time!" % value)


def GetDate(attr, extract=True):
    """
    Function that extracts a date from a tree node or a string
    @param attr: tree node containing data to extract or data as a string
    @param extract: attr is a tree node or not
    @return: data as a date
    """
    if extract:
        value = GetAttributeValue(attr)
    else:
        value = attr
    result = date_model.match(value)
    if result:
        values = result.groups()
        date_values = [int(v) for v in values[:3]]
        if values[3] is not None:
            tz = xml_timezone()
            tz.SetOffset(values[3])
            date_values.append(tz)
        return datetime.date(*date_values)
    else:
        raise ValueError("\"%s\" isn't a valid date!" % value)


def GetDateTime(attr, extract=True):
    """
    Function that extracts date and time from a tree node or a string
    @param attr: tree node containing data to extract or data as a string
    @param extract: attr is a tree node or not
    @return: data as date and time
    """
    if extract:
        value = GetAttributeValue(attr)
    else:
        value = attr
    result = datetime_model.match(value)
    if result:
        values = result.groups()
        datetime_values = [int(v) for v in values[:5]]
        seconds = float(values[5])
        datetime_values.extend([int(seconds), int((seconds % 1) * 1000000)])
        if values[6] is not None:
            tz = xml_timezone()
            tz.SetOffset(values[6])
            datetime_values.append(tz)
        return datetime.datetime(*datetime_values)
    else:
        raise ValueError("\"%s\" isn't a valid datetime!" % value)


def GenerateModelNameExtraction(type, model):
    """
    Function that generates an extraction function for string matching a model
    @param type: name of the data type
    @param model: model that data must match
    @return: function generated
    """
    def GetModelName(attr, extract=True):
        """
        Function that extracts a string from a tree node or not and check that
        string extracted or given match the model
        @param attr: tree node containing data to extract or data as a string
        @param extract: attr is a tree node or not
        @return: data as a string if matching
        """
        if extract:
            value = GetAttributeValue(attr)
        else:
            value = attr
        result = model.match(value)
        if not result:
            raise ValueError("\"%s\" isn't a valid %s!" % (value, type))
        return value
    return GetModelName


def GenerateLimitExtraction(min=None, max=None, unbounded=True):
    """
    Function that generates an extraction function for integer defining min and
    max of integer value
    @param min: minimum limit value
    @param max: maximum limit value
    @param unbounded: value can be "unbounded" or not
    @return: function generated
    """
    def GetLimit(attr, extract=True):
        """
        Function that extracts a string from a tree node or not and check that
        string extracted or given is in a list of values
        @param attr: tree node containing data to extract or data as a string
        @param extract: attr is a tree node or not
        @return: data as a string
        """
        if extract:
            value = GetAttributeValue(attr)
        else:
            value = attr
        if value == "unbounded":
            if unbounded:
                return value
            else:
                raise ValueError("Member limit can't be defined to \"unbounded\"!")
        try:
            limit = int(value)
        except:
            raise ValueError("\"%s\" isn't a valid value for this member limit!" % value)
        if limit < 0:
            raise ValueError("Member limit can't be negative!")
        elif min is not None and limit < min:
            raise ValueError("Member limit can't be lower than \"%d\"!" % min)
        elif max is not None and limit > max:
            raise ValueError("Member limit can't be upper than \"%d\"!" % max)
        return limit
    return GetLimit


def GenerateEnumeratedExtraction(type, list):
    """
    Function that generates an extraction function for enumerated values
    @param type: name of the data type
    @param list: list of possible values
    @return: function generated
    """
    def GetEnumerated(attr, extract=True):
        """
        Function that extracts a string from a tree node or not and check that
        string extracted or given is in a list of values
        @param attr: tree node containing data to extract or data as a string
        @param extract: attr is a tree node or not
        @return: data as a string
        """
        if extract:
            value = GetAttributeValue(attr)
        else:
            value = attr
        if value in list:
            return value
        else:
            raise ValueError("\"%s\" isn't a valid value for %s!" % \
                             (value, type))
    return GetEnumerated


def GetNamespaces(attr, extract=True):
    """
    Function that extracts a list of namespaces from a tree node or a string
    @param attr: tree node containing data to extract or data as a string
    @param extract: attr is a tree node or not
    @return: list of namespaces
    """
    if extract:
        value = GetAttributeValue(attr)
    else:
        value = attr
    if value == "":
        return []
    elif value == "##any" or value == "##other":
        namespaces = [value]
    else:
        namespaces = []
        for item in value.split(" "):
            if item == "##targetNamespace" or item == "##local":
                namespaces.append(item)
            else:
                result = URI_model.match(item)
                if result is not None:
                    namespaces.append(item)
                else:
                    raise ValueError("\"%s\" isn't a valid value for namespace!" % value)
    return namespaces


def GenerateGetList(type, list):
    """
    Function that generates an extraction function for a list of values
    @param type: name of the data type
    @param list: list of possible values
    @return: function generated
    """
    def GetLists(attr, extract=True):
        """
        Function that extracts a list of values from a tree node or a string
        @param attr: tree node containing data to extract or data as a string
        @param extract: attr is a tree node or not
        @return: list of values
        """
        if extract:
            value = GetAttributeValue(attr)
        else:
            value = attr
        if value == "":
            return []
        elif value == "#all":
            return [value]
        else:
            values = []
            for item in value.split(" "):
                if item in list:
                    values.append(item)
                else:
                    raise ValueError("\"%s\" isn't a valid value for %s!" % \
                                     (value, type))
            return values
    return GetLists


def GenerateModelNameListExtraction(type, model):
    """
    Function that generates an extraction function for list of string matching
    a model
    @param type: name of the data type
    @param model: model that list elements must match
    @return: function generated
    """
    def GetModelNameList(attr, extract=True):
        """
        Function that extracts a list of string from a tree node or not and
        check that all extracted items match the model
        @param attr: tree node containing data to extract or data as a string
        @param extract: attr is a tree node or not
        @return: data as a list of string if matching 
        """
        if extract:
            value = GetAttributeValue(attr)
        else:
            value = attr
        values = []
        for item in value.split(" "):
            result = model.match(item)
            if result is not None:
                values.append(item)
            else:
                raise ValueError("\"%s\" isn't a valid value for %s!" % \
                                 (value, type))
        return values
    return GetModelNameList

def GenerateAnyInfos(infos):
    def ExtractAny(tree):
        if tree.nodeName in ["#text", "#cdata-section"]:
            return unescape(tree.data.encode("utf-8"))
        else:
            return tree
    
    def GenerateAny(value, name=None, indent=0):
        if isinstance(value, (StringType, UnicodeType)):
            try:
                value = value.decode("utf-8")
            except:
                pass
            return u'<![CDATA[%s]]>\n' % value
        else:
            return value.toprettyxml(indent=" "*indent, encoding="utf-8")
        
    return {
        "type": COMPLEXTYPE, 
        "extract": ExtractAny,
        "generate": GenerateAny,
        "initial": lambda: "",
        "check": lambda x: isinstance(x, (StringType, UnicodeType, minidom.Node))
    }

def GenerateTagInfos(infos):
    def ExtractTag(tree):
        if len(tree._attrs) > 0:
            raise ValueError("\"%s\" musn't have attributes!" % infos["name"])
        if len(tree.childNodes) > 0:
            raise ValueError("\"%s\" musn't have children!" % infos["name"])
        if infos["minOccurs"] == 0:
            return True
        else:
            return None
    
    def GenerateTag(value, name=None, indent=0):
        if name is not None and not (infos["minOccurs"] == 0 and value is None):
            ind1, ind2 = getIndent(indent, name)
            return ind1 + "<%s/>\n" % name
        else:
            return ""
    
    return {
        "type": TAG, 
        "extract": ExtractTag,
        "generate": GenerateTag,
        "initial": lambda: None,
        "check": lambda x: x == None or infos["minOccurs"] == 0 and value == True
    }

def FindTypeInfos(factory, infos):
    if isinstance(infos, (UnicodeType, StringType)):
        namespace, name = DecomposeQualifiedName(infos)
        return factory.GetQualifiedNameInfos(name, namespace)
    return infos
    
def GetElementInitialValue(factory, infos):
    infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
    if infos["minOccurs"] == 0 and infos["maxOccurs"] == 1:
        if infos.has_key("default"):
            return infos["elmt_type"]["extract"](infos["default"], False)
        else:
            return None
    elif infos["minOccurs"] == 1 and infos["maxOccurs"] == 1:
        return infos["elmt_type"]["initial"]()
    else:
        return [infos["elmt_type"]["initial"]() for i in xrange(infos["minOccurs"])]

def HandleError(message, raise_exception):
    if raise_exception:
        raise ValueError(message)
    return False

def CheckElementValue(factory, name, infos, value, raise_exception=True):
    infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
    if value is None and raise_exception:
        if not (infos["minOccurs"] == 0 and infos["maxOccurs"] == 1):
            return HandleError("Attribute '%s' isn't optional." % name, raise_exception)
    elif infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1:
        if not isinstance(value, ListType):
            return HandleError("Attribute '%s' must be a list." % name, raise_exception)
        if len(value) < infos["minOccurs"] or infos["maxOccurs"] != "unbounded" and len(value) > infos["maxOccurs"]:
            return HandleError("List out of bounds for attribute '%s'." % name, raise_exception)
        if not reduce(lambda x, y: x and y, map(infos["elmt_type"]["check"], value), True):
            return HandleError("Attribute '%s' must be a list of valid elements." % name, raise_exception)
    elif infos.has_key("fixed") and value != infos["fixed"]:
        return HandleError("Value of attribute '%s' can only be '%s'." % (name, str(infos["fixed"])), raise_exception)
    else:
        return infos["elmt_type"]["check"](value)
    return True

def GetContentInfos(name, choices):
    for choice_infos in choices:
        if choices_infos["type"] == "sequence":
            for element_infos in choices_infos["elements"]:
                if element_infos["type"] == CHOICE:
                    if GetContentInfos(name, element_infos["choices"]):
                        return choices_infos
                elif element_infos["name"] == name:
                    return choices_infos
        elif choice_infos["name"] == name:
            return choices_infos
    return None

def ComputeContentChoices(factory, name, infos):
    choices = []
    for choice in infos["choices"]:
        if choice["type"] == "sequence":
            choice["name"] = "sequence"
            for sequence_element in choice["elements"]:
                if sequence_element["type"] != CHOICE:
                    element_infos = factory.ExtractTypeInfos(sequence_element["name"], name, sequence_element["elmt_type"])
                    if element_infos is not None:
                        sequence_element["elmt_type"] = element_infos
        elif choice["elmt_type"] == "tag":
            choice["elmt_type"] = GenerateTagInfos(choice)
        else:
            choice_infos = factory.ExtractTypeInfos(choice["name"], name, choice["elmt_type"])
            if choice_infos is not None:
                choice["elmt_type"] = choice_infos
        choices.append((choice["name"], choice))
    return choices

def ExtractContentElement(factory, tree, infos, content):
    infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
    if infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1:
        if isinstance(content, ListType) and len(content) > 0 and \
           content[-1]["name"] == tree.nodeName:
            content_item = content.pop(-1)
            content_item["value"].append(infos["elmt_type"]["extract"](tree))
            return content_item
        elif not isinstance(content, ListType) and \
             content is not None and \
             content["name"] == tree.nodeName:
            return {"name": tree.nodeName, 
                    "value": content["value"] + [infos["elmt_type"]["extract"](tree)]}
        else:
            return {"name": tree.nodeName, 
                    "value": [infos["elmt_type"]["extract"](tree)]}
    else:
        return {"name": tree.nodeName, 
                "value": infos["elmt_type"]["extract"](tree)}

def GenerateContentInfos(factory, name, choices):
    choices_dict = {}
    for choice_name, infos in choices:
        if choice_name == "sequence":
            for element in infos["elements"]:
                if element["type"] == CHOICE:
                    element["elmt_type"] = GenerateContentInfos(factory, name, ComputeContentChoices(factory, name, element))
                elif choices_dict.has_key(element["name"]):
                    raise ValueError("'%s' element defined two times in choice" % choice_name)
                else:
                    choices_dict[element["name"]] = infos
        else:
            if choices_dict.has_key(choice_name):
                raise ValueError("'%s' element defined two times in choice" % choice_name)
            choices_dict[choice_name] = infos
    
    def GetContentInitial():
        content_name, infos = choices[0]
        if content_name == "sequence":
            content_value = []
            for i in xrange(infos["minOccurs"]):
                for element_infos in infos["elements"]:
                    value = GetElementInitialValue(factory, element_infos)
                    if value is not None:
                        if element_infos["type"] == CHOICE:
                            content_value.append(value)
                        else:
                            content_value.append({"name": element_infos["name"], "value": value})
        else:
            content_value = GetElementInitialValue(factory, infos)
        return {"name": content_name, "value": content_value}
        
    def CheckContent(value):
        if value["name"] != "sequence":
            infos = choices_dict.get(value["name"], None)
            if infos is not None:
                return CheckElementValue(factory, value["name"], infos, value["value"], False)
        elif len(value["value"]) > 0:
            infos = choices_dict.get(value["value"][0]["name"], None)
            if infos is None:
                for choice_name, infos in choices:
                    if infos["type"] == "sequence":
                        for element_infos in infos["elements"]:
                            if element_infos["type"] == CHOICE:
                                infos = GetContentInfos(value["value"][0]["name"], element_infos["choices"])
            if infos is not None:
                sequence_number = 0
                element_idx = 0
                while element_idx < len(value["value"]):
                    for element_infos in infos["elements"]:
                        if element_infos["name"] == value["value"][element_idx]["name"]:
                            element_value = value["value"][element_idx]["value"]
                            element_idx += 1
                        else:
                            element_value = None
                        if not CheckElementValue(factory, element_infos["name"], element_infos, element_value, False):
                            raise ValueError("Invalid sequence value in attribute 'content'")
                    sequence_number += 1
                if sequence_number < infos["minOccurs"] or infos["maxOccurs"] != "unbounded" and sequence_number > infos["maxOccurs"]:
                    raise ValueError("Invalid sequence value in attribute 'content'")
                return True
        else:
            for element_name, infos in choices:
                if element_name == "sequence":
                    required = 0
                    for element in infos["elements"]:
                        if element["minOccurs"] > 0:
                            required += 1
                    if required == 0:
                        return True
        return False
    
    def ExtractContent(tree, content):
        infos = choices_dict.get(tree.nodeName, None)
        if infos is not None:
            if infos["name"] == "sequence":
                sequence_dict = dict([(element_infos["name"], element_infos) for element_infos in infos["elements"] if element_infos["type"] != CHOICE])
                element_infos = sequence_dict.get(tree.nodeName)
                if content is not None and \
                   content["name"] == "sequence" and \
                   len(content["value"]) > 0 and \
                   choices_dict.get(content["value"][-1]["name"]) == infos:
                    return {"name": "sequence",
                            "value": content["value"] + [ExtractContentElement(factory, tree, element_infos, content["value"][-1])]}
                else:
                    return {"name": "sequence",
                            "value": [ExtractContentElement(factory, tree, element_infos, None)]}
            else:
                return ExtractContentElement(factory, tree, infos, content)
        else:
            for choice_name, infos in choices:
                if infos["type"] == "sequence":
                    for element_infos in infos["elements"]:
                        if element_infos["type"] == CHOICE:
                            try:
                                if content is not None and \
                                    content["name"] == "sequence" and \
                                    len(content["value"]) > 0:
                                    return {"name": "sequence",
                                            "value": content["value"] + [element_infos["elmt_type"]["extract"](tree, content["value"][-1])]}
                                else:
                                    return {"name": "sequence",
                                            "value": [element_infos["elmt_type"]["extract"](tree, None)]}
                            except:
                                pass
        raise ValueError("Invalid element \"%s\" for content!" % tree.nodeName)
    
    def GenerateContent(value, name=None, indent=0):
        text = ""
        if value["name"] != "sequence":
            infos = choices_dict.get(value["name"], None)
            if infos is not None:
                infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
                if infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1:
                    for item in value["value"]:
                        text += infos["elmt_type"]["generate"](item, value["name"], indent)
                else:
                    text += infos["elmt_type"]["generate"](value["value"], value["name"], indent)
        elif len(value["value"]) > 0:
            infos = choices_dict.get(value["value"][0]["name"], None)
            if infos is None:
                for choice_name, infos in choices:
                    if infos["type"] == "sequence":
                        for element_infos in infos["elements"]:
                            if element_infos["type"] == CHOICE:
                                infos = GetContentInfos(value["value"][0]["name"], element_infos["choices"])
            if infos is not None:
                sequence_dict = dict([(element_infos["name"], element_infos) for element_infos in infos["elements"]]) 
                for element_value in value["value"]:
                    element_infos = sequence_dict.get(element_value["name"])
                    if element_infos["maxOccurs"] == "unbounded" or element_infos["maxOccurs"] > 1:
                        for item in element_value["value"]:
                            text += element_infos["elmt_type"]["generate"](item, element_value["name"], indent)
                    else:
                        text += element_infos["elmt_type"]["generate"](element_value["value"], element_infos["name"], indent)
        return text
        
    return {
        "type": COMPLEXTYPE,
        "initial": GetContentInitial,
        "check": CheckContent,
        "extract": ExtractContent,
        "generate": GenerateContent
    }

#-------------------------------------------------------------------------------
#                           Structure extraction functions
#-------------------------------------------------------------------------------


def DecomposeQualifiedName(name):
    result = QName_model.match(name)
    if not result:
        raise ValueError("\"%s\" isn't a valid QName value!" % name) 
    parts = result.groups()[0].split(':')
    if len(parts) == 1:
        return None, parts[0]
    return parts
    
def GenerateElement(element_name, attributes, elements_model, 
                    accept_text=False):
    def ExtractElement(factory, node):
        attrs = factory.ExtractNodeAttrs(element_name, node, attributes)
        children_structure = ""
        children_infos = []
        children = []
        for child in node.childNodes:
            if child.nodeName not in ["#comment", "#text"]:
                namespace, childname = DecomposeQualifiedName(child.nodeName)
                children_structure += "%s "%childname
        result = elements_model.match(children_structure)
        if not result:
            raise ValueError("Invalid structure for \"%s\" children!. First element invalid." % node.nodeName)
        valid = result.groups()[0]
        if len(valid) < len(children_structure):
            raise ValueError("Invalid structure for \"%s\" children!. Element number %d invalid." % (node.nodeName, len(valid.split(" ")) - 1))
        for child in node.childNodes:
            if child.nodeName != "#comment" and \
               (accept_text or child.nodeName != "#text"):
                if child.nodeName == "#text":
                    children.append(GetAttributeValue(node))
                else:
                    namespace, childname = DecomposeQualifiedName(child.nodeName)
                    infos = factory.GetQualifiedNameInfos(childname, namespace)
                    if infos["type"] != SYNTAXELEMENT:
                        raise ValueError("\"%s\" can't be a member child!" % name)
                    if infos["extract"].has_key(element_name):
                        children.append(infos["extract"][element_name](factory, child))
                    else:
                        children.append(infos["extract"]["default"](factory, child))
        return node.nodeName, attrs, children
    return ExtractElement


"""
Class that generate class from an XML Tree
"""
class ClassFactory:

    def __init__(self, document, filepath=None, debug=False):
        self.Document = document
        if filepath is not None:
            self.BaseFolder, self.FileName = os.path.split(filepath)
        else:
            self.BaseFolder = self.FileName = None
        self.Debug = debug
        
        # Dictionary for stocking Classes and Types definitions created from
        # the XML tree
        self.XMLClassDefinitions = {}
        
        self.DefinedNamespaces = {}
        self.Namespaces = {}
        self.SchemaNamespace = None
        self.TargetNamespace = None
        
        self.CurrentCompilations = []
        
        # Dictionaries for stocking Classes and Types generated
        self.ComputeAfter = []
        if self.FileName is not None:
            self.ComputedClasses = {self.FileName: {}}
        else:
            self.ComputedClasses = {}
        self.ComputedClassesInfos = {}
        self.AlreadyComputed = {}

    def GetQualifiedNameInfos(self, name, namespace=None, canbenone=False):
        if namespace is None:
            if self.Namespaces[self.SchemaNamespace].has_key(name):
                return self.Namespaces[self.SchemaNamespace][name]
            for space, elements in self.Namespaces.iteritems():
                if space != self.SchemaNamespace and elements.has_key(name):
                    return elements[name]
            parts = name.split("_", 1)
            if len(parts) > 1:
                group = self.GetQualifiedNameInfos(parts[0], namespace)
                if group is not None and group["type"] == ELEMENTSGROUP:
                    elements = []
                    if group.has_key("elements"):
                        elements = group["elements"]
                    elif group.has_key("choices"):
                        elements = group["choices"]
                    for element in elements:
                        if element["name"] == parts[1]:
                            return element
            if not canbenone:
                raise ValueError("Unknown element \"%s\" for any defined namespaces!" % name)
        elif self.Namespaces.has_key(namespace):
            if self.Namespaces[namespace].has_key(name):
                return self.Namespaces[namespace][name]
            parts = name.split("_", 1)
            if len(parts) > 1:
                group = self.GetQualifiedNameInfos(parts[0], namespace)
                if group is not None and group["type"] == ELEMENTSGROUP:
                    elements = []
                    if group.has_key("elements"):
                        elements = group["elements"]
                    elif group.has_key("choices"):
                        elements = group["choices"]
                    for element in elements:
                        if element["name"] == parts[1]:
                            return element
            if not canbenone:
                raise ValueError("Unknown element \"%s\" for namespace \"%s\"!" % (name, namespace))
        elif not canbenone:
            raise ValueError("Unknown namespace \"%s\"!" % namespace)
        return None

    def SplitQualifiedName(self, name, namespace=None, canbenone=False):
        if namespace is None:
            if self.Namespaces[self.SchemaNamespace].has_key(name):
                return name, None
            for space, elements in self.Namespaces.items():
                if space != self.SchemaNamespace and elements.has_key(name):
                    return name, None
            parts = name.split("_", 1)
            if len(parts) > 1:
                group = self.GetQualifiedNameInfos(parts[0], namespace)
                if group is not None and group["type"] == ELEMENTSGROUP:
                    elements = []
                    if group.has_key("elements"):
                        elements = group["elements"]
                    elif group.has_key("choices"):
                        elements = group["choices"]
                    for element in elements:
                        if element["name"] == parts[1]:
                            return part[1], part[0]
            if not canbenone:
                raise ValueError("Unknown element \"%s\" for any defined namespaces!" % name)
        elif self.Namespaces.has_key(namespace):
            if self.Namespaces[namespace].has_key(name):
                return name, None
            parts = name.split("_", 1)
            if len(parts) > 1:
                group = self.GetQualifiedNameInfos(parts[0], namespace)
                if group is not None and group["type"] == ELEMENTSGROUP:
                    elements = []
                    if group.has_key("elements"):
                        elements = group["elements"]
                    elif group.has_key("choices"):
                        elements = group["choices"]
                    for element in elements:
                        if element["name"] == parts[1]:
                            return parts[1], parts[0]
            if not canbenone:
                raise ValueError("Unknown element \"%s\" for namespace \"%s\"!" % (name, namespace))
        elif not canbenone:
            raise ValueError("Unknown namespace \"%s\"!" % namespace)
        return None, None

    def ExtractNodeAttrs(self, element_name, node, valid_attrs):
        attrs = {}
        for qualified_name, attr in node._attrs.items():
            namespace, name =  DecomposeQualifiedName(qualified_name)
            if name in valid_attrs:
                infos = self.GetQualifiedNameInfos(name, namespace)
                if infos["type"] != SYNTAXATTRIBUTE:
                    raise ValueError("\"%s\" can't be a member attribute!" % name)
                elif name in attrs:
                    raise ValueError("\"%s\" attribute has been twice!" % name)
                elif element_name in infos["extract"]:
                    attrs[name] = infos["extract"][element_name](attr)
                else:
                    attrs[name] = infos["extract"]["default"](attr)
            elif namespace == "xmlns":
                infos = self.GetQualifiedNameInfos("anyURI", self.SchemaNamespace)
                self.DefinedNamespaces[infos["extract"](attr)] = name
            else:
                raise ValueError("Invalid attribute \"%s\" for member \"%s\"!" % (qualified_name, node.nodeName))
        for attr in valid_attrs:
            if attr not in attrs and \
               self.Namespaces[self.SchemaNamespace].has_key(attr) and \
               self.Namespaces[self.SchemaNamespace][attr].has_key("default"):
                if self.Namespaces[self.SchemaNamespace][attr]["default"].has_key(element_name):
                    default = self.Namespaces[self.SchemaNamespace][attr]["default"][element_name]
                else:
                    default = self.Namespaces[self.SchemaNamespace][attr]["default"]["default"]
                if default is not None:
                    attrs[attr] = default
        return attrs

    def ReduceElements(self, elements, schema=False):
        result = []
        for child_infos in elements:
            if child_infos is not None:
                if child_infos[1].has_key("name") and schema:
                    self.CurrentCompilations.append(child_infos[1]["name"])
                namespace, name = DecomposeQualifiedName(child_infos[0])
                infos = self.GetQualifiedNameInfos(name, namespace)
                if infos["type"] != SYNTAXELEMENT:
                    raise ValueError("\"%s\" can't be a member child!" % name)
                element = infos["reduce"](self, child_infos[1], child_infos[2])
                if element is not None:
                    result.append(element)
                if child_infos[1].has_key("name") and schema:
                    self.CurrentCompilations.pop(-1)
        annotations = []
        children = []
        for element in result:
            if element["type"] == "annotation":
                annotations.append(element)
            else:
                children.append(element)
        return annotations, children

    def AddComplexType(self, typename, infos):
        if not self.XMLClassDefinitions.has_key(typename):
            self.XMLClassDefinitions[typename] = infos
        else:
            raise ValueError("\"%s\" class already defined. Choose another name!" % typename)

    def ParseSchema(self):
        pass

    def ExtractTypeInfos(self, name, parent, typeinfos):
        if isinstance(typeinfos, (StringType, UnicodeType)):
            namespace, name = DecomposeQualifiedName(typeinfos)
            infos = self.GetQualifiedNameInfos(name, namespace)
            if infos["type"] == COMPLEXTYPE:
                name, parent = self.SplitQualifiedName(name, namespace)
                result = self.CreateClass(name, parent, infos)
                if result is not None and not isinstance(result, (UnicodeType, StringType)):
                    self.Namespaces[self.TargetNamespace][result["name"]] = result
                return result
            elif infos["type"] == ELEMENT and infos["elmt_type"]["type"] == COMPLEXTYPE:
                name, parent = self.SplitQualifiedName(name, namespace)
                result = self.CreateClass(name, parent, infos["elmt_type"])
                if result is not None and not isinstance(result, (UnicodeType, StringType)):
                    self.Namespaces[self.TargetNamespace][result["name"]] = result
                return result
            else:
                return infos
        elif typeinfos["type"] == COMPLEXTYPE:
            return self.CreateClass(name, parent, typeinfos)
        elif typeinfos["type"] == SIMPLETYPE:
            return typeinfos
            
    """
    Methods that generates the classes
    """
    def CreateClasses(self):
        self.ParseSchema()
        for name, infos in self.Namespaces[self.TargetNamespace].items():
            if infos["type"] == ELEMENT:
                if not isinstance(infos["elmt_type"], (UnicodeType, StringType)) and \
                   infos["elmt_type"]["type"] == COMPLEXTYPE:
                    self.ComputeAfter.append((name, None, infos["elmt_type"], True))
                    while len(self.ComputeAfter) > 0:
                        result = self.CreateClass(*self.ComputeAfter.pop(0))
                        if result is not None and not isinstance(result, (UnicodeType, StringType)):
                            self.Namespaces[self.TargetNamespace][result["name"]] = result
            elif infos["type"] == COMPLEXTYPE:
                self.ComputeAfter.append((name, None, infos))
                while len(self.ComputeAfter) > 0:
                    result = self.CreateClass(*self.ComputeAfter.pop(0))
                    if result is not None and \
                       not isinstance(result, (UnicodeType, StringType)):
                        self.Namespaces[self.TargetNamespace][result["name"]] = result
            elif infos["type"] == ELEMENTSGROUP:
                elements = []
                if infos.has_key("elements"):
                    elements = infos["elements"]
                elif infos.has_key("choices"):
                    elements = infos["choices"]
                for element in elements:
                    if not isinstance(element["elmt_type"], (UnicodeType, StringType)) and \
                       element["elmt_type"]["type"] == COMPLEXTYPE:
                        self.ComputeAfter.append((element["name"], infos["name"], element["elmt_type"]))
                        while len(self.ComputeAfter) > 0:
                            result = self.CreateClass(*self.ComputeAfter.pop(0))
                            if result is not None and \
                               not isinstance(result, (UnicodeType, StringType)):
                                self.Namespaces[self.TargetNamespace][result["name"]] = result
        return self.ComputedClasses

    def CreateClass(self, name, parent, classinfos, baseclass = False):
        if parent is not None:
            classname = "%s_%s" % (parent, name)
        else:
            classname = name
        
        # Checks that classe haven't been generated yet
        if self.AlreadyComputed.get(classname, False):
            if baseclass:
                self.AlreadyComputed[classname].IsBaseClass = baseclass
            return self.ComputedClassesInfos.get(classname, None)
        
        # If base classes haven't been generated
        bases = []
        base_infos = classinfos.get("base", None)
        if base_infos is not None:
            result = self.ExtractTypeInfos("base", name, base_infos)
            if result is None:
                namespace, base_name = DecomposeQualifiedName(base_infos)                
                if self.AlreadyComputed.get(base_name, False):
                    self.ComputeAfter.append((name, parent, classinfos))
                    if self.TargetNamespace is not None:
                        return "%s:%s" % (self.TargetNamespace, classname)
                    else:
                        return classname
            elif result is not None:
                if self.FileName is not None:
                    classinfos["base"] = self.ComputedClasses[self.FileName].get(result["name"], None)
                    if classinfos["base"] is None:
                        for filename, classes in self.ComputedClasses.iteritems():
                            if filename != self.FileName:
                                classinfos["base"] = classes.get(result["name"], None)
                                if classinfos["base"] is not None:
                                    break
                else:
                    classinfos["base"] = self.ComputedClasses.get(result["name"], None)
                if classinfos["base"] is None:
                    raise ValueError("No class found for base type")
                bases.append(classinfos["base"])
        bases.append(object)
        bases = tuple(bases)
        classmembers = {"__doc__": classinfos.get("doc", ""), "IsBaseClass": baseclass}
        
        self.AlreadyComputed[classname] = True
        
        for attribute in classinfos["attributes"]:
            infos = self.ExtractTypeInfos(attribute["name"], name, attribute["attr_type"])
            if infos is not None:                    
                if infos["type"] != SIMPLETYPE:
                    raise ValueError("\"%s\" type is not a simple type!" % attribute["attr_type"])
                attrname = attribute["name"]
                if attribute["use"] == "optional":
                    classmembers[attrname] = None
                    classmembers["add%s"%attrname] = generateAddMethod(attrname, self, attribute)
                    classmembers["delete%s"%attrname] = generateDeleteMethod(attrname)
                else:
                    classmembers[attrname] = infos["initial"]()
                classmembers["set%s"%attrname] = generateSetMethod(attrname)
                classmembers["get%s"%attrname] = generateGetMethod(attrname)
            else:
                raise ValueError("\"%s\" type unrecognized!" % attribute["attr_type"])
            attribute["attr_type"] = infos
        
        for element in classinfos["elements"]:
            if element["type"] == CHOICE:
                elmtname = element["name"]
                choices = ComputeContentChoices(self, name, element)
                classmembers["get%schoices"%elmtname] = generateGetChoicesMethod(element["choices"])
                if element["maxOccurs"] == "unbounded" or element["maxOccurs"] > 1:
                    classmembers["append%sbytype" % elmtname] = generateAppendChoiceByTypeMethod(element["maxOccurs"], self, element["choices"])
                    classmembers["insert%sbytype" % elmtname] = generateInsertChoiceByTypeMethod(element["maxOccurs"], self, element["choices"])
                else:
                    classmembers["set%sbytype" % elmtname] = generateSetChoiceByTypeMethod(self, element["choices"])
                infos = GenerateContentInfos(self, name, choices)
            elif element["type"] == ANY:
                elmtname = element["name"] = "text"
                element["minOccurs"] = element["maxOccurs"] = 1
                infos = GenerateAnyInfos(element)
            else:
                elmtname = element["name"]
                if element["elmt_type"] == "tag":
                    infos = GenerateTagInfos(element)
                else:
                    infos = self.ExtractTypeInfos(element["name"], name, element["elmt_type"])
            if infos is not None:
                element["elmt_type"] = infos
            if element["maxOccurs"] == "unbounded" or element["maxOccurs"] > 1:
                classmembers[elmtname] = []
                classmembers["append%s" % elmtname] = generateAppendMethod(elmtname, element["maxOccurs"], self, element)
                classmembers["insert%s" % elmtname] = generateInsertMethod(elmtname, element["maxOccurs"], self, element)
                classmembers["remove%s" % elmtname] = generateRemoveMethod(elmtname, element["minOccurs"])
                classmembers["count%s" % elmtname] = generateCountMethod(elmtname)
            else:
                if element["minOccurs"] == 0:
                    classmembers[elmtname] = None
                    classmembers["add%s" % elmtname] = generateAddMethod(elmtname, self, element)
                    classmembers["delete%s" % elmtname] = generateDeleteMethod(elmtname)
                elif not isinstance(element["elmt_type"], (UnicodeType, StringType)):
                    classmembers[elmtname] = element["elmt_type"]["initial"]()
                else:
                    classmembers[elmtname] = None
            classmembers["set%s" % elmtname] = generateSetMethod(elmtname)
            classmembers["get%s" % elmtname] = generateGetMethod(elmtname)
            
        classmembers["__init__"] = generateInitMethod(self, classinfos)
        classmembers["__setattr__"] = generateSetattrMethod(self, classinfos)
        classmembers["getStructure"] = generateStructureMethod(classinfos)
        classmembers["loadXMLTree"] = generateLoadXMLTree(self, classinfos)
        classmembers["generateXMLText"] = generateGenerateXMLText(self, classinfos)
        classmembers["getElementAttributes"] = generateGetElementAttributes(self, classinfos)
        classmembers["getElementInfos"] = generateGetElementInfos(self, classinfos)
        classmembers["setElementValue"] = generateSetElementValue(self, classinfos)
        classmembers["singleLineAttributes"] = True
        classmembers["compatibility"] = lambda x, y: None
        
        class_definition = classobj(str(classname), bases, classmembers)
        class_infos = {"type": COMPILEDCOMPLEXTYPE,
                "name": classname,
                "check": generateClassCheckFunction(class_definition),
                "initial": generateClassCreateFunction(class_definition),
                "extract": generateClassExtractFunction(class_definition),
                "generate": class_definition.generateXMLText}
        
        if self.FileName is not None:
            self.ComputedClasses[self.FileName][classname] = class_definition
        else:
            self.ComputedClasses[classname] = class_definition
        self.ComputedClassesInfos[classname] = class_infos
        
        return class_infos

    """
    Methods that print the classes generated
    """
    def PrintClasses(self):
        items = self.ComputedClasses.items()
        items.sort()
        if self.FileName is not None:
            for filename, classes in items:
                print "File '%s':" % filename
                class_items = classes.items()
                class_items.sort()
                for classname, xmlclass in class_items:
                    print "%s: %s" % (classname, str(xmlclass))
        else:
            for classname, xmlclass in items:
                print "%s: %s" % (classname, str(xmlclass))
        
    def PrintClassNames(self):
        classnames = self.XMLClassDefinitions.keys()
        classnames.sort()
        for classname in classnames:
            print classname

"""
Method that generate the method for checking a class instance
"""
def generateClassCheckFunction(class_definition):
    def classCheckfunction(instance):
        return isinstance(instance, class_definition)
    return classCheckfunction

"""
Method that generate the method for creating a class instance
"""
def generateClassCreateFunction(class_definition):
    def classCreatefunction():
        return class_definition()
    return classCreatefunction

"""
Method that generate the method for extracting a class instance
"""
def generateClassExtractFunction(class_definition):
    def classExtractfunction(node):
        instance = class_definition()
        instance.loadXMLTree(node)
        return instance
    return classExtractfunction

"""
Method that generate the method for loading an xml tree by following the
attributes list defined
"""
def generateSetattrMethod(factory, classinfos):
    attributes = dict([(attr["name"], attr) for attr in classinfos["attributes"] if attr["use"] != "prohibited"])
    optional_attributes = dict([(attr["name"], True) for attr in classinfos["attributes"] if attr["use"] == "optional"])
    elements = dict([(element["name"], element) for element in classinfos["elements"]])
    
    def setattrMethod(self, name, value):
        if attributes.has_key(name):
            attributes[name]["attr_type"] = FindTypeInfos(factory, attributes[name]["attr_type"])
            if value is None:
                if optional_attributes.get(name, False):
                    return object.__setattr__(self, name, None)
                else:
                    raise ValueError("Attribute '%s' isn't optional." % name)
            elif attributes[name].has_key("fixed") and value != attributes[name]["fixed"]:
                raise ValueError, "Value of attribute '%s' can only be '%s'."%(name, str(attributes[name]["fixed"]))
            elif attributes[name]["attr_type"]["check"](value):
                return object.__setattr__(self, name, value)
            else:
                raise ValueError("Invalid value for attribute '%s'." % (name))
        elif elements.has_key(name):
            if CheckElementValue(factory, name, elements[name], value):
                return object.__setattr__(self, name, value)
            else:
                raise ValueError("Invalid value for attribute '%s'." % (name))
        elif classinfos.has_key("base"):
            return classinfos["base"].__setattr__(self, name, value)
        elif self.__class__.__dict__.has_key(name):
            return object.__setattr__(self, name, value)
        else:
            raise AttributeError("'%s' can't have an attribute '%s'." % (self.__class__.__name__, name))
        
    return setattrMethod

"""
Method that generate the method for generating the xml tree structure model by 
following the attributes list defined
"""
def ComputeMultiplicity(name, infos):
    if infos["minOccurs"] == 0:
        if infos["maxOccurs"] == "unbounded":
            return "(?:%s)*" % name
        elif infos["maxOccurs"] == 1:
            return "(?:%s)?" % name
        else:
            return "(?:%s){,%d}" % (name, infos["maxOccurs"])
    elif infos["minOccurs"] == 1:
        if infos["maxOccurs"] == "unbounded":
            return "(?:%s)+" % name
        elif infos["maxOccurs"] == 1:
            return "(?:%s)" % name
        else:
            return "(?:%s){1,%d}" % (name, infos["maxOccurs"])
    else:
        if infos["maxOccurs"] == "unbounded":
            return "(?:%s){%d,}" % (name, infos["minOccurs"], name)
        else:
            return "(?:%s){%d,%d}" % (name, infos["minOccurs"], 
                                       infos["maxOccurs"])

def GetStructure(classinfos):
    elements = []
    for element in classinfos["elements"]:
        if element["type"] == ANY:
            infos = element.copy()
            infos["minOccurs"] = 0
            elements.append(ComputeMultiplicity("#text |#cdata-section |\w* ", infos))
        elif element["type"] == CHOICE:
            choices = []
            for infos in element["choices"]:
                if infos["type"] == "sequence":
                    structure = "(?:%s)" % GetStructure(infos)
                else:
                    structure = "%s " % infos["name"]
                choices.append(ComputeMultiplicity(structure, infos))
            elements.append(ComputeMultiplicity("|".join(choices), element))
        elif element["name"] == "content" and element["elmt_type"]["type"] == SIMPLETYPE:
            elements.append("(?:#text |#cdata-section )?")
        else:
            elements.append(ComputeMultiplicity("%s " % element["name"], element))
    if classinfos.get("order", True) or len(elements) == 0:
        return "".join(elements)
    else:
        raise ValueError("XSD structure not yet supported!")

def generateStructureMethod(classinfos):
    def getStructureMethod(self):
        structure = GetStructure(classinfos)
        if classinfos.has_key("base"):
            return classinfos["base"].getStructure(self) + structure
        return structure
    return getStructureMethod

"""
Method that generate the method for loading an xml tree by following the
attributes list defined
"""
def generateLoadXMLTree(factory, classinfos):
    attributes = dict([(attr["name"], attr) for attr in classinfos["attributes"] if attr["use"] != "prohibited"])
    elements = dict([(element["name"], element) for element in classinfos["elements"]])
    
    def loadXMLTreeMethod(self, tree, extras=[], derived=False):
        self.compatibility(tree)
        if not derived:
            children_structure = ""
            for node in tree.childNodes:
                if not (node.nodeName == "#text" and node.data.strip() == "") and node.nodeName != "#comment":
                    children_structure += "%s " % node.nodeName
            structure_pattern = self.getStructure()
            if structure_pattern != "":
                structure_model = re.compile("(%s)$" % structure_pattern)
                result = structure_model.match(children_structure)
                if not result:
                    raise ValueError("Invalid structure for \"%s\" children!." % tree.nodeName)
        required_attributes = dict([(attr["name"], True) for attr in classinfos["attributes"] if attr["use"] == "required"])
        if classinfos.has_key("base"):
            extras.extend([attr["name"] for attr in classinfos["attributes"] if attr["use"] != "prohibited"])
            classinfos["base"].loadXMLTree(self, tree, extras, True)
        for attrname, attr in tree._attrs.iteritems():
            if attributes.has_key(attrname):
                attributes[attrname]["attr_type"] = FindTypeInfos(factory, attributes[attrname]["attr_type"])
                object.__setattr__(self, attrname, attributes[attrname]["attr_type"]["extract"](attr))
            elif not classinfos.has_key("base") and attrname not in extras:
                raise ValueError("Invalid attribute \"%s\" for \"%s\" element!" % (attrname, tree.nodeName))
            required_attributes.pop(attrname, None)
        if len(required_attributes) > 0:
            raise ValueError("Required attributes %s missing for \"%s\" element!" % (", ".join(["\"%s\""%name for name in required_attributes]), tree.nodeName))
        first = {}
        for node in tree.childNodes:
            name = node.nodeName
            if name == "#text" and node.data.strip() == "" or name == "#comment":
                continue
            elif elements.has_key(name):
                elements[name]["elmt_type"] = FindTypeInfos(factory, elements[name]["elmt_type"])
                if elements[name]["maxOccurs"] == "unbounded" or elements[name]["maxOccurs"] > 1:
                    if first.get(name, True):
                        object.__setattr__(self, name, [elements[name]["elmt_type"]["extract"](node)])
                        first[name] = False
                    else:
                        getattr(self, name).append(elements[name]["elmt_type"]["extract"](node))
                else:
                    object.__setattr__(self, name, elements[name]["elmt_type"]["extract"](node))
            elif elements.has_key("text"):
                if elements["text"]["maxOccurs"] == "unbounded" or elements["text"]["maxOccurs"] > 1:
                    if first.get("text", True):
                        object.__setattr__(self, "text", [elements["text"]["elmt_type"]["extract"](node)])
                        first["text"] = False
                    else:
                        getattr(self, "text").append(elements["text"]["elmt_type"]["extract"](node))
                else:
                    object.__setattr__(self, "text", elements["text"]["elmt_type"]["extract"](node))
            elif elements.has_key("content"):
                if name in ["#cdata-section", "#text"]:
                    if elements["content"]["elmt_type"]["type"] == SIMPLETYPE:
                        object.__setattr__(self, "content", elements["content"]["elmt_type"]["extract"](node.data, False))
                else:
                    content = getattr(self, "content")
                    if elements["content"]["maxOccurs"] == "unbounded" or elements["content"]["maxOccurs"] > 1:
                        if first.get("content", True):
                            object.__setattr__(self, "content", [elements["content"]["elmt_type"]["extract"](node, None)])
                            first["content"] = False
                        else:
                            content.append(elements["content"]["elmt_type"]["extract"](node, content))
                    else:
                        object.__setattr__(self, "content", elements["content"]["elmt_type"]["extract"](node, content))
    return loadXMLTreeMethod
        

"""
Method that generates the method for generating an xml text by following the
attributes list defined
"""
def generateGenerateXMLText(factory, classinfos):
    def generateXMLTextMethod(self, name, indent=0, extras={}, derived=False):
        ind1, ind2 = getIndent(indent, name)
        if not derived:
            text = ind1 + u'<%s' % name
        else:
            text = u''
        
        first = True
        if not classinfos.has_key("base"):
            for attr, value in extras.items():
                if not first and not self.singleLineAttributes:
                    text += u'\n%s' % (ind2)
                text += u' %s=%s' % (attr, quoteattr(value))
                first = False
            extras.clear()
        for attr in classinfos["attributes"]:
            if attr["use"] != "prohibited":
                attr["attr_type"] = FindTypeInfos(factory, attr["attr_type"])
                value = getattr(self, attr["name"], None)
                if value != None:
                    computed_value = attr["attr_type"]["generate"](value)
                else:
                    computed_value = None
                if attr["use"] != "optional" or (value != None and \
                   computed_value != attr.get("default", attr["attr_type"]["generate"](attr["attr_type"]["initial"]()))):
                    if classinfos.has_key("base"):
                        extras[attr["name"]] = computed_value
                    else:
                        if not first and not self.singleLineAttributes:
                            text += u'\n%s' % (ind2)
                        text += ' %s=%s' % (attr["name"], quoteattr(computed_value))
                    first = False
        if classinfos.has_key("base"):
            first, new_text = classinfos["base"].generateXMLText(self, name, indent, extras, True)
            text += new_text
        else:
            first = True
        for element in classinfos["elements"]:
            element["elmt_type"] = FindTypeInfos(factory, element["elmt_type"])
            value = getattr(self, element["name"], None)
            if element["minOccurs"] == 0 and element["maxOccurs"] == 1:
                if value is not None:
                    if first:
                        text += u'>\n'
                        first = False
                    text += element["elmt_type"]["generate"](value, element["name"], indent + 1)
            elif element["minOccurs"] == 1 and element["maxOccurs"] == 1:
                if first:
                    text += u'>\n'
                    first = False
                if element["name"] == "content" and element["elmt_type"]["type"] == SIMPLETYPE:
                    text += element["elmt_type"]["generate"](value)
                else:
                    text += element["elmt_type"]["generate"](value, element["name"], indent + 1)
            else:
                if first and len(value) > 0:
                    text += u'>\n'
                    first = False
                for item in value:
                    text += element["elmt_type"]["generate"](item, element["name"], indent + 1)
        if not derived:
            if first:
                text += u'/>\n'
            else:
                text += ind1 + u'</%s>\n' % (name)
            return text
        else:
            return first, text
    return generateXMLTextMethod

def gettypeinfos(name, facets):
    if facets.has_key("enumeration") and facets["enumeration"][0] is not None:
        return facets["enumeration"][0]
    elif facets.has_key("maxInclusive"):
        limits = {"max" : None, "min" : None}
        if facets["maxInclusive"][0] is not None:
            limits["max"] = facets["maxInclusive"][0]
        elif facets["maxExclusive"][0] is not None:
            limits["max"] = facets["maxExclusive"][0] - 1
        if facets["minInclusive"][0] is not None:
            limits["min"] = facets["minInclusive"][0]
        elif facets["minExclusive"][0] is not None:
            limits["min"] = facets["minExclusive"][0] + 1
        if limits["max"] is not None or limits["min"] is not None:
            return limits
    return name

def generateGetElementAttributes(factory, classinfos):
    def getElementAttributes(self):
        attr_list = []
        if classinfos.has_key("base"):
            attr_list.extend(classinfos["base"].getElementAttributes(self))
        for attr in classinfos["attributes"]:
            if attr["use"] != "prohibited":
                attr_params = {"name" : attr["name"], "use" : attr["use"], 
                    "type" : gettypeinfos(attr["attr_type"]["basename"], attr["attr_type"]["facets"]),
                    "value" : getattr(self, attr["name"], "")}
                attr_list.append(attr_params)
        return attr_list
    return getElementAttributes

def generateGetElementInfos(factory, classinfos):
    attributes = dict([(attr["name"], attr) for attr in classinfos["attributes"] if attr["use"] != "prohibited"])
    elements = dict([(element["name"], element) for element in classinfos["elements"]])
    
    def getElementInfos(self, name, path=None, derived=False):
        attr_type = "element"
        value = None
        use = "required"
        children = []
        if path is not None:
            parts = path.split(".", 1)
            if attributes.has_key(parts[0]):
                if len(parts) != 0:
                    raise ValueError("Wrong path!")
                attr_type = gettypeinfos(attributes[parts[0]]["attr_type"]["basename"], 
                                         attributes[parts[0]]["attr_type"]["facets"])
                value = getattr(self, parts[0], "")
            elif elements.has_key(parts[0]):
                if elements[parts[0]]["elmt_type"]["type"] == SIMPLETYPE:
                    if len(parts) != 0:
                        raise ValueError("Wrong path!")
                    attr_type = gettypeinfos(elements[parts[0]]["elmt_type"]["basename"], 
                                             elements[parts[0]]["elmt_type"]["facets"])
                    value = getattr(self, parts[0], "")
                elif parts[0] == "content":
                    return self.content["value"].getElementInfos(self.content["name"], path)
                else:
                    attr = getattr(self, parts[0], None)
                    if attr is None:
                        raise ValueError("Wrong path!")
                    if len(parts) == 1:
                        return attr.getElementInfos(parts[0])
                    else:
                        return attr.getElementInfos(parts[0], parts[1])
            else:
                raise ValueError("Wrong path!")
        else:
            if not derived:
                children.extend(self.getElementAttributes())
            if classinfos.has_key("base"):
                children.extend(classinfos["base"].getElementInfos(self, name, derived=True)["children"])
            for element_name, element in elements.items():
                if element["minOccurs"] == 0:
                    use = "optional"
                if element_name == "content" and element["type"] == CHOICE:
                    attr_type = [(choice["name"], None) for choice in element["choices"]]
                    if self.content is None:
                        value = ""
                    else:
                        value = self.content["name"]
                        if self.content["value"] is not None:
                            if self.content["name"] == "sequence":
                                choices_dict = dict([(choice["name"], choice) for choice in element["choices"]])
                                sequence_infos = choices_dict.get("sequence", None)
                                if sequence_infos is not None:
                                    children.extend([item.getElementInfos(infos["name"]) for item, infos in zip(self.content["value"], sequence_infos["elements"])])
                            else:
                                children.extend(self.content["value"].getElementInfos(self.content["name"])["children"])
                elif element["elmt_type"]["type"] == SIMPLETYPE:
                    children.append({"name": element_name, "require": element["minOccurs"] != 0, 
                        "type": gettypeinfos(element["elmt_type"]["basename"], 
                                              element["elmt_type"]["facets"]),
                        "value": getattr(self, element_name, None)})
                else:
                    instance = getattr(self, element_name, None)
                    if instance is None:
                        instance = element["elmt_type"]["initial"]()
                    children.append(instance.getElementInfos(element_name))
        return {"name": name, "type": attr_type, "value": value, "use": use, "children": children}
    return getElementInfos

def generateSetElementValue(factory, classinfos):
    attributes = dict([(attr["name"], attr) for attr in classinfos["attributes"] if attr["use"] != "prohibited"])
    elements = dict([(element["name"], element) for element in classinfos["elements"]])
    
    def setElementValue(self, path, value):
        if path is not None:
            parts = path.split(".", 1)
            if attributes.has_key(parts[0]):
                if len(parts) != 1:
                    raise ValueError("Wrong path!")
                if attributes[parts[0]]["attr_type"]["basename"] == "boolean":
                    setattr(self, parts[0], value)
                else:
                    setattr(self, parts[0], attributes[parts[0]]["attr_type"]["extract"](value, False))
            elif elements.has_key(parts[0]):
                if elements[parts[0]]["elmt_type"]["type"] == SIMPLETYPE:
                    if len(parts) != 1:
                        raise ValueError("Wrong path!")
                    if elements[parts[0]]["elmt_type"]["basename"] == "boolean":
                        setattr(self, parts[0], value)
                    else:
                        setattr(self, parts[0], elements[parts[0]]["elmt_type"]["extract"](value, False))
                else:
                    instance = getattr(self, parts[0], None)
                    if instance != None:
                        if len(parts) > 1:
                            instance.setElementValue(parts[1], value)
                        else:
                            instance.setElementValue(None, value)
            elif elements.has_key("content"):
                if len(parts) > 0:
                    self.content["value"].setElementValue(path, value)
            elif classinfos.has_key("base"):
                classinfos["base"].setElementValue(self, path, value)
        elif elements.has_key("content"):
            if value == "":
                if elements["content"]["minOccurs"] == 0:
                    self.setcontent(None)
                else:
                    raise ValueError("\"content\" element is required!")
            else:
                self.setcontentbytype(value)
    return setElementValue

"""
Methods that generates the different methods for setting and getting the attributes
"""
def generateInitMethod(factory, classinfos):
    def initMethod(self):
        if classinfos.has_key("base"):
            classinfos["base"].__init__(self)
        for attribute in classinfos["attributes"]:
            attribute["attr_type"] = FindTypeInfos(factory, attribute["attr_type"])
            if attribute["use"] == "required":
                setattr(self, attribute["name"], attribute["attr_type"]["initial"]())
            elif attribute["use"] == "optional":
                if attribute.has_key("default"):
                    setattr(self, attribute["name"], attribute["attr_type"]["extract"](attribute["default"], False))
                else:
                    setattr(self, attribute["name"], None)
        for element in classinfos["elements"]:
            setattr(self, element["name"], GetElementInitialValue(factory, element))
    return initMethod

def generateSetMethod(attr):
    def setMethod(self, value):
        setattr(self, attr, value)
    return setMethod

def generateGetMethod(attr):
    def getMethod(self):
        return getattr(self, attr, None)
    return getMethod

def generateAddMethod(attr, factory, infos):
    def addMethod(self):
        if infos["type"] == ATTRIBUTE:
            infos["attr_type"] = FindTypeInfos(factory, infos["attr_type"])
            initial = infos["attr_type"]["initial"]
            extract = infos["attr_type"]["extract"]
        elif infos["type"] == ELEMENT:
            infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
            initial = infos["elmt_type"]["initial"]
            extract = infos["elmt_type"]["extract"]
        else:
            raise ValueError("Invalid class attribute!")
        if infos.has_key("default"):
            setattr(self, attr, extract(infos["default"], False))
        else:
            setattr(self, attr, initial())
    return addMethod

def generateDeleteMethod(attr):
    def deleteMethod(self):
        setattr(self, attr, None)
    return deleteMethod

def generateAppendMethod(attr, maxOccurs, factory, infos):
    def appendMethod(self, value):
        infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
        attr_list = getattr(self, attr)
        if maxOccurs == "unbounded" or len(attr_list) < maxOccurs:
            if infos["elmt_type"]["check"](value):
                attr_list.append(value)
            else:
                raise ValueError("\"%s\" value isn't valid!" % attr)
        else:
            raise ValueError("There can't be more than %d values in \"%s\"!" % (maxOccurs, attr))
    return appendMethod

def generateInsertMethod(attr, maxOccurs, factory, infos):
    def insertMethod(self, index, value):
        infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
        attr_list = getattr(self, attr)
        if maxOccurs == "unbounded" or len(attr_list) < maxOccurs:
            if infos["elmt_type"]["check"](value):
                attr_list.insert(index, value)
            else:
                raise ValueError("\"%s\" value isn't valid!" % attr)
        else:
            raise ValueError("There can't be more than %d values in \"%s\"!" % (maxOccurs, attr))
    return insertMethod

def generateGetChoicesMethod(choice_types):
    def getChoicesMethod(self):
        return [choice["name"] for choice in choice_types]
    return getChoicesMethod

def generateSetChoiceByTypeMethod(factory, choice_types):
    choices = dict([(choice["name"], choice) for choice in choice_types])
    def setChoiceMethod(self, type):
        if not choices.has_key(type):
            raise ValueError("Unknown \"%s\" choice type for \"content\"!" % type)
        choices[type]["elmt_type"] = FindTypeInfos(factory, choices[type]["elmt_type"])
        new_element = choices[type]["elmt_type"]["initial"]()
        self.content = {"name": type, "value": new_element}
        return new_element
    return setChoiceMethod

def generateAppendChoiceByTypeMethod(maxOccurs, factory, choice_types):
    choices = dict([(choice["name"], choice) for choice in choice_types])
    def appendChoiceMethod(self, type):
        if not choices.has_key(type):
            raise ValueError("Unknown \"%s\" choice type for \"content\"!" % type)
        choices[type]["elmt_type"] = FindTypeInfos(factory, choices[type]["elmt_type"])
        if maxOccurs == "unbounded" or len(self.content) < maxOccurs:
            new_element = choices[type]["elmt_type"]["initial"]()
            self.content.append({"name": type, "value": new_element})
            return new_element
        else:
            raise ValueError("There can't be more than %d values in \"content\"!" % maxOccurs)
    return appendChoiceMethod

def generateInsertChoiceByTypeMethod(maxOccurs, factory, choice_types):
    choices = dict([(choice["name"], choice) for choice in choice_types])
    def insertChoiceMethod(self, index, type):
        if not choices.has_key(type):
            raise ValueError("Unknown \"%s\" choice type for \"content\"!" % type)
        choices[type]["elmt_type"] = FindTypeInfos(factory, choices[type]["elmt_type"])
        if maxOccurs == "unbounded" or len(self.content) < maxOccurs:
            new_element = choices[type]["elmt_type"]["initial"]()
            self.content.insert(index, {"name" : type, "value" : new_element})
            return new_element
        else:
            raise ValueError("There can't be more than %d values in \"content\"!" % maxOccurs)
    return insertChoiceMethod

def generateRemoveMethod(attr, minOccurs):
    def removeMethod(self, index):
        attr_list = getattr(self, attr)
        if len(attr_list) > minOccurs:
            getattr(self, attr).pop(index)
        else:
            raise ValueError("There can't be less than %d values in \"%s\"!" % (minOccurs, attr))
    return removeMethod

def generateCountMethod(attr):
    def countMethod(self):
        return len(getattr(self, attr))
    return countMethod

"""
This function generate the classes from a class factory
"""
def GenerateClasses(factory, declare=False):
    ComputedClasses = factory.CreateClasses()
    #factory.PrintClasses()
    if declare:
        for ClassName, Class in pluginClasses.items():
            sys._getframe(1).f_locals[ClassName] = Class
        for TypeName, Type in pluginTypes.items():
            sys._getframe(1).f_locals[TypeName] = Type
    if factory.FileName is not None and len(ComputedClasses) == 1:
        globals().update(ComputedClasses[factory.FileName])
        return ComputedClasses[factory.FileName]
    else:
        globals().update(ComputedClasses)
        return ComputedClasses