xmlclass/xmlclass.py
author lbessard
Wed, 24 Oct 2007 16:00:00 +0200
changeset 113 9eeaebd867aa
parent 92 76d5001393df
child 116 58b9b84e385f
permissions -rw-r--r--
Adding support for wxpython 2.4
#!/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

from xml.dom import minidom
import sys,re
from types import *
from datetime import *
from new import classobj

"""
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]*)?)')
date_model = re.compile('([0-9]{4})-([0-9]{2})-([0-9]{2})')
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]*)?)')

XSD_INTEGER_TYPES = ["integer","nonPositiveInteger","negativeInteger","long",
    "int","short","byte","nonNegativeInteger","unsignedLong","unsignedInt",
    "unsignedShort","unsignedByte","positiveInteger"]

XSD_STRING_TYPES = ["string","normalizedString","token","anyURI","NMTOKEN","language"]

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

"""
Function that extracts data from a node
"""
def GetAttributeValue(attr):
    if len(attr.childNodes) == 1:
        return attr.childNodes[0].data.encode()
    else:
        text = ""
        for node in attr.childNodes:
            if node.nodeName != "#text":
                text += node.data.encode()
        return text

"""
Function that computes value from a python type (Only Boolean are critical because
there is no uppercase in plcopen)
"""
def ComputeValue(value):
    if type(value) == BooleanType:
        if value:
            return "true"
        else:
            return "false"
    else:
        return str(value)

"""
Function that extracts a value from a string following the xsd type given
"""
def GetComputedValue(attr_type, value):
    type_compute = attr_type[4:].replace("[]", "")
    if type_compute == "boolean":
         if value == "true":
             return True
         elif value == "false":
             return False
         else:
            raise ValueError, "\"%s\" is not a valid boolean!"%value
    elif type_compute in XSD_INTEGER_TYPES:
        return int(value)
    elif type_compute in ["decimal", "float", "double"]:
        computed_value = float(value)
        if computed_value % 1 == 0:
            return int(computed_value)
        return computed_value
    elif type_compute in XSD_STRING_TYPES:
        return value
    elif type_compute == "time":
        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 time(*time_values)
        else:
            raise ValueError, "\"%s\" is not a valid time!"%value
    elif type_compute == "date":
        result = date_model.match(value)
        if result:
            date_values = [int(v) for v in result.groups()]
            return date(*date_values)
        else:
            raise ValueError, "\"%s\" is not a valid date!"%value
    elif type_compute == "dateTime":
        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)])
            return datetime(*datetime_values)
        else:
            raise ValueError, "\"%s\" is not a valid datetime!"%value
    else:
        print "Can't affect: %s"%type_compute
        return None

def GetInitialValueFunction(value):
    def GetInitialValue():
        return value
    return GetInitialValue

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

    def __init__(self, xsd_tree):
        self.XML_Tree = xsd_tree
        
        # Dictionary for stocking Classes and Types definitions created from the XML tree
        self.XMLClassDefinitions = {}
        
        # Dictionaries for stocking Classes and Types generated
        self.ComputedClasses = {}
        self.ComputedTypes = {}
        self.AlreadyComputed = {}

    """
    This function recursively creates a definition of the classes and their attributes
    for plcopen from the xsd file of plcopen opened in a DOM model
    """
    def GenerateXSDClasses(self, tree, parent, sequence = False):
        attributes = {}
        inheritance = []
        if sequence:
            order = []
        # The lists of attributes and inheritance of the node are generated from the childrens 
        for node in tree.childNodes:
            # We make fun of #text elements and all other tags that don't are xsd tags
            if node.nodeName != "#text" and node.nodeName.startswith("xsd:"):
                recursion = False
                name = node.nodeName[4:].encode()
                
                # This tags defines an attribute of the class
                if name in ["element", "attribute"]:
                    nodename = GetAttributeValue(node._attrs["name"])
                    default = None
                    if "type" in node._attrs:
                        nodetype = GetAttributeValue(node._attrs["type"])
                        if nodetype.startswith("xsd"):
                            nodetype = nodetype.replace("xsd", "bse")
                        elif nodetype.startswith("ppx"):
                            nodetype = nodetype.replace("ppx", "cls")
                    else:
                        # The type of attribute is defines in the child tree so we generate a new class
                        # No name is defined so we create one from nodename and parent class name
                        # (because some different nodes can have the same name)
                        if parent:
                            classname = "%s_%s"%(parent, nodename)
                        else:
                            classname = nodename
                        self.GenerateXSDClasses(node, classname)
                        nodetype = "cls:%s"%classname
                    if name == "attribute":
                        if "use" in node._attrs:
                            use = GetAttributeValue(node._attrs["use"])
                        else:
                            use = "optional"
                        if "default" in node._attrs:
                            default = GetAttributeValue(node._attrs["default"])
                    elif name == "element":
                        # If a tag can be written more than one time we define a list attribute
                        if "maxOccurs" in node._attrs and GetAttributeValue(node._attrs["maxOccurs"]) == "unbounded":
                            nodetype = "%s[]"%nodetype
                        if "minOccurs" in node._attrs and GetAttributeValue(node._attrs["minOccurs"]) == "0":
                            use = "optional"
                        else:
                            use = "required"
                    attributes[nodename] = (nodetype, name, use, default)
                    if sequence:
                        order.append(nodename)
                
                # This tag defines a new class
                elif name == "complexType" or name == "simpleType":
                    if "name" in node._attrs:
                        classname = GetAttributeValue(node._attrs["name"])
                        super, attrs = self.GenerateXSDClasses(node, classname)
                    else:
                        classname = parent
                        super, attrs = self.GenerateXSDClasses(node, classname.split("_")[-1])
                    # When all attributes and inheritances have been extracted, the
                    # values are added in the list of classes to create
                    if self.XMLClassDefinitions.get(classname, None) == None:
                        self.XMLClassDefinitions[classname] = (super, attrs)
                    elif self.XMLClassDefinitions[classname] != (super, attrs):
                        print "A different class has already got %s for name"%classname
                
                # This tag defines an attribute that can have different types
                elif name == "choice":
                    super, attrs = self.GenerateXSDClasses(node, parent)
                    
                    choices = {}
                    for attr, values in attrs.items():
                        if attr == "ref":
                            choices[attr] = values
                        else:
                            choices[attr] = values[0]
                    if "maxOccurs" in node._attrs and GetAttributeValue(node._attrs["maxOccurs"]) == "unbounded":
                        attributes["multichoice_content"] = choices
                        if sequence:
                            order.append("multichoice_content")
                    else:
                        attributes["choice_content"] = choices
                        if sequence:
                            order.append("choice_content")
                
                # This tag defines the order in which class attributes must be written
                # in plcopen xml file. We have to store this order like an attribute
                elif name in "sequence":
                    super, attrs, order = self.GenerateXSDClasses(node, parent, True)
                    if "maxOccurs" in node._attrs and GetAttributeValue(node._attrs["maxOccurs"]) == "unbounded":
                        for attr, (attr_type, xml_type, write_type, default) in attrs.items():
                            attrs[attr] = ("%s[]"%attr_type, xml_type, write_type, default)
                    if "minOccurs" in node._attrs and GetAttributeValue(node._attrs["minOccurs"]) == "0":
                        for attr, (attr_type, xml_type, write_type, default) in attrs.items():
                            attrs[attr] = (attr_type, xml_type, "optional", default)
                    inheritance.extend(super)
                    attributes.update(attrs)
                    attributes["order"] = order
                
                # This tag defines of types
                elif name == "group":
                    if "name" in node._attrs:
                        nodename = GetAttributeValue(node._attrs["name"])
                        super, attrs = self.GenerateXSDClasses(node, None)
                        self.XMLClassDefinitions[nodename] = (super, {"group":attrs["choice_content"]})
                    elif "ref" in node._attrs:
                        if "ref" not in attributes:
                            attributes["ref"] = [GetAttributeValue(node._attrs["ref"])]
                        else:
                            attributes["ref"].append(GetAttributeValue(node._attrs["ref"]))
                
                # This tag define a base class for the node
                elif name == "extension":
                    super = GetAttributeValue(node._attrs["base"])
                    if super.startswith("xsd"):
                        super = super.replace("xsd", "bse")
                    elif super.startswith("ppx"):
                        super = super.replace("ppx", "cls")
                    inheritance.append(super[4:])
                    recursion = True
                    
                # This tag defines a restriction on the type of attribute
                elif name == "restriction":
                    basetype = GetAttributeValue(node._attrs["base"])
                    if basetype.startswith("xsd"):
                        basetype = basetype.replace("xsd", "bse")
                    elif basetype.startswith("ppx"):
                        basetype = basetype.replace("ppx", "cls")
                    attributes["basetype"] = basetype
                    recursion = True
                
                # This tag defines an enumerated type
                elif name == "enumeration":
                    if "enum" not in attributes:
                        attributes["enum"] = [GetAttributeValue(node._attrs["value"])]
                    else:
                        attributes["enum"].append(GetAttributeValue(node._attrs["value"]))
                
                # This tags defines a restriction on a numerical value
                elif name in ["minInclusive","maxInclusive"]:
                    if "limit" not in attributes:
                        attributes["limit"] = {}
                    if name == "minInclusive":
                        attributes["limit"]["min"] = eval(GetAttributeValue(node._attrs["value"]))
                    elif name == "maxInclusive":
                        attributes["limit"]["max"] = eval(GetAttributeValue(node._attrs["value"]))
                
                # This tag are not important but their childrens are. The childrens are then parsed. 
                elif name in ["complexContent", "schema"]:
                    recursion = True
                
                # We make fun of xsd documentation
                elif name in ["annotation"]:
                    pass
                
                else:
                    # Unable this line to print XSD element that is not yet supported 
                    #print name
                    self.GenerateXSDClasses(node, parent)
                
                # Parse the childrens of node
                if recursion:
                    super, attrs = self.GenerateXSDClasses(node, parent)
                    inheritance.extend(super)
                    attributes.update(attrs)
        
        # if sequence tag have been found, order is returned
        if sequence:
            return inheritance, attributes, order
        else:
            return inheritance, attributes

    """
    Funtion that returns the Python type and default value for a given type
    """
    def GetTypeInitialValue(self, attr_type, default = None):
        type_compute = attr_type[4:].replace("[]", "")
        if attr_type.startswith("bse:"):
            if type_compute == "boolean":
                if default:
                    def GetBooleanInitialValue():
                        return default == "true"
                    return BooleanType, GetBooleanInitialValue
                else:
                    return BooleanType, lambda:False
            elif type_compute in ["unsignedLong","long","integer"]:
                if default:
                    def GetIntegerInitialValue():
                        return int(default)
                    return IntType, GetIntegerInitialValue
                else:
                    return IntType, lambda:0
            elif type_compute == "decimal":
                if default:
                    def GetFloatInitialValue():
                        return float(default)
                    return FloatType, GetFloatInitialValue
                else:
                    return FloatType, lambda:0.
            elif type_compute in ["string","anyURI","NMTOKEN"]:
                if default:
                    def GetStringInitialValue():
                        return default
                    return StringType, GetStringInitialValue
                else:
                    return StringType, lambda:""
            elif type_compute == "time":
                if default:
                    def GetTimeInitialValue():
                        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 time(*time_values)
                        return time(0,0,0,0)
                    return time, GetTimeInitialValue
                else:
                    return time, lambda:time(0,0,0,0)
            elif type_compute == "date":
                if default:
                    def GetDateInitialValue():
                        result = date_model.match(value)
                        if result:
                            date_values = [int(v) for v in result.groups()]
                            return date(*date_values)
                        return date(1,1,1)
                    return date, GetDateInitialValue
                else:
                    return date, lambda:date(1,1,1)
            elif type_compute == "dateTime":
                if default:
                    def GetDateTimeInitialValue():
                        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)])
                            return datetime(*datetime_values)
                        return datetime(1,1,1,0,0,0,0)
                    return datetime, GetDateTimeInitialValue
                else:
                    return datetime, lambda:datetime(1,1,1,0,0,0,0)
            elif type_compute == "language":
                if default:
                    def GetStringInitialValue():
                        return default
                    return StringType, GetStringInitialValue
                else:
                    return StringType, lambda:"en-US"
            else:
                print "Can't affect: %s"%type_compute
        elif attr_type.startswith("cls:"):
            if self.XMLClassDefinitions.get(type_compute, None) != None:
                def GetClassInitialValue():
                    if self.XMLClassDefinitions.get(type_compute, None) != None:
                        obj = self.ComputedClasses[type_compute]()
                        if default:
                            obj.setValue(default)
                        return obj
                    return None
                return self.XMLClassDefinitions[type_compute], GetClassInitialValue
        return None, lambda:None

    """
    Funtion that returns the Python type and default value for a given type
    """
    def GetInitialValues(self, value_types):
        initial_values = {}
        for name, value_type in value_types.items():
            result = self.GetTypeInitialValue(value_type)
            if result:
                initial_values[name] = result[1]
        return initial_values

    """
    Methods that generate the classes
    """
    def CreateClasses(self):
        self.GenerateXSDClasses(self.XML_Tree, None)
        for classname in self.XMLClassDefinitions.keys():
            self.CreateClass(classname)
        for classname in self.XMLClassDefinitions.keys():
            self.MarkUsedClasses(classname)
        return self.ComputedClasses, self.ComputedTypes

    def CreateClass(self, classname):
        # Checks that classe haven't been generated yet
        if not self.AlreadyComputed.get(classname, False) and classname in self.XMLClassDefinitions:
            self.AlreadyComputed[classname] = True
            inheritance, attributes = self.XMLClassDefinitions[classname]
            #print classname, inheritance, attributes
            members = {}
            bases = []
            
            # If inheritance classes haven't been generated
            for base in inheritance:
                self.CreateClass(base)
                bases.append(self.ComputedClasses[base])
            
            # Checks that all attribute types are available 
            for attribute, type_attribute in attributes.items():
                if attribute == "group":
                    self.ComputedTypes[classname] = type_attribute
                elif attribute in ["choice_content","multichoice_content"]:
                    element_types = {}
                    for attr, value in type_attribute.items():
                        if attr == "ref":
                            for ref in value:
                                self.CreateClass(ref[4:])
                                element_types.update(self.ComputedTypes[ref[4:]])
                        else:
                            element_types[attr] = value
                    members[attribute] = element_types
                else:
                    members[attribute] = type_attribute
                    if attribute == "enum":
                        self.ComputedTypes["%s_enum"%classname] = type_attribute
                    elif attribute not in ["limit", "order"]:
                        if type_attribute[0].startswith("cls:"):
                            type_compute = type_attribute[0][4:].replace("[]","")
                            self.CreateClass(type_compute)
            if "group" not in attributes:
                bases = tuple(bases)
                classmembers = {"IsBaseClass" : True}
                initialValues = {}
                for attr, values in members.items():
                    if attr in ["order", "basetype"]:
                        pass
                    
                    # Class is a enumerated type
                    elif attr == "enum":
                        value_type, initial = self.GetTypeInitialValue(members["basetype"])
                        initialValues["value"] = GetInitialValueFunction(values[0])
                        classmembers["value"] = values[0]
                        classmembers["setValue"] = generateSetEnumMethod(values, value_type)
                        classmembers["getValue"] = generateGetMethod("value")
                        classmembers["getValidValues"] = generateGetChoicesMethod(values)
                    
                    # Class is a limited type
                    elif attr == "limit":
                        value_type, initial = self.GetTypeInitialValue(members["basetype"])
                        if "min" in values:
                            initial = max(initial, values["min"])
                        elif "max" in values:
                            initial = min(initial, values["max"])
                        initialValues["value"] = GetInitialValueFunction(initial)
                        classmembers["value"] = initial
                        classmembers["setValue"] = generateSetLimitMethod(values, value_type)
                        classmembers["getValue"] = generateGetMethod("value")
                    
                    # Class has an attribute that can have different value types
                    elif attr == "choice_content":
                        classmembers["content"] = None
                        initialValues["content"] = lambda:None
                        classmembers["deleteContent"] = generateDeleteMethod("content")
                        classmembers["addContent"] = generateAddChoiceMethod(values, self.GetInitialValues(values))
                        classmembers["setContent"] = generateSetChoiceMethod(values)
                        classmembers["getContent"] = generateGetMethod("content")
                        classmembers["getChoices"] = generateGetChoicesMethod(values)
                    elif attr == "multichoice_content":
                        classmembers["content"] = []
                        initialValues["content"] = lambda:[]
                        classmembers["appendContent"] = generateAppendChoiceMethod(values)
                        classmembers["appendContentByType"] = generateAppendChoiceByTypeMethod(values, self.GetInitialValues(values))
                        classmembers["insertContent"] = generateInsertChoiceMethod(values)
                        classmembers["removeContent"] = generateRemoveMethod("content")
                        classmembers["countContent"] = generateCountMethod("content")
                        classmembers["setContent"] = generateSetMethod("content", ListType)
                        classmembers["getContent"] = generateGetMethod("content")
                        classmembers["getChoices"] = generateGetChoicesMethod(values)
                    
                    # It's an attribute of the class
                    else:
                        attrname = attr[0].upper()+attr[1:]
                        attr_type, xml_type, write_type, default = values
                        value_type, initial = self.GetTypeInitialValue(attr_type, default)
                        # Value of the attribute is a list
                        if attr_type.endswith("[]"):
                            classmembers[attr] = []
                            initialValues[attr] = lambda:[]
                            classmembers["append"+attrname] = generateAppendMethod(attr, value_type)
                            classmembers["insert"+attrname] = generateInsertMethod(attr, value_type)
                            classmembers["remove"+attrname] = generateRemoveMethod(attr)
                            classmembers["count"+attrname] = generateCountMethod(attr)
                            classmembers["set"+attrname] = generateSetMethod(attr, ListType)
                        else:
                            if write_type == "optional":
                                classmembers[attr] = None
                                initialValues[attr] = lambda:None
                                classmembers["add"+attrname] = generateAddMethod(attr, initial)
                                classmembers["delete"+attrname] = generateDeleteMethod(attr)
                            else:
                                classmembers[attr] = initial()
                                initialValues[attr] = initial
                            classmembers["set"+attrname] = generateSetMethod(attr, value_type)
                        classmembers["get"+attrname] = generateGetMethod(attr)
                classmembers["__init__"] = generateInitMethod(bases, initialValues)
                classmembers["loadXMLTree"] = generateLoadXMLTree(bases, members, self.ComputedClasses)
                classmembers["generateXMLText"] = generateGenerateXMLText(bases, members)
                classmembers["getElementAttributes"] = generateGetElementAttributes(members, self.ComputedClasses)
                classmembers["getElementInfos"] = generateGetElementInfos(members, self.ComputedClasses)
                classmembers["setElementValue"] = generateSetElementValue(members)
                classmembers["singleLineAttributes"] = True
                
                self.ComputedClasses[classname] = classobj(classname, bases, classmembers)

    def MarkUsedClasses(self, classname):
        # Checks that classe haven't been generated yet
        if classname in self.XMLClassDefinitions:
            inheritance, attributes = self.XMLClassDefinitions[classname]
            
            # If inheritance classes haven't been generated
            for base in inheritance:
                if base in self.ComputedClasses:
                    self.ComputedClasses[base].IsBaseClass = False
                
            # Checks that all attribute types are available 
            for attribute, type_attribute in attributes.items():
                if attribute in ["choice_content","multichoice_content"]:
                    element_types = {}
                    for attr, value in type_attribute.items():
                        if attr == "ref":
                            for ref in value:
                                element_types.update(self.ComputedTypes[ref[4:]])
                        else:
                            element_types[attr] = value
                    for type_name in element_types.values():
                        type_compute = type_name[4:].replace("[]","")
                        if type_compute in self.ComputedClasses:
                            self.ComputedClasses[type_compute].IsBaseClass = False
                elif attribute != "group":
                    if attribute not in ["enum", "limit", "order"]:
                        if type_attribute[0].startswith("cls:"):
                            type_compute = type_attribute[0][4:].replace("[]","")
                            if type_compute in self.ComputedClasses:
                                self.ComputedClasses[type_compute].IsBaseClass = False

    """
    Methods that print the classes generated
    """
    def PrintClasses(self):
        for classname, xmlclass in self.ComputedClasses.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 loading an xml tree by following the
attributes list defined
"""
def generateLoadXMLTree(bases, members, classes):
    def loadXMLTreeMethod(self, tree):
        # If class is derived, values of inheritance classes are loaded
        for base in bases:
            base.loadXMLTree(self, tree)
        # Class is a enumerated or limited value
        if "enum" in members.keys() or "limit" in members.keys():
            attr_value = GetAttributeValue(tree)
            attr_type = members["basetype"]
            val = GetComputedValue(attr_type, attr_value)
            self.setValue(val)
        else:
            
            # Load the node attributes if they are defined in the list
            for attrname, attr in tree._attrs.items():
                if attrname in members.keys():
                    attr_type, xml_type, write_type, default = members[attrname]
                    attr_value = GetAttributeValue(attr)
                    if write_type != "optional" or attr_value != "":
                        # Extracts the value
                        if attr_type.startswith("bse:"):
                            val = GetComputedValue(attr_type, attr_value)
                        elif attr_type.startswith("cls:"):
                            val = classes[attr_type[4:]]()
                            val.loadXMLTree(attr)
                        setattr(self, attrname, val)
            
            # Load the node childs if they are defined in the list
            for node in tree.childNodes:
                name = node.nodeName
                # We make fun of #text elements
                if name != "#text":
                    
                    # Class has an attribute that can have different value types
                    if "choice_content" in members.keys() and name in members["choice_content"].keys():
                        attr_type = members["choice_content"][name]
                        # Extracts the value
                        if attr_type.startswith("bse:"):
                            attr_value = GetAttributeValue(node)
                            if write_type != "optional" or attr_value != "":
                                val = GetComputedValue(attr_type.replace("[]",""), attr_value)
                            else:
                                val = None
                        elif attr_type.startswith("cls:"):
                            val = classes[attr_type[4:].replace("[]","")]()
                            val.loadXMLTree(node)
                        # Stock value in content attribute
                        if val:
                            if attr_type.endswith("[]"):
                                if self.content:
                                    self.content["value"].append(val)
                                else:
                                    self.content = {"name":name,"value":[val]}
                            else:
                                self.content = {"name":name,"value":val}
                    
                    # Class has a list of attributes that can have different value types
                    elif "multichoice_content" in members.keys() and name in members["multichoice_content"].keys():
                        attr_type = members["multichoice_content"][name]
                        # Extracts the value
                        if attr_type.startswith("bse:"):
                            attr_value = GetAttributeValue(node)
                            if write_type != "optional" or attr_value != "":
                                val = GetComputedValue(attr_type, attr_value)
                            else:
                                val = None
                        elif attr_type.startswith("cls:"):
                            val = classes[attr_type[4:]]()
                            val.loadXMLTree(node)
                        # Add to content attribute list
                        if val:
                            self.content.append({"name":name,"value":val})
                    
                    # The node child is defined in the list
                    elif name in members.keys():
                        attr_type, xml_type, write_type, default = members[name]
                        # Extracts the value
                        if attr_type.startswith("bse:"):
                            attr_value = GetAttributeValue(node)
                            if write_type != "optional" or attr_value != "":
                                val = GetComputedValue(attr_type.replace("[]",""), attr_value)
                            else:
                                val = None
                        elif attr_type.startswith("cls:"):
                            val = classes[attr_type[4:].replace("[]","")]()
                            val.loadXMLTree(node)
                        # Stock value in attribute
                        if val:
                            if attr_type.endswith("[]"):
                                getattr(self, name).append(val)
                            else:
                                setattr(self, name, val)
    return loadXMLTreeMethod

"""
Method that generates the method for generating an xml text by following the
attributes list defined
"""
def generateGenerateXMLText(bases, members):
    def generateXMLTextMethod(self, name, indent, extras = {}, derived = False):
        ind1, ind2 = getIndent(indent, name)
        if not derived:
            text = ind1 + "<%s"%name
        else:
            text = ""
        if len(bases) > 0:
            base_extras = {}
        if "order" in members.keys():
            order = members["order"]
        else:
            order = []
        for attr, values in members.items():
            if attr != "order" and (attr in ("choice_content", "multichoice_content") or values[1] != "attribute"):
                if attr not in order:
                    order.append(attr)
        size = 0
        first = True
        for attr, value in extras.items():
            if not first and not self.singleLineAttributes:
                text += "\n%s"%(ind2)
            text += " %s=\"%s\""%(attr, ComputeValue(value))
            first = False
        for attr, values in members.items():
            if attr in ["order","choice_content","multichoice_content"]:
                pass
            elif attr in ["enum","limit"]:
                if not derived:
                    text += ">%s</%s>\n"%(ComputeValue(self.value),name)
                else:
                    text += ComputeValue(self.value)
                return text
            elif values[1] == "attribute":
                value = getattr(self, attr, None)
                if value != None:
                    if values[0].startswith("cls"):
                        value = value.getValue()
                    computed_value = ComputeValue(value)
                else:
                    computed_value = None
                if values[2] != "optional" or (value != None and computed_value != values[3]):
                    if len(bases) > 0:
                        base_extras[attr] = value
                    else:
                        if not first and not self.singleLineAttributes:
                            text += "\n%s"%(ind2)
                        text += " %s=\"%s\""%(attr, computed_value)
                    first = False
        if len(bases) > 0:
            first, new_text = bases[0].generateXMLText(self, name, indent, base_extras, True)
            text += new_text
        else:
            first = True
        ind3, ind4 = getIndent(indent + 1, name)
        for attr in order:
            value = getattr(self, attr, None)
            if attr == "choice_content":
                if self.content:
                    if first:
                        text += ">\n"
                        first = False
                    value_type = members[attr][self.content["name"]]
                    if value_type.startswith("bse:"):
                        if value_type.endswith("[]"):
                            for content in self.content["value"]:
                                text += ind1 + "<%s>%s</%s>\n"%(self.content["name"], ComputeValue(content), self.content["name"])
                        else:
                            text += ind1 + "<%s>%s</%s>\n"%(self.content["name"], ComputeValue(self.content["value"]), self.content["name"])
                    elif value_type.endswith("[]"):
                        for content in self.content["value"]:
                            text += content.generateXMLText(self.content["name"], indent + 1)
                    else:
                        text += self.content["value"].generateXMLText(self.content["name"], indent + 1)
            elif attr == "multichoice_content":
                if len(self.content) > 0:
                    for element in self.content:
                        if first:
                            text += ">\n"
                            first = False
                        value_type = members[attr][element["name"]]
                        if value_type.startswith("bse:"):
                            text += ind1 + "<%s>%s</%s>\n"%(element["name"], ComputeValue(element["value"]), element["name"])
                        else:
                            text += element["value"].generateXMLText(element["name"], indent + 1)
            elif members[attr][2] != "optional" or value != None:
                if members[attr][0].endswith("[]"):
                    if first and len(value) > 0:
                        text += ">\n"
                        first = False
                    for element in value:
                        if members[attr][0].startswith("bse:"):
                            text += ind3 + "<%s>%s</%s>\n"%(attr, ComputeValue(element), attr)
                        else:
                            text += element.generateXMLText(attr, indent + 1)
                else:
                    if first:
                        text += ">\n"
                        first = False
                    if members[attr][0].startswith("bse:"):
                        text += ind3 + "<%s>%s</%s>\n"%(attr, ComputeValue(value), attr)
                    else:
                        text += getattr(self, attr).generateXMLText(attr, indent + 1)
        if not derived:
            if first:
                text += "/>\n"
            else:
                text += ind1 + "</%s>\n"%(name)
            return text
        else:
            return first, text
    return generateXMLTextMethod


def generateGetElementAttributes(members, classes):
    def getElementAttributes(self):
        attr_list = []
        for attr, values in members.items():
            if attr in ["order","choice_content","multichoice_content"]:
                pass
            elif values[1] == "attribute":
                attr_params = {"name": attr, "require": values[2] == "required"}
                if values[0].startswith("cls:"):
                    attr_value = getattr(self, attr, None)
                    if attr_value:
                        attr_params["value"] = attr_value.getValue()
                    else:
                        attr_params["value"] = ""
                    attr_params["type"] = classes[values[0][4:]]().getValidValues()
                else:
                    attr_params["value"] = getattr(self, attr, "")
                    attr_params["type"] = values[0][4:]
                attr_list.append(attr_params)
        return attr_list
    return getElementAttributes

def generateGetElementInfos(members, classes):
    def getElementInfos(self, name, path = None):
        attr_type = "element"
        value = None
        children = []
        if "enum" in members:
            attr_type = self.getValidValues()
            value = self.value
        elif "limit" in members:
            attr_type = {"min" : None, "max" : None}
            if "min" in members:
                attr_type["min"] = members["min"]
            if "max" in members:
                attr_type["max"] = members["max"]
            value = self.value
        elif path:
            if "choice_content" in members:
                return self.content["value"].getElementInfos(self.content["name"], path)
            elif "multichoice_content" not in members:
                parts = path.split(".", 1)
                if parts[0] in members:
                    values = members[parts[0]]
                    if values[1] == "attribute" and len(parts) == 1:
                        attr = getattr(self, parts[0], None)
                        if attr != None:
                            if values[0].startswith("cls:"):
                                return attr.getElementInfos(parts[0])
                            else:
                                attr_type = values[0][4:]
                                value = getattr(self, attr, "")
                    elif values[1] == "element":
                        attr = getattr(self, parts[0], None)
                        if attr != None:
                            if len(parts) == 1:
                                return attr.getElementInfos(parts[0])
                            else:
                                return attr.getElementInfos(parts[0], parts[1])
        else:
            for attr, values in members.items():
                if attr == "order":
                    pass
                elif attr == "choice_content":
                    attr_type = self.getChoices().items()
                    if self.content:
                        value = self.content["name"]
                        children.extend(self.content["value"].getElementInfos(self.content["name"])["children"])
                elif attr == "multichoice_content":
                    for element_infos in self.content:
                        children.append(element_infos["value"].getElementInfos(element_infos["name"]))
                elif values[1] == "attribute" and not values[0].startswith("cls:"):
                    children.append({"name" : attr, "value" : getattr(self, attr, ""), "type" : values[0][4:], "children" : []})
                else:
                    element = getattr(self, attr, None)
                    if not element:
                        element = classes[values[0][4:]]()
                    children.append(element.getElementInfos(attr))
        return {"name" : name, "type" : attr_type, "value" : value, "children" : children}
    return getElementInfos

def generateSetElementValue(members):
    def setElementValue(self, path, value):
        if "enum" in members or "limit" in members:
            if not path:
                self.setValue(value)
        elif "choice_content" in members:
            if path:
                self.content["value"].setElementValue(path, value)
            else:
                self.addContent(value)
        else: 
            parts = path.split(".", 1)
            if parts[0] in members:
                values = members[parts[0]]
                if values[1] == "attribute" and len(parts) == 1:
                    attr = getattr(self, parts[0], None)
                    if attr != None:
                        if values[0].startswith("cls:"):
                            attr.setElementValue(None, value)
                        elif values[0][4:] == "boolean":
                            setattr(self, parts[0], value)
                        else:
                            setattr(self, parts[0], GetComputedValue(values[0], value))
                elif values[1] == "element":
                    attr = getattr(self, parts[0], None)
                    if attr != None:
                        if len(parts) == 1:
                            attr.setElementValue(None, value)
                        else:
                            attr.setElementValue(parts[1], value)
    return setElementValue

"""
Methods that generates the different methods for setting and getting the attributes
"""
def generateInitMethod(bases, members):
    def initMethod(self):
        for base in bases:
            base.__init__(self)
        for attr, initial in members.items():
            setattr(self, attr, initial())
    return initMethod

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

def generateAddChoiceMethod(choice_type, initial_values):
    def addChoiceMethod(self, name):
        if name in choice_type:
            self.content = {"name" : name, "value" : initial_values[name]()}
    return addChoiceMethod

def generateSetChoiceMethod(choice_type):
    def setChoiceMethod(self, name, value):
        self.content = {"name" : name, "value" : value}
    return setChoiceMethod

def generateGetChoicesMethod(choice_type):
    def getChoicesMethod(self):
        return choice_type
    return getChoicesMethod

def generateSetEnumMethod(enum, attr_type):
    def setEnumMethod(self, value):
        if value in enum:
            self.value = value
        else:
            raise ValueError, "%s is not a valid value. Must be in %s"%(value, str(enum))
    return setEnumMethod

def generateSetLimitMethod(limit, attr_type):
    def setMethod(self, value):
        if "min" in limit and value < limit["min"]:
            raise ValueError, "%s is not a valid value. Must be greater than %d"%(value, limit["min"])
        elif "max" in limit and value > limit["max"]:
            raise ValueError, "%s is not a valid value. Must be smaller than %d"%(value, limit["max"])
        else:
            self.value = value
    return setMethod

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

def generateAddMethod(attr, initial):
    def addMethod(self):
        setattr(self, attr, initial())
    return addMethod

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

def generateAppendMethod(attr, attr_type):
    def appendMethod(self, value):
        getattr(self, attr).append(value)
    return appendMethod

def generateInsertMethod(attr, attr_type):
    def insertMethod(self, index, value):
        getattr(self, attr).insert(index, value)
    return insertMethod

def generateAppendChoiceByTypeMethod(choice_type, initial_values):
    def addChoiceMethod(self, name):
        if name in choice_type:
            self.content.append({"name" : name, "value" : initial_values[name]()})
    return addChoiceMethod

def generateAppendChoiceMethod(choice_types):
    def appendMethod(self, name, value):
        self.content.append({"name":name,"value":value})
    return appendMethod

def generateInsertChoiceMethod(choice_types):
    def insertMethod(self, index, name, value):
        self.content.insert(index, {"name":name,"value":value})
    return insertMethod

def generateRemoveMethod(attr):
    def removeMethod(self, index):
        getattr(self, attr).pop(index)
    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, ComputedTypes = factory.CreateClasses()
    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
    globals().update(ComputedClasses)
    return ComputedClasses, ComputedTypes

"""
This function opens the xsd file and generate the classes from the xml tree
"""
def GenerateClassesFromXSD(filename, declare = False):
    xsdfile = open(filename, 'r')
    factory = ClassFactory(minidom.parse(xsdfile))
    xsdfile.close()
    return GenerateClasses(factory, declare)

"""
This function generate the classes from the xsd given as a string
"""
def GenerateClassesFromXSDstring(xsdstring, declare = False):
    factory = ClassFactory(minidom.parseString(xsdstring))
    return GenerateClasses(factory, declare)