xmlclass/xmlclass.py
author etisserant
Tue, 21 Aug 2007 08:55:59 +0200
changeset 75 82d371634f15
parent 67 3a1b0afdaf84
child 76 5bac3213fea1
permissions -rw-r--r--
Changed the way class are generated, using classobj from "new" module, instead of type inheritence.
#!/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

"""
Time and date definitions
"""
TimeType = time(0,0,0).__class__
DateType = date(1,1,1).__class__
DateTimeType = datetime(1,1,1,0,0,0).__class__

"""
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]*)?)')

"""
Dictionaries for stocking Classes and Types created from XML
"""
XMLClasses = {}

"""
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)

"""
This function opens the xsd file and generate the classes from the xml tree
"""
def GenerateClassesFromXSD(filename):
    xsdfile = open(filename, 'r')
    Generate_xsd_classes(minidom.parse(xsdfile), None)
    xsdfile.close()

"""
This function generate the classes from the xsd given as a string
"""
def GenerateClassesFromXSDstring(xsdstring):
    Generate_xsd_classes(minidom.parseString(xsdstring), None)

"""
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 Generate_xsd_classes(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"])
                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
                    Generate_xsd_classes(node, classname)
                    nodetype = "cls:%s"%classname
                if name == "attribute":
                    if "use" in node._attrs:
                        use = GetAttributeValue(node._attrs["use"])
                    else:
                        use = "optional"
                if 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)
                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 = Generate_xsd_classes(node, classname)
                else:
                    classname = parent
                    super, attrs = Generate_xsd_classes(node, classname.split("_")[-1])
                # When all attributes and inheritances have been extracted, the
                # values are added in the list of classes to create
                if classname not in XMLClasses:
                    XMLClasses[classname] = (super, attrs)
                elif XMLClasses[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 = Generate_xsd_classes(node, parent)
                if "ref" in attrs.keys():
                    choices = attrs
                else:
                    choices = {}
                    for attr, (attr_type, xml_type, write_type) in attrs.items():
                        choices[attr] = attr_type
                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 = Generate_xsd_classes(node, parent, True)
                if "maxOccurs" in node._attrs and GetAttributeValue(node._attrs["maxOccurs"]) == "unbounded":
                    for attr, (attr_type, xml_type, write_type) in attrs.items():
                        attrs[attr] = ("%s[]"%attr_type, xml_type, write_type)
                if "minOccurs" in node._attrs and GetAttributeValue(node._attrs["minOccurs"]) == "0":
                    for attr, (attr_type, xml_type, write_type) in attrs.items():
                        attrs[attr] = (attr_type, xml_type, "optional")
                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 = Generate_xsd_classes(node, None)
                    XMLClasses[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:
                #print name
                Generate_xsd_classes(node, parent)
            
            # Parse the childrens of node
            if recursion:
                super, attrs = Generate_xsd_classes(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
"""
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

"""
Funtion that returns the Python type and default value for a given type
"""
def GetTypeInitialValue(attr_type):
    type_compute = attr_type[4:].replace("[]", "")
    if attr_type.startswith("bse:"):
        if type_compute == "boolean":
            return BooleanType, "False"
        elif type_compute in ["decimal","unsignedLong","long","integer"]:
            return IntType, "0"
        elif type_compute in ["string","anyURI","NMTOKEN"]:
            return StringType, "\"\""
        elif type_compute == "time":
            return TimeType, "time(0,0,0,0)"
        elif type_compute == "date":
            return DateType, "date(1,1,1)"
        elif type_compute == "dateTime":
            return DateTimeType, "datetime(1,1,1,0,0,0,0)"
        elif type_compute == "language":
            return StringType, "\"en-US\""
        else:
            print "Can't affect: %s"%type_compute
    elif attr_type.startswith("cls:"):
        if type_compute in XMLClasses:
            return XMLClasses[type_compute],"%s()"%type_compute

"""
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 ["decimal","unsignedLong","long","integer"]:
        return int(value)
    elif type_compute in ["string","anyURI","NMTOKEN","language"]:
        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

"""
Method that generate the method for loading an xml tree by following the
attributes list defined
"""
def generateLoadXMLTree(bases, members, user_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 = 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 = eval("%s()"%attr_type[4:], globals().update(user_classes))
                            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 = eval("%s()"%attr_type[4:].replace("[]",""), globals().update(user_classes))
                            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 = eval("%s()"%attr_type[4:], globals().update(user_classes))
                            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 = 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 = eval("%s()"%attr_type[4:].replace("[]",""), globals().update(user_classes))
                            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 = []
        if "choice_content" in members.keys() and "choice_content" not in order:
            order.append("choice_content") 
        if "multichoice_content" in members.keys() and "multichoice_content" not in order:
            order.append("multichoice_content") 
        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 == "":
                    value = None
                if values[2] != "optional" or value != None:
                    if not first and not self.singleLineAttributes:
                        text += "\n%s"%(ind2)
                    if values[0].startswith("cls"):
                        if len(bases) > 0:
                            base_extras[attr] = value.getValue()
                        else:
                            text += " %s=\"%s\""%(attr, ComputeValue(value.getValue()))
                    else:
                        if len(bases) > 0:
                            base_extras[attr] = value
                        else:
                            text += " %s=\"%s\""%(attr, ComputeValue(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(bases, members):
    def getElementAttributes(self):
        attr_list = []
        for attr, values in members.items():
            if attr in ["order","choice_content","multichoice_content"]:
                pass
            elif values[1] == "attribute":
                if values[2] == "required":
                    require = True
                else:
                    require = False
                attr_hash ={"name": attr,"type": values[0] ,"value": getattr(self, attr, "") ,"require": require}
                attr_list.append(attr_hash)
        return attr_list
    return getElementAttributes
    
"""
Methods that generates the different methods for setting and getting the attributes
"""

def generateInitMethod(bases, members, user_classes):
    def initMethod(self):
        for base in bases:
            base.__init__(self)
        for attr, initial in members.items():
            setattr(self, attr, eval(initial, globals().update(user_classes)))
    return initMethod

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

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

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, user_classes):
    def addMethod(self):
        setattr(self, attr, eval(initial, globals().update(user_classes)))
    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 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 is the Metaclass for PLCOpen element classes. It generates automatically
the basic useful methods for manipulate the differents attributes of the classes
"""
def MetaClass(name, bases, members, user_classes):
    classmembers = {}
    initialValues = {}
    for attr, values in members.items():
        if attr in ["order", "basetype"]:
            pass
        
        # Class is a enumerated type
        elif attr == "enum":
            value_type, initial = GetTypeInitialValue(members["basetype"])
            initialValues["value"] = "\"%s\""%values[0]
            classmembers["value"]= values[0]
            classmembers["setValue"]= generateSetEnumMethod(values, value_type)
            classmembers["getValue"]= generateGetMethod("value")
        
        # Class is a limited type
        elif attr == "limit":
            value_type, initial = GetTypeInitialValue(members["basetype"])
            initial = 0
            if "min" in values:
                initial = max(initial, values["min"])
            if "max" in values:
                initial = min(initial, values["max"])
            initialValues["value"] = "%d"%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"] = "None"
            classmembers["deleteContent"]= generateDeleteMethod("content")
            classmembers["setContent"]= generateSetChoiceMethod(values)
            classmembers["getContent"]= generateGetMethod("content")
        elif attr == "multichoice_content":
            classmembers["content"]= []
            initialValues["content"] = "[]"
            classmembers["appendContent"]= generateAppendChoiceMethod(values)
            classmembers["insertContent"]= generateInsertChoiceMethod(values)
            classmembers["removeContent"]= generateRemoveMethod("content")
            classmembers["countContent"]= generateCountMethod("content")
            classmembers["setContent"]= generateSetMethod("content", ListType)
            classmembers["getContent"]= generateGetMethod("content")
        
        # It's an attribute of the class
        else:
            attrname = attr[0].upper()+attr[1:]
            attr_type, xml_type, write_type = values
            value_type, initial = GetTypeInitialValue(attr_type)
            # Value of the attribute is a list
            if attr_type.endswith("[]"):
                classmembers[attr]= []
                initialValues[attr] = "[]"
                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] = "None"
                    classmembers["add"+attrname] = generateAddMethod(attr, initial, user_classes)
                    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, user_classes)
    classmembers["loadXMLTree"]= generateLoadXMLTree(bases, members, user_classes)
    classmembers["generateXMLText"]= generateGenerateXMLText(bases, members)
    classmembers["getElementAttributes"]= generategetElementAttributes(bases, members)
    classmembers["singleLineAttributes"]= True

    return classobj(name, bases, classmembers)
    


"""
Methods that generate the classes
"""
def CreateClasses(user_classes, user_types):
    for classname in XMLClasses.keys():
        CreateClass(classname, user_classes, user_types)

def CreateClass(classname, user_classes, user_types):
    # Checks that classe haven't been generated yet
    if classname not in user_classes and classname not in user_types and classname in XMLClasses:
        inheritance, attributes = XMLClasses[classname]
        #print classe, inheritance, attributes
        members = {}
        bases = []
        
        # If inheritance classes haven't been generated
        for base in inheritance:
            if base not in user_classes:
                CreateClass(base, user_classes, user_types)
            bases.append(user_classes[base])
        
        # Checks that all attribute types are available 
        for attribute, type_attribute in attributes.items():
            if attribute == "group":
                user_types[classname] = type_attribute
            elif attribute == "ref":
                user_types[classname] = {}
                for attr in type_attribute:
                    if attr[4:] not in user_types:
                        CreateClass(attr[4:], user_classes, user_types)
                    user_types[classname].update(user_types[attr[4:]])
            elif attribute in ["choice_content","multichoice_content"]:
                element_types = {}
                for attr, value in type_attribute.items():
                    if attr == "ref":
                        for ref in value:
                            if ref[4:] not in user_types:
                                CreateClass(ref[4:], user_classes, user_types)
                            element_types.update(user_types[ref[4:]])
                    else:
                        element_types[attr] = value
                members[attribute] = element_types
            else:
                members[attribute] = type_attribute
                if attribute == "enum":
                    user_types["%s_enum"%classname] = type_attribute
                elif attribute not in ["limit", "order"]:
                    if type_attribute[0].startswith("ppx:"):
                        type_compute = type_attribute[0][4:].replace("[]","")
                        if type_compute not in user_classes:
                            CreateClass(type_compute, user_classes, user_types)
        if "group" not in attributes.keys() and "ref" not in attributes.keys():
            cls = MetaClass(classname, tuple(bases), members, user_classes)
            user_classes[classname] = cls

"""
Methods that print the classes generated
"""
def PrintClasses():
    for classname, xmlclass in XMLClasses.items():
        print "%s : %s\n"%(classname, str(xmlclass))
    
def PrintClassNames():
    classnames = XMLClasses.keys()
    classnames.sort()
    for classname in classnames:
        print classname

def DeclareXSDClass(XSDstring):
    pluginClasses = {}
    pluginTypes = {}
    GenerateClassesFromXSDstring(XSDstring)
    CreateClasses(pluginClasses, pluginTypes)
    
    for ClassName, Class in pluginClasses.items():
        sys._getframe(1).f_locals[ClassName] = Class