Laurent@814: #!/usr/bin/env python
Laurent@814: # -*- coding: utf-8 -*-
Laurent@814: 
andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for
andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
Laurent@814: #
andrej@1571: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
Laurent@814: #
andrej@1571: # See COPYING file for copyrights details.
Laurent@814: #
andrej@1571: # This program is free software; you can redistribute it and/or
andrej@1571: # modify it under the terms of the GNU General Public License
andrej@1571: # as published by the Free Software Foundation; either version 2
andrej@1571: # of the License, or (at your option) any later version.
Laurent@814: #
andrej@1571: # This program is distributed in the hope that it will be useful,
andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of
andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
andrej@1571: # GNU General Public License for more details.
Laurent@814: #
andrej@1571: # You should have received a copy of the GNU General Public License
andrej@1571: # along with this program; if not, write to the Free Software
andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
Laurent@814: 
andrej@1826: 
andrej@1881: from __future__ import absolute_import
andrej@1826: from __future__ import print_function
andrej@1732: import os
Laurent@814: import re
Laurent@814: import datetime
Edouard@2551: from functools import reduce
Laurent@814: from xml.dom import minidom
andrej@1850: from xml.sax.saxutils import unescape
Laurent@1290: from collections import OrderedDict
andrej@2434: from builtins import str as text
andrej@2434: 
andrej@2450: from six import string_types
andrej@2432: from six.moves import xrange
andrej@1832: from lxml import etree
andrej@1832: 
andrej@1736: 
Laurent@814: def CreateNode(name):
Laurent@814:     node = minidom.Node()
Laurent@814:     node.nodeName = name
Laurent@814:     node._attrs = {}
Laurent@814:     node.childNodes = []
Laurent@814:     return node
Laurent@814: 
andrej@1736: 
Laurent@814: def NodeRenameAttr(node, old_name, new_name):
Laurent@814:     node._attrs[new_name] = node._attrs.pop(old_name)
Laurent@814: 
andrej@1736: 
Laurent@814: def NodeSetAttr(node, name, value):
Laurent@814:     attr = minidom.Attr(name)
andrej@2434:     txt = minidom.Text()
andrej@2434:     txt.data = value
andrej@2434:     attr.childNodes[0] = txt
Laurent@814:     node._attrs[name] = attr
Laurent@814: 
andrej@1749: 
andrej@1837: # Regular expression models for checking all kind of
andrej@1837: # string values defined in XML standard
andrej@1837: 
andrej@2439: Name_model = re.compile(r'([a-zA-Z_\:][\w\.\-\:]*)$')
andrej@2439: Names_model = re.compile(r'([a-zA-Z_\:][\w\.\-\:]*(?: [a-zA-Z_\:][\w\.\-\:]*)*)$')
andrej@2439: NMToken_model = re.compile(r'([\w\.\-\:]*)$')
andrej@2439: NMTokens_model = re.compile(r'([\w\.\-\:]*(?: [\w\.\-\:]*)*)$')
andrej@2439: QName_model = re.compile(r'((?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*)$')
andrej@2439: QNames_model = re.compile(r'((?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*(?: (?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*)*)$')
andrej@2439: NCName_model = re.compile(r'([a-zA-Z_][\w]*)$')
andrej@2439: URI_model = re.compile(r'((?:htt(p|ps)://|/)?(?:[\w.-]*/?)*)$')
andrej@2439: LANGUAGE_model = re.compile(r'([a-zA-Z]{1,8}(?:-[a-zA-Z0-9]{1,8})*)$')
andrej@2439: 
andrej@2439: ONLY_ANNOTATION = re.compile(r"((?:annotation )?)")
Laurent@814: 
Laurent@814: """
Laurent@814: Regular expression models for extracting dates and times from a string
Laurent@814: """
andrej@2439: time_model = re.compile(r'([0-9]{2}):([0-9]{2}):([0-9]{2}(?:\.[0-9]*)?)(?:Z)?$')
andrej@2439: date_model = re.compile(r'([0-9]{4})-([0-9]{2})-([0-9]{2})((?:[\-\+][0-9]{2}:[0-9]{2})|Z)?$')
andrej@2439: datetime_model = re.compile(r'([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)?$')
Laurent@814: 
andrej@1736: 
Laurent@814: class xml_timezone(datetime.tzinfo):
Laurent@814: 
Laurent@814:     def SetOffset(self, offset):
Laurent@814:         if offset == "Z":
andrej@1872:             self.__offset = datetime.timedelta(minutes=0)
Laurent@814:             self.__name = "UTC"
Laurent@814:         else:
andrej@1739:             sign = {"-": -1, "+": 1}[offset[0]]
Laurent@814:             hours, minutes = [int(val) for val in offset[1:].split(":")]
andrej@1872:             self.__offset = datetime.timedelta(minutes=sign * (hours * 60 + minutes))
Laurent@814:             self.__name = ""
Laurent@814: 
Laurent@814:     def utcoffset(self, dt):
Laurent@814:         return self.__offset
Laurent@814: 
Laurent@814:     def tzname(self, dt):
Laurent@814:         return self.__name
Laurent@814: 
Laurent@814:     def dst(self, dt):
andrej@1872:         return datetime.timedelta(0)
Laurent@814: 
andrej@1749: 
andrej@1773: [
andrej@1773:     SYNTAXELEMENT, SYNTAXATTRIBUTE, SIMPLETYPE, COMPLEXTYPE, COMPILEDCOMPLEXTYPE,
andrej@1773:     ATTRIBUTESGROUP, ELEMENTSGROUP, ATTRIBUTE, ELEMENT, CHOICE, ANY, TAG, CONSTRAINT,
Laurent@814: ] = range(13)
Laurent@814: 
andrej@1736: 
Laurent@814: def NotSupportedYet(type):
Laurent@814:     """
Laurent@814:     Function that generates a function that point out to user that datatype
Laurent@814:     used is not supported by xmlclass yet
Laurent@814:     @param type: data type
Laurent@814:     @return: function generated
Laurent@814:     """
Laurent@814:     def GetUnknownValue(attr):
andrej@1764:         raise ValueError("\"%s\" type isn't supported by \"xmlclass\" yet!" % type)
Laurent@814:     return GetUnknownValue
Laurent@814: 
andrej@1736: 
Laurent@814: def getIndent(indent, balise):
andrej@1736:     """
andrej@1736:     This function calculates the number of whitespace for indentation
andrej@1736:     """
Laurent@814:     first = indent * 2
Laurent@814:     second = first + len(balise) + 1
Laurent@814:     return u'\t'.expandtabs(first), u'\t'.expandtabs(second)
Laurent@814: 
Laurent@814: 
Laurent@814: def GetAttributeValue(attr, extract=True):
Laurent@814:     """
Laurent@814:     Function that extracts data from a tree node
Laurent@814:     @param attr: tree node containing data to extract
Laurent@814:     @param extract: attr is a tree node or not
Laurent@814:     @return: data extracted as string
Laurent@814:     """
Laurent@814:     if not extract:
Laurent@814:         return attr
Laurent@814:     if len(attr.childNodes) == 1:
andrej@2434:         return text(unescape(attr.childNodes[0].data))
Laurent@814:     else:
Laurent@814:         # content is a CDATA
andrej@2434:         txt = u''
Laurent@814:         for node in attr.childNodes:
Laurent@814:             if not (node.nodeName == "#text" and node.data.strip() == u''):
andrej@2434:                 txt += text(unescape(node.data))
Laurent@814:         return text
Laurent@814: 
Laurent@814: 
Laurent@814: def GetNormalizedString(attr, extract=True):
Laurent@814:     """
andrej@1730:     Function that normalizes a string according to XML 1.0. Replace
Laurent@814:     tabulations, line feed and carriage return by white space
Laurent@814:     @param attr: tree node containing data to extract or data to normalize
Laurent@814:     @param extract: attr is a tree node or not
Laurent@814:     @return: data normalized as string
Laurent@814:     """
Laurent@814:     if extract:
Laurent@814:         value = GetAttributeValue(attr)
Laurent@814:     else:
Laurent@814:         value = attr
Laurent@814:     return value.replace("\t", " ").replace("\r", " ").replace("\n", " ")
Laurent@814: 
Laurent@814: 
Laurent@814: def GetToken(attr, extract=True):
Laurent@814:     """
andrej@1730:     Function that tokenizes a string according to XML 1.0. Remove any leading
andrej@1730:     and trailing white space and replace internal sequence of two or more
Laurent@814:     spaces by only one white space
Laurent@814:     @param attr: tree node containing data to extract or data to tokenize
Laurent@814:     @param extract: attr is a tree node or not
Laurent@814:     @return: data tokenized as string
Laurent@814:     """
andrej@1730:     return " ".join([part for part in
Laurent@814:                      GetNormalizedString(attr, extract).split(" ")
Laurent@814:                      if part])
Laurent@814: 
Laurent@814: 
Laurent@814: def GetHexInteger(attr, extract=True):
Laurent@814:     """
Laurent@814:     Function that extracts an hexadecimal integer from a tree node or a string
Laurent@814:     @param attr: tree node containing data to extract or data as a string
Laurent@814:     @param extract: attr is a tree node or not
Laurent@814:     @return: data as an integer
Laurent@814:     """
Laurent@814:     if extract:
Laurent@814:         value = GetAttributeValue(attr)
Laurent@814:     else:
Laurent@814:         value = attr
Laurent@814:     if len(value) % 2 != 0:
Laurent@814:         raise ValueError("\"%s\" isn't a valid hexadecimal integer!" % value)
Laurent@814:     try:
Laurent@814:         return int(value, 16)
andrej@1780:     except Exception:
Laurent@814:         raise ValueError("\"%s\" isn't a valid hexadecimal integer!" % value)
Laurent@814: 
Laurent@814: 
andrej@1730: def GenerateIntegerExtraction(minInclusive=None, maxInclusive=None,
Laurent@814:                               minExclusive=None, maxExclusive=None):
Laurent@814:     """
Laurent@814:     Function that generates an extraction function for integer defining min and
Laurent@814:     max of integer value
Laurent@814:     @param minInclusive: inclusive minimum
Laurent@814:     @param maxInclusive: inclusive maximum
Laurent@814:     @param minExclusive: exclusive minimum
Laurent@814:     @param maxExclusive: exclusive maximum
Laurent@814:     @return: function generated
Laurent@814:     """
Laurent@814:     def GetInteger(attr, extract=True):
Laurent@814:         """
Laurent@814:         Function that extracts an integer from a tree node or a string
Laurent@814:         @param attr: tree node containing data to extract or data as a string
Laurent@814:         @param extract: attr is a tree node or not
Laurent@814:         @return: data as an integer
Laurent@814:         """
Laurent@814: 
Laurent@814:         if extract:
Laurent@814:             value = GetAttributeValue(attr)
Laurent@814:         else:
Laurent@814:             value = attr
Laurent@814:         try:
Laurent@814:             # TODO: permit to write value like 1E2
Laurent@814:             value = int(value)
andrej@1780:         except Exception:
Laurent@814:             raise ValueError("\"%s\" isn't a valid integer!" % value)
Laurent@814:         if minInclusive is not None and value < minInclusive:
andrej@1764:             raise ValueError("\"%d\" isn't greater or equal to %d!" %
Laurent@814:                              (value, minInclusive))
Laurent@814:         if maxInclusive is not None and value > maxInclusive:
andrej@1764:             raise ValueError("\"%d\" isn't lesser or equal to %d!" %
Laurent@814:                              (value, maxInclusive))
Laurent@814:         if minExclusive is not None and value <= minExclusive:
andrej@1764:             raise ValueError("\"%d\" isn't greater than %d!" %
Laurent@814:                              (value, minExclusive))
Laurent@814:         if maxExclusive is not None and value >= maxExclusive:
andrej@1764:             raise ValueError("\"%d\" isn't lesser than %d!" %
Laurent@814:                              (value, maxExclusive))
Laurent@814:         return value
Laurent@814:     return GetInteger
Laurent@814: 
Laurent@814: 
andrej@1852: def GenerateFloatExtraction(type, extra_values=None):
Laurent@814:     """
Laurent@814:     Function that generates an extraction function for float
Laurent@814:     @param type: name of the type of float
Laurent@814:     @return: function generated
Laurent@814:     """
andrej@1852:     extra_values = [] if extra_values is None else extra_values
andrej@1852: 
andrej@1744:     def GetFloat(attr, extract=True):
Laurent@814:         """
Laurent@814:         Function that extracts a float from a tree node or a string
Laurent@814:         @param attr: tree node containing data to extract or data as a string
Laurent@814:         @param extract: attr is a tree node or not
Laurent@814:         @return: data as a float
Laurent@814:         """
Laurent@814:         if extract:
Laurent@814:             value = GetAttributeValue(attr)
Laurent@814:         else:
Laurent@814:             value = attr
Laurent@814:         if value in extra_values:
Laurent@814:             return value
Laurent@814:         try:
Laurent@814:             return float(value)
andrej@1780:         except Exception:
Laurent@814:             raise ValueError("\"%s\" isn't a valid %s!" % (value, type))
Laurent@814:     return GetFloat
Laurent@814: 
Laurent@814: 
Laurent@814: def GetBoolean(attr, extract=True):
Laurent@814:     """
Laurent@814:     Function that extracts a boolean from a tree node or a string
Laurent@814:     @param attr: tree node containing data to extract or data as a string
Laurent@814:     @param extract: attr is a tree node or not
Laurent@814:     @return: data as a boolean
Laurent@814:     """
Laurent@814:     if extract:
Laurent@814:         value = GetAttributeValue(attr)
Laurent@814:     else:
Laurent@814:         value = attr
Laurent@814:     if value == "true" or value == "1":
Laurent@814:         return True
Laurent@814:     elif value == "false" or value == "0":
Laurent@814:         return False
Laurent@814:     else:
Laurent@814:         raise ValueError("\"%s\" isn't a valid boolean!" % value)
Laurent@814: 
Laurent@814: 
Laurent@814: def GetTime(attr, extract=True):
Laurent@814:     """
Laurent@814:     Function that extracts a time from a tree node or a string
Laurent@814:     @param attr: tree node containing data to extract or data as a string
Laurent@814:     @param extract: attr is a tree node or not
Laurent@814:     @return: data as a time
Laurent@814:     """
Laurent@814:     if extract:
Laurent@814:         value = GetAttributeValue(attr)
Laurent@814:     else:
Laurent@814:         value = attr
Laurent@814:     result = time_model.match(value)
Laurent@814:     if result:
Laurent@814:         values = result.groups()
Laurent@814:         time_values = [int(v) for v in values[:2]]
Laurent@814:         seconds = float(values[2])
Laurent@814:         time_values.extend([int(seconds), int((seconds % 1) * 1000000)])
Laurent@814:         return datetime.time(*time_values)
Laurent@814:     else:
Laurent@814:         raise ValueError("\"%s\" isn't a valid time!" % value)
Laurent@814: 
Laurent@814: 
Laurent@814: def GetDate(attr, extract=True):
Laurent@814:     """
Laurent@814:     Function that extracts a date from a tree node or a string
Laurent@814:     @param attr: tree node containing data to extract or data as a string
Laurent@814:     @param extract: attr is a tree node or not
Laurent@814:     @return: data as a date
Laurent@814:     """
Laurent@814:     if extract:
Laurent@814:         value = GetAttributeValue(attr)
Laurent@814:     else:
Laurent@814:         value = attr
Laurent@814:     result = date_model.match(value)
Laurent@814:     if result:
Laurent@814:         values = result.groups()
Laurent@814:         date_values = [int(v) for v in values[:3]]
Laurent@814:         if values[3] is not None:
Laurent@814:             tz = xml_timezone()
Laurent@814:             tz.SetOffset(values[3])
Laurent@814:             date_values.append(tz)
Laurent@814:         return datetime.date(*date_values)
Laurent@814:     else:
Laurent@814:         raise ValueError("\"%s\" isn't a valid date!" % value)
Laurent@814: 
Laurent@814: 
Laurent@814: def GetDateTime(attr, extract=True):
Laurent@814:     """
Laurent@814:     Function that extracts date and time from a tree node or a string
Laurent@814:     @param attr: tree node containing data to extract or data as a string
Laurent@814:     @param extract: attr is a tree node or not
Laurent@814:     @return: data as date and time
Laurent@814:     """
Laurent@814:     if extract:
Laurent@814:         value = GetAttributeValue(attr)
Laurent@814:     else:
Laurent@814:         value = attr
Laurent@814:     result = datetime_model.match(value)
Laurent@814:     if result:
Laurent@814:         values = result.groups()
Laurent@814:         datetime_values = [int(v) for v in values[:5]]
Laurent@814:         seconds = float(values[5])
Laurent@814:         datetime_values.extend([int(seconds), int((seconds % 1) * 1000000)])
Laurent@814:         if values[6] is not None:
Laurent@814:             tz = xml_timezone()
Laurent@814:             tz.SetOffset(values[6])
Laurent@814:             datetime_values.append(tz)
Laurent@814:         return datetime.datetime(*datetime_values)
Laurent@814:     else:
Laurent@814:         raise ValueError("\"%s\" isn't a valid datetime!" % value)
Laurent@814: 
Laurent@814: 
Laurent@814: def GenerateModelNameExtraction(type, model):
Laurent@814:     """
Laurent@814:     Function that generates an extraction function for string matching a model
Laurent@814:     @param type: name of the data type
Laurent@814:     @param model: model that data must match
Laurent@814:     @return: function generated
Laurent@814:     """
Laurent@814:     def GetModelName(attr, extract=True):
Laurent@814:         """
Laurent@814:         Function that extracts a string from a tree node or not and check that
Laurent@814:         string extracted or given match the model
Laurent@814:         @param attr: tree node containing data to extract or data as a string
Laurent@814:         @param extract: attr is a tree node or not
Laurent@814:         @return: data as a string if matching
Laurent@814:         """
Laurent@814:         if extract:
Laurent@814:             value = GetAttributeValue(attr)
Laurent@814:         else:
Laurent@814:             value = attr
Laurent@814:         result = model.match(value)
Laurent@814:         if not result:
Laurent@814:             raise ValueError("\"%s\" isn't a valid %s!" % (value, type))
Laurent@814:         return value
Laurent@814:     return GetModelName
Laurent@814: 
Laurent@814: 
Laurent@814: def GenerateLimitExtraction(min=None, max=None, unbounded=True):
Laurent@814:     """
Laurent@814:     Function that generates an extraction function for integer defining min and
Laurent@814:     max of integer value
Laurent@814:     @param min: minimum limit value
Laurent@814:     @param max: maximum limit value
Laurent@814:     @param unbounded: value can be "unbounded" or not
Laurent@814:     @return: function generated
Laurent@814:     """
Laurent@814:     def GetLimit(attr, extract=True):
Laurent@814:         """
Laurent@814:         Function that extracts a string from a tree node or not and check that
Laurent@814:         string extracted or given is in a list of values
Laurent@814:         @param attr: tree node containing data to extract or data as a string
Laurent@814:         @param extract: attr is a tree node or not
Laurent@814:         @return: data as a string
Laurent@814:         """
Laurent@814:         if extract:
Laurent@814:             value = GetAttributeValue(attr)
Laurent@814:         else:
Laurent@814:             value = attr
Laurent@814:         if value == "unbounded":
Laurent@814:             if unbounded:
Laurent@814:                 return value
Laurent@814:             else:
Laurent@814:                 raise ValueError("Member limit can't be defined to \"unbounded\"!")
Laurent@814:         try:
Laurent@814:             limit = int(value)
andrej@1780:         except Exception:
Laurent@814:             raise ValueError("\"%s\" isn't a valid value for this member limit!" % value)
Laurent@814:         if limit < 0:
Laurent@814:             raise ValueError("Member limit can't be negative!")
Laurent@814:         elif min is not None and limit < min:
Laurent@814:             raise ValueError("Member limit can't be lower than \"%d\"!" % min)
Laurent@814:         elif max is not None and limit > max:
Laurent@814:             raise ValueError("Member limit can't be upper than \"%d\"!" % max)
Laurent@814:         return limit
Laurent@814:     return GetLimit
Laurent@814: 
Laurent@814: 
Laurent@814: def GenerateEnumeratedExtraction(type, list):
Laurent@814:     """
Laurent@814:     Function that generates an extraction function for enumerated values
Laurent@814:     @param type: name of the data type
Laurent@814:     @param list: list of possible values
Laurent@814:     @return: function generated
Laurent@814:     """
Laurent@814:     def GetEnumerated(attr, extract=True):
Laurent@814:         """
Laurent@814:         Function that extracts a string from a tree node or not and check that
Laurent@814:         string extracted or given is in a list of values
Laurent@814:         @param attr: tree node containing data to extract or data as a string
Laurent@814:         @param extract: attr is a tree node or not
Laurent@814:         @return: data as a string
Laurent@814:         """
Laurent@814:         if extract:
Laurent@814:             value = GetAttributeValue(attr)
Laurent@814:         else:
Laurent@814:             value = attr
Laurent@814:         if value in list:
Laurent@814:             return value
Laurent@814:         else:
andrej@1764:             raise ValueError(
andrej@1764:                 "\"%s\" isn't a valid value for %s!" % (value, type))
Laurent@814:     return GetEnumerated
Laurent@814: 
Laurent@814: 
Laurent@814: def GetNamespaces(attr, extract=True):
Laurent@814:     """
Laurent@814:     Function that extracts a list of namespaces from a tree node or a string
Laurent@814:     @param attr: tree node containing data to extract or data as a string
Laurent@814:     @param extract: attr is a tree node or not
Laurent@814:     @return: list of namespaces
Laurent@814:     """
Laurent@814:     if extract:
Laurent@814:         value = GetAttributeValue(attr)
Laurent@814:     else:
Laurent@814:         value = attr
Laurent@814:     if value == "":
Laurent@814:         return []
Laurent@814:     elif value == "##any" or value == "##other":
Laurent@814:         namespaces = [value]
Laurent@814:     else:
Laurent@814:         namespaces = []
Laurent@814:         for item in value.split(" "):
Laurent@814:             if item == "##targetNamespace" or item == "##local":
Laurent@814:                 namespaces.append(item)
Laurent@814:             else:
Laurent@814:                 result = URI_model.match(item)
Laurent@814:                 if result is not None:
Laurent@814:                     namespaces.append(item)
Laurent@814:                 else:
Laurent@814:                     raise ValueError("\"%s\" isn't a valid value for namespace!" % value)
Laurent@814:     return namespaces
Laurent@814: 
Laurent@814: 
Laurent@814: def GenerateGetList(type, list):
Laurent@814:     """
Laurent@814:     Function that generates an extraction function for a list of values
Laurent@814:     @param type: name of the data type
Laurent@814:     @param list: list of possible values
Laurent@814:     @return: function generated
Laurent@814:     """
Laurent@814:     def GetLists(attr, extract=True):
Laurent@814:         """
Laurent@814:         Function that extracts a list of values from a tree node or a string
Laurent@814:         @param attr: tree node containing data to extract or data as a string
Laurent@814:         @param extract: attr is a tree node or not
Laurent@814:         @return: list of values
Laurent@814:         """
Laurent@814:         if extract:
Laurent@814:             value = GetAttributeValue(attr)
Laurent@814:         else:
Laurent@814:             value = attr
Laurent@814:         if value == "":
Laurent@814:             return []
Laurent@814:         elif value == "#all":
Laurent@814:             return [value]
Laurent@814:         else:
Laurent@814:             values = []
Laurent@814:             for item in value.split(" "):
Laurent@814:                 if item in list:
Laurent@814:                     values.append(item)
Laurent@814:                 else:
andrej@1764:                     raise ValueError(
andrej@1764:                         "\"%s\" isn't a valid value for %s!" % (value, type))
Laurent@814:             return values
Laurent@814:     return GetLists
Laurent@814: 
Laurent@814: 
Laurent@814: def GenerateModelNameListExtraction(type, model):
Laurent@814:     """
Laurent@814:     Function that generates an extraction function for list of string matching
Laurent@814:     a model
Laurent@814:     @param type: name of the data type
Laurent@814:     @param model: model that list elements must match
Laurent@814:     @return: function generated
Laurent@814:     """
Laurent@814:     def GetModelNameList(attr, extract=True):
Laurent@814:         """
Laurent@814:         Function that extracts a list of string from a tree node or not and
Laurent@814:         check that all extracted items match the model
Laurent@814:         @param attr: tree node containing data to extract or data as a string
Laurent@814:         @param extract: attr is a tree node or not
andrej@1730:         @return: data as a list of string if matching
Laurent@814:         """
Laurent@814:         if extract:
Laurent@814:             value = GetAttributeValue(attr)
Laurent@814:         else:
Laurent@814:             value = attr
Laurent@814:         values = []
Laurent@814:         for item in value.split(" "):
Laurent@814:             result = model.match(item)
Laurent@814:             if result is not None:
Laurent@814:                 values.append(item)
Laurent@814:             else:
andrej@1764:                 raise ValueError("\"%s\" isn't a valid value for %s!" % (value, type))
Laurent@814:         return values
Laurent@814:     return GetModelNameList
Laurent@814: 
andrej@1736: 
Laurent@814: def GenerateAnyInfos(infos):
andrej@1730: 
Laurent@1294:     def GetTextElement(tree):
Laurent@1294:         if infos["namespace"][0] == "##any":
Laurent@1294:             return tree.xpath("p")[0]
Laurent@1294:         return tree.xpath("ns:p", namespaces={"ns": infos["namespace"][0]})[0]
andrej@1730: 
Laurent@814:     def ExtractAny(tree):
Laurent@1294:         return GetTextElement(tree).text
andrej@1730: 
Laurent@1291:     def GenerateAny(tree, value):
Laurent@1294:         GetTextElement(tree).text = etree.CDATA(value)
andrej@1730: 
Laurent@1291:     def InitialAny():
Laurent@1291:         if infos["namespace"][0] == "##any":
Laurent@1291:             element_name = "p"
Laurent@1291:         else:
Laurent@1291:             element_name = "{%s}p" % infos["namespace"][0]
Laurent@1293:         p = etree.Element(element_name)
Laurent@1293:         p.text = etree.CDATA("")
Laurent@1293:         return p
andrej@1730: 
Laurent@814:     return {
andrej@1730:         "type": COMPLEXTYPE,
Laurent@814:         "extract": ExtractAny,
Laurent@814:         "generate": GenerateAny,
Laurent@1293:         "initial": InitialAny,
andrej@2450:         "check": lambda x: isinstance(x, (string_types, etree.ElementBase))
Laurent@814:     }
Laurent@814: 
andrej@1736: 
Laurent@814: def GenerateTagInfos(infos):
Laurent@814:     def ExtractTag(tree):
Laurent@814:         if len(tree._attrs) > 0:
Laurent@814:             raise ValueError("\"%s\" musn't have attributes!" % infos["name"])
Laurent@814:         if len(tree.childNodes) > 0:
Laurent@814:             raise ValueError("\"%s\" musn't have children!" % infos["name"])
Laurent@814:         if infos["minOccurs"] == 0:
Laurent@814:             return True
Laurent@814:         else:
Laurent@814:             return None
andrej@1730: 
Laurent@814:     def GenerateTag(value, name=None, indent=0):
Laurent@814:         if name is not None and not (infos["minOccurs"] == 0 and value is None):
andrej@1847:             ind1, _ind2 = getIndent(indent, name)
Laurent@814:             return ind1 + "<%s/>\n" % name
Laurent@814:         else:
Laurent@814:             return ""
andrej@1730: 
Laurent@814:     return {
andrej@1730:         "type": TAG,
Laurent@814:         "extract": ExtractTag,
Laurent@814:         "generate": GenerateTag,
Laurent@814:         "initial": lambda: None,
andrej@1872:         "check": lambda x: x is None or infos["minOccurs"] == 0 and x
Laurent@814:     }
Laurent@814: 
andrej@1736: 
Laurent@814: def FindTypeInfos(factory, infos):
andrej@2450:     if isinstance(infos, string_types):
Laurent@814:         namespace, name = DecomposeQualifiedName(infos)
Laurent@814:         return factory.GetQualifiedNameInfos(name, namespace)
Laurent@814:     return infos
andrej@1730: 
andrej@1736: 
Laurent@814: def GetElementInitialValue(factory, infos):
Laurent@814:     infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
Laurent@1290:     if infos["minOccurs"] == 1:
Laurent@1290:         element_name = factory.etreeNamespaceFormat % infos["name"]
Laurent@1293:         if infos["elmt_type"]["type"] == SIMPLETYPE:
Laurent@1290:             def initial_value():
andrej@2297:                 value = factory.Parser.makeelement(element_name)
Laurent@1290:                 value.text = (infos["elmt_type"]["generate"](infos["elmt_type"]["initial"]()))
andrej@2297:                 value._init_()
Laurent@1290:                 return value
Laurent@1290:         else:
Laurent@1290:             def initial_value():
Laurent@1290:                 value = infos["elmt_type"]["initial"]()
Laurent@1293:                 if infos["type"] != ANY:
Laurent@1293:                     DefaultElementClass.__setattr__(value, "tag", element_name)
Laurent@1290:                 return value
andrej@1847:         return [initial_value() for dummy in xrange(infos["minOccurs"])]
Laurent@814:     else:
Laurent@1290:         return []
Laurent@814: 
andrej@1736: 
Laurent@814: def GetContentInfos(name, choices):
Laurent@814:     for choice_infos in choices:
andrej@1872:         if choice_infos["type"] == "sequence":
andrej@1872:             for element_infos in choice_infos["elements"]:
Laurent@814:                 if element_infos["type"] == CHOICE:
Laurent@814:                     if GetContentInfos(name, element_infos["choices"]):
andrej@1872:                         return choice_infos
Laurent@814:                 elif element_infos["name"] == name:
andrej@1872:                     return choice_infos
Laurent@814:         elif choice_infos["name"] == name:
andrej@1872:             return choice_infos
Laurent@814:     return None
Laurent@814: 
andrej@1736: 
Laurent@814: def ComputeContentChoices(factory, name, infos):
Laurent@814:     choices = []
Laurent@814:     for choice in infos["choices"]:
Laurent@814:         if choice["type"] == "sequence":
Laurent@814:             choice["name"] = "sequence"
Laurent@814:             for sequence_element in choice["elements"]:
Laurent@814:                 if sequence_element["type"] != CHOICE:
Laurent@814:                     element_infos = factory.ExtractTypeInfos(sequence_element["name"], name, sequence_element["elmt_type"])
Laurent@814:                     if element_infos is not None:
Laurent@814:                         sequence_element["elmt_type"] = element_infos
Laurent@814:         elif choice["elmt_type"] == "tag":
Laurent@814:             choice["elmt_type"] = GenerateTagInfos(choice)
Laurent@1291:             factory.AddToLookupClass(choice["name"], name, DefaultElementClass)
Laurent@814:         else:
Laurent@814:             choice_infos = factory.ExtractTypeInfos(choice["name"], name, choice["elmt_type"])
Laurent@814:             if choice_infos is not None:
Laurent@814:                 choice["elmt_type"] = choice_infos
Laurent@814:         choices.append((choice["name"], choice))
Laurent@814:     return choices
Laurent@814: 
andrej@1736: 
Laurent@814: def GenerateContentInfos(factory, name, choices):
Laurent@814:     choices_dict = {}
Laurent@814:     for choice_name, infos in choices:
Laurent@814:         if choice_name == "sequence":
Laurent@814:             for element in infos["elements"]:
Laurent@814:                 if element["type"] == CHOICE:
Laurent@814:                     element["elmt_type"] = GenerateContentInfos(factory, name, ComputeContentChoices(factory, name, element))
andrej@1763:                 elif element["name"] in choices_dict:
Laurent@814:                     raise ValueError("'%s' element defined two times in choice" % choice_name)
Laurent@814:                 else:
Laurent@814:                     choices_dict[element["name"]] = infos
Laurent@814:         else:
andrej@1763:             if choice_name in choices_dict:
Laurent@814:                 raise ValueError("'%s' element defined two times in choice" % choice_name)
Laurent@814:             choices_dict[choice_name] = infos
Laurent@1315:     prefix = ("%s:" % factory.TargetNamespace
Laurent@1315:               if factory.TargetNamespace is not None else "")
Laurent@1315:     choices_xpath = "|".join(map(lambda x: prefix + x, choices_dict.keys()))
andrej@1730: 
Laurent@814:     def GetContentInitial():
Laurent@814:         content_name, infos = choices[0]
Laurent@814:         if content_name == "sequence":
Laurent@814:             content_value = []
andrej@1847:             for dummy in xrange(infos["minOccurs"]):
Laurent@814:                 for element_infos in infos["elements"]:
Laurent@1305:                     content_value.extend(GetElementInitialValue(factory, element_infos))
Laurent@814:         else:
Laurent@814:             content_value = GetElementInitialValue(factory, infos)
Laurent@1305:         return content_value
andrej@1730: 
Laurent@814:     return {
Laurent@814:         "type": COMPLEXTYPE,
Laurent@1305:         "choices_xpath": etree.XPath(choices_xpath, namespaces=factory.NSMAP),
Laurent@814:         "initial": GetContentInitial,
Laurent@814:     }
Laurent@814: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                           Structure extraction functions
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
Laurent@814: 
Laurent@814: def DecomposeQualifiedName(name):
Laurent@814:     result = QName_model.match(name)
Laurent@814:     if not result:
andrej@1730:         raise ValueError("\"%s\" isn't a valid QName value!" % name)
Laurent@814:     parts = result.groups()[0].split(':')
Laurent@814:     if len(parts) == 1:
Laurent@814:         return None, parts[0]
Laurent@814:     return parts
andrej@1730: 
andrej@1736: 
andrej@1730: def GenerateElement(element_name, attributes, elements_model,
Laurent@814:                     accept_text=False):
Laurent@814:     def ExtractElement(factory, node):
Laurent@814:         attrs = factory.ExtractNodeAttrs(element_name, node, attributes)
Laurent@814:         children_structure = ""
Laurent@814:         children = []
Laurent@814:         for child in node.childNodes:
Laurent@814:             if child.nodeName not in ["#comment", "#text"]:
Laurent@814:                 namespace, childname = DecomposeQualifiedName(child.nodeName)
andrej@1734:                 children_structure += "%s " % childname
Laurent@814:         result = elements_model.match(children_structure)
Laurent@814:         if not result:
Laurent@814:             raise ValueError("Invalid structure for \"%s\" children!. First element invalid." % node.nodeName)
Laurent@814:         valid = result.groups()[0]
Laurent@814:         if len(valid) < len(children_structure):
Laurent@814:             raise ValueError("Invalid structure for \"%s\" children!. Element number %d invalid." % (node.nodeName, len(valid.split(" ")) - 1))
Laurent@814:         for child in node.childNodes:
Laurent@814:             if child.nodeName != "#comment" and \
Laurent@814:                (accept_text or child.nodeName != "#text"):
Laurent@814:                 if child.nodeName == "#text":
Laurent@814:                     children.append(GetAttributeValue(node))
Laurent@814:                 else:
Laurent@814:                     namespace, childname = DecomposeQualifiedName(child.nodeName)
Laurent@814:                     infos = factory.GetQualifiedNameInfos(childname, namespace)
Laurent@814:                     if infos["type"] != SYNTAXELEMENT:
andrej@1872:                         raise ValueError("\"%s\" can't be a member child!" % childname)
andrej@1763:                     if element_name in infos["extract"]:
Laurent@814:                         children.append(infos["extract"][element_name](factory, child))
Laurent@814:                     else:
Laurent@814:                         children.append(infos["extract"]["default"](factory, child))
Laurent@814:         return node.nodeName, attrs, children
Laurent@814:     return ExtractElement
Laurent@814: 
Laurent@814: 
andrej@1831: class ClassFactory(object):
andrej@1736:     """
andrej@1736:     Class that generate class from an XML Tree
andrej@1736:     """
Laurent@814: 
Laurent@814:     def __init__(self, document, filepath=None, debug=False):
Laurent@814:         self.Document = document
Laurent@814:         if filepath is not None:
Laurent@814:             self.BaseFolder, self.FileName = os.path.split(filepath)
Laurent@814:         else:
Laurent@814:             self.BaseFolder = self.FileName = None
Laurent@814:         self.Debug = debug
andrej@1730: 
Laurent@814:         # Dictionary for stocking Classes and Types definitions created from
Laurent@814:         # the XML tree
Laurent@814:         self.XMLClassDefinitions = {}
andrej@1730: 
Laurent@814:         self.DefinedNamespaces = {}
Laurent@1290:         self.NSMAP = {}
Laurent@814:         self.Namespaces = {}
Laurent@814:         self.SchemaNamespace = None
Laurent@814:         self.TargetNamespace = None
Laurent@1290:         self.etreeNamespaceFormat = "%s"
andrej@2297:         self.Parser = None
andrej@1730: 
Laurent@814:         self.CurrentCompilations = []
andrej@1730: 
Laurent@814:         # Dictionaries for stocking Classes and Types generated
Laurent@814:         self.ComputeAfter = []
Laurent@814:         if self.FileName is not None:
Laurent@814:             self.ComputedClasses = {self.FileName: {}}
Laurent@814:         else:
Laurent@814:             self.ComputedClasses = {}
Laurent@814:         self.ComputedClassesInfos = {}
Laurent@1290:         self.ComputedClassesLookUp = {}
Laurent@1290:         self.EquivalentClassesParent = {}
Laurent@814:         self.AlreadyComputed = {}
Laurent@814: 
Laurent@814:     def GetQualifiedNameInfos(self, name, namespace=None, canbenone=False):
Laurent@814:         if namespace is None:
andrej@1763:             if name in self.Namespaces[self.SchemaNamespace]:
Laurent@814:                 return self.Namespaces[self.SchemaNamespace][name]
Laurent@814:             for space, elements in self.Namespaces.iteritems():
andrej@1763:                 if space != self.SchemaNamespace and name in elements:
Laurent@814:                     return elements[name]
Laurent@814:             parts = name.split("_", 1)
Laurent@814:             if len(parts) > 1:
Laurent@814:                 group = self.GetQualifiedNameInfos(parts[0], namespace)
Laurent@814:                 if group is not None and group["type"] == ELEMENTSGROUP:
Laurent@814:                     elements = []
andrej@1763:                     if "elements" in group:
Laurent@814:                         elements = group["elements"]
andrej@1763:                     elif "choices" in group:
Laurent@814:                         elements = group["choices"]
Laurent@814:                     for element in elements:
Laurent@814:                         if element["name"] == parts[1]:
Laurent@814:                             return element
Laurent@814:             if not canbenone:
Laurent@814:                 raise ValueError("Unknown element \"%s\" for any defined namespaces!" % name)
andrej@1763:         elif namespace in self.Namespaces:
andrej@1763:             if name in self.Namespaces[namespace]:
Laurent@814:                 return self.Namespaces[namespace][name]
Laurent@814:             parts = name.split("_", 1)
Laurent@814:             if len(parts) > 1:
Laurent@814:                 group = self.GetQualifiedNameInfos(parts[0], namespace)
Laurent@814:                 if group is not None and group["type"] == ELEMENTSGROUP:
Laurent@814:                     elements = []
andrej@1763:                     if "elements" in group:
Laurent@814:                         elements = group["elements"]
andrej@1763:                     elif "choices" in group:
Laurent@814:                         elements = group["choices"]
Laurent@814:                     for element in elements:
Laurent@814:                         if element["name"] == parts[1]:
Laurent@814:                             return element
Laurent@814:             if not canbenone:
Laurent@814:                 raise ValueError("Unknown element \"%s\" for namespace \"%s\"!" % (name, namespace))
Laurent@814:         elif not canbenone:
Laurent@814:             raise ValueError("Unknown namespace \"%s\"!" % namespace)
Laurent@814:         return None
Laurent@814: 
Laurent@814:     def SplitQualifiedName(self, name, namespace=None, canbenone=False):
Laurent@814:         if namespace is None:
andrej@1763:             if name in self.Namespaces[self.SchemaNamespace]:
Laurent@814:                 return name, None
Laurent@814:             for space, elements in self.Namespaces.items():
andrej@1763:                 if space != self.SchemaNamespace and name in elements:
Laurent@814:                     return name, None
Laurent@814:             parts = name.split("_", 1)
Laurent@814:             if len(parts) > 1:
Laurent@814:                 group = self.GetQualifiedNameInfos(parts[0], namespace)
Laurent@814:                 if group is not None and group["type"] == ELEMENTSGROUP:
Laurent@814:                     elements = []
andrej@1763:                     if "elements" in group:
Laurent@814:                         elements = group["elements"]
andrej@1763:                     elif "choices" in group:
Laurent@814:                         elements = group["choices"]
Laurent@814:                     for element in elements:
Laurent@814:                         if element["name"] == parts[1]:
andrej@1872:                             return parts[1], parts[0]
Laurent@814:             if not canbenone:
Laurent@814:                 raise ValueError("Unknown element \"%s\" for any defined namespaces!" % name)
andrej@1763:         elif namespace in self.Namespaces:
andrej@1763:             if name in self.Namespaces[namespace]:
Laurent@814:                 return name, None
Laurent@814:             parts = name.split("_", 1)
Laurent@814:             if len(parts) > 1:
Laurent@814:                 group = self.GetQualifiedNameInfos(parts[0], namespace)
Laurent@814:                 if group is not None and group["type"] == ELEMENTSGROUP:
Laurent@814:                     elements = []
andrej@1763:                     if "elements" in group:
Laurent@814:                         elements = group["elements"]
andrej@1763:                     elif "choices" in group:
Laurent@814:                         elements = group["choices"]
Laurent@814:                     for element in elements:
Laurent@814:                         if element["name"] == parts[1]:
Laurent@814:                             return parts[1], parts[0]
Laurent@814:             if not canbenone:
Laurent@814:                 raise ValueError("Unknown element \"%s\" for namespace \"%s\"!" % (name, namespace))
Laurent@814:         elif not canbenone:
Laurent@814:             raise ValueError("Unknown namespace \"%s\"!" % namespace)
Laurent@814:         return None, None
Laurent@814: 
Laurent@814:     def ExtractNodeAttrs(self, element_name, node, valid_attrs):
Laurent@814:         attrs = {}
Laurent@814:         for qualified_name, attr in node._attrs.items():
andrej@1758:             namespace, name = DecomposeQualifiedName(qualified_name)
Laurent@814:             if name in valid_attrs:
Laurent@814:                 infos = self.GetQualifiedNameInfos(name, namespace)
Laurent@814:                 if infos["type"] != SYNTAXATTRIBUTE:
Laurent@814:                     raise ValueError("\"%s\" can't be a member attribute!" % name)
Laurent@814:                 elif name in attrs:
Laurent@814:                     raise ValueError("\"%s\" attribute has been twice!" % name)
Laurent@814:                 elif element_name in infos["extract"]:
Laurent@814:                     attrs[name] = infos["extract"][element_name](attr)
Laurent@814:                 else:
Laurent@814:                     attrs[name] = infos["extract"]["default"](attr)
Laurent@814:             elif namespace == "xmlns":
Laurent@814:                 infos = self.GetQualifiedNameInfos("anyURI", self.SchemaNamespace)
Laurent@1290:                 value = infos["extract"](attr)
Laurent@1290:                 self.DefinedNamespaces[value] = name
Laurent@1290:                 self.NSMAP[name] = value
Laurent@814:             else:
Laurent@814:                 raise ValueError("Invalid attribute \"%s\" for member \"%s\"!" % (qualified_name, node.nodeName))
Laurent@814:         for attr in valid_attrs:
Laurent@814:             if attr not in attrs and \
andrej@1774:                attr in self.Namespaces[self.SchemaNamespace] and \
andrej@1763:                "default" in self.Namespaces[self.SchemaNamespace][attr]:
andrej@1763:                 if element_name in self.Namespaces[self.SchemaNamespace][attr]["default"]:
Laurent@814:                     default = self.Namespaces[self.SchemaNamespace][attr]["default"][element_name]
Laurent@814:                 else:
Laurent@814:                     default = self.Namespaces[self.SchemaNamespace][attr]["default"]["default"]
Laurent@814:                 if default is not None:
Laurent@814:                     attrs[attr] = default
Laurent@814:         return attrs
Laurent@814: 
Laurent@814:     def ReduceElements(self, elements, schema=False):
Laurent@814:         result = []
Laurent@814:         for child_infos in elements:
Laurent@814:             if child_infos is not None:
andrej@1763:                 if "name" in child_infos[1] and schema:
Laurent@814:                     self.CurrentCompilations.append(child_infos[1]["name"])
Laurent@814:                 namespace, name = DecomposeQualifiedName(child_infos[0])
Laurent@814:                 infos = self.GetQualifiedNameInfos(name, namespace)
Laurent@814:                 if infos["type"] != SYNTAXELEMENT:
Laurent@814:                     raise ValueError("\"%s\" can't be a member child!" % name)
Laurent@814:                 element = infos["reduce"](self, child_infos[1], child_infos[2])
Laurent@814:                 if element is not None:
Laurent@814:                     result.append(element)
andrej@1763:                 if "name" in child_infos[1] and schema:
Laurent@814:                     self.CurrentCompilations.pop(-1)
Laurent@814:         annotations = []
Laurent@814:         children = []
Laurent@814:         for element in result:
Laurent@814:             if element["type"] == "annotation":
Laurent@814:                 annotations.append(element)
Laurent@814:             else:
Laurent@814:                 children.append(element)
Laurent@814:         return annotations, children
Laurent@814: 
Laurent@814:     def AddComplexType(self, typename, infos):
andrej@1775:         if typename not in self.XMLClassDefinitions:
Laurent@814:             self.XMLClassDefinitions[typename] = infos
Laurent@814:         else:
Laurent@814:             raise ValueError("\"%s\" class already defined. Choose another name!" % typename)
Laurent@814: 
Laurent@814:     def ParseSchema(self):
Laurent@814:         pass
andrej@1730: 
Laurent@1290:     def AddEquivalentClass(self, name, base):
Laurent@1291:         if name != base:
Laurent@1291:             equivalences = self.EquivalentClassesParent.setdefault(self.etreeNamespaceFormat % base, {})
Laurent@1291:             equivalences[self.etreeNamespaceFormat % name] = True
andrej@1730: 
Laurent@1322:     def AddDistinctionBetweenParentsInLookupClass(
andrej@1878:             self, lookup_classes, parent, typeinfos):
andrej@1730:         parent = (self.etreeNamespaceFormat % parent
Laurent@1322:                   if parent is not None else None)
Laurent@1322:         parent_class = lookup_classes.get(parent)
Laurent@1322:         if parent_class is not None:
andrej@2450:             if isinstance(parent_class, list):
Laurent@1322:                 if typeinfos not in parent_class:
Laurent@1322:                     lookup_classes[parent].append(typeinfos)
Laurent@1322:             elif parent_class != typeinfos:
Edouard@1405:                 lookup_classes[parent] = [typeinfos, parent_class]
Laurent@1322:         else:
Laurent@1322:             lookup_classes[parent] = typeinfos
andrej@1730: 
Laurent@1290:     def AddToLookupClass(self, name, parent, typeinfos):
Laurent@1290:         lookup_name = self.etreeNamespaceFormat % name
andrej@2450:         if isinstance(typeinfos, string_types):
Laurent@1290:             self.AddEquivalentClass(name, typeinfos)
Laurent@1290:             typeinfos = self.etreeNamespaceFormat % typeinfos
Laurent@1290:         lookup_classes = self.ComputedClassesLookUp.get(lookup_name)
Laurent@1290:         if lookup_classes is None:
Laurent@1290:             self.ComputedClassesLookUp[lookup_name] = (typeinfos, parent)
andrej@2450:         elif isinstance(lookup_classes, dict):
Laurent@1322:             self.AddDistinctionBetweenParentsInLookupClass(
Laurent@1322:                 lookup_classes, parent, typeinfos)
Laurent@1322:         else:
Laurent@1322:             lookup_classes = {
Laurent@1322:                 self.etreeNamespaceFormat % lookup_classes[1]
Laurent@1322:                 if lookup_classes[1] is not None else None: lookup_classes[0]}
Laurent@1322:             self.AddDistinctionBetweenParentsInLookupClass(
Laurent@1322:                 lookup_classes, parent, typeinfos)
Laurent@1290:             self.ComputedClassesLookUp[lookup_name] = lookup_classes
andrej@1730: 
Laurent@814:     def ExtractTypeInfos(self, name, parent, typeinfos):
andrej@2450:         if isinstance(typeinfos, string_types):
Laurent@1290:             namespace, type_name = DecomposeQualifiedName(typeinfos)
Laurent@1290:             infos = self.GetQualifiedNameInfos(type_name, namespace)
Laurent@1293:             if name != "base":
Laurent@1293:                 if infos["type"] == SIMPLETYPE:
Laurent@1293:                     self.AddToLookupClass(name, parent, DefaultElementClass)
Laurent@1293:                 elif namespace == self.TargetNamespace:
Laurent@1293:                     self.AddToLookupClass(name, parent, type_name)
Laurent@814:             if infos["type"] == COMPLEXTYPE:
Laurent@1290:                 type_name, parent = self.SplitQualifiedName(type_name, namespace)
Laurent@1290:                 result = self.CreateClass(type_name, parent, infos)
andrej@2450:                 if result is not None and not isinstance(result, string_types):
Laurent@814:                     self.Namespaces[self.TargetNamespace][result["name"]] = result
Laurent@814:                 return result
Laurent@814:             elif infos["type"] == ELEMENT and infos["elmt_type"]["type"] == COMPLEXTYPE:
Laurent@1290:                 type_name, parent = self.SplitQualifiedName(type_name, namespace)
Laurent@1290:                 result = self.CreateClass(type_name, parent, infos["elmt_type"])
andrej@2450:                 if result is not None and not isinstance(result, string_types):
Laurent@814:                     self.Namespaces[self.TargetNamespace][result["name"]] = result
Laurent@814:                 return result
Laurent@814:             else:
Laurent@814:                 return infos
Laurent@814:         elif typeinfos["type"] == COMPLEXTYPE:
Laurent@814:             return self.CreateClass(name, parent, typeinfos)
Laurent@814:         elif typeinfos["type"] == SIMPLETYPE:
Laurent@814:             return typeinfos
andrej@1730: 
Laurent@1291:     def GetEquivalentParents(self, parent):
Laurent@1291:         return reduce(lambda x, y: x + y,
andrej@1768:                       [[p] + self.GetEquivalentParents(p)
andrej@1768:                        for p in self.EquivalentClassesParent.get(parent, {}).keys()], [])
andrej@1730: 
Laurent@814:     def CreateClasses(self):
andrej@1837:         """
andrej@1837:         Method that generates the classes
andrej@1837:         """
Laurent@814:         self.ParseSchema()
Laurent@814:         for name, infos in self.Namespaces[self.TargetNamespace].items():
Laurent@814:             if infos["type"] == ELEMENT:
andrej@2450:                 if not isinstance(infos["elmt_type"], string_types) and \
Laurent@814:                    infos["elmt_type"]["type"] == COMPLEXTYPE:
Laurent@814:                     self.ComputeAfter.append((name, None, infos["elmt_type"], True))
Laurent@814:                     while len(self.ComputeAfter) > 0:
Laurent@814:                         result = self.CreateClass(*self.ComputeAfter.pop(0))
andrej@2450:                         if result is not None and not isinstance(result, string_types):
Laurent@814:                             self.Namespaces[self.TargetNamespace][result["name"]] = result
Laurent@814:             elif infos["type"] == COMPLEXTYPE:
Laurent@814:                 self.ComputeAfter.append((name, None, infos))
Laurent@814:                 while len(self.ComputeAfter) > 0:
Laurent@814:                     result = self.CreateClass(*self.ComputeAfter.pop(0))
Laurent@814:                     if result is not None and \
andrej@2450:                        not isinstance(result, string_types):
Laurent@814:                         self.Namespaces[self.TargetNamespace][result["name"]] = result
Laurent@814:             elif infos["type"] == ELEMENTSGROUP:
Laurent@814:                 elements = []
andrej@1763:                 if "elements" in infos:
Laurent@814:                     elements = infos["elements"]
andrej@1763:                 elif "choices" in infos:
Laurent@814:                     elements = infos["choices"]
Laurent@814:                 for element in elements:
andrej@2450:                     if not isinstance(element["elmt_type"], string_types) and \
Laurent@814:                        element["elmt_type"]["type"] == COMPLEXTYPE:
Laurent@814:                         self.ComputeAfter.append((element["name"], infos["name"], element["elmt_type"]))
Laurent@814:                         while len(self.ComputeAfter) > 0:
Laurent@814:                             result = self.CreateClass(*self.ComputeAfter.pop(0))
Laurent@814:                             if result is not None and \
andrej@2450:                                not isinstance(result, string_types):
Laurent@814:                                 self.Namespaces[self.TargetNamespace][result["name"]] = result
andrej@1730: 
Laurent@1291:         for name, parents in self.ComputedClassesLookUp.iteritems():
andrej@2450:             if isinstance(parents, dict):
Laurent@1291:                 computed_classes = parents.items()
Laurent@1291:             elif parents[1] is not None:
Laurent@1291:                 computed_classes = [(self.etreeNamespaceFormat % parents[1], parents[0])]
Laurent@1291:             else:
Laurent@1291:                 computed_classes = []
Laurent@1291:             for parent, computed_class in computed_classes:
Laurent@1291:                 for equivalent_parent in self.GetEquivalentParents(parent):
andrej@2450:                     if not isinstance(parents, dict):
Laurent@1291:                         parents = dict(computed_classes)
Laurent@1291:                         self.ComputedClassesLookUp[name] = parents
Laurent@1291:                     parents[equivalent_parent] = computed_class
andrej@1730: 
Laurent@814:         return self.ComputedClasses
Laurent@814: 
andrej@1744:     def CreateClass(self, name, parent, classinfos, baseclass=False):
Laurent@814:         if parent is not None:
Laurent@814:             classname = "%s_%s" % (parent, name)
Laurent@814:         else:
Laurent@814:             classname = name
andrej@1730: 
Laurent@814:         # Checks that classe haven't been generated yet
Laurent@814:         if self.AlreadyComputed.get(classname, False):
Laurent@814:             return self.ComputedClassesInfos.get(classname, None)
andrej@1730: 
Laurent@814:         # If base classes haven't been generated
Laurent@814:         bases = []
Laurent@814:         base_infos = classinfos.get("base", None)
Laurent@814:         if base_infos is not None:
Laurent@1290:             namespace, base_name = DecomposeQualifiedName(base_infos)
Laurent@1290:             if namespace == self.TargetNamespace:
Laurent@1290:                 self.AddEquivalentClass(name, base_name)
Laurent@814:             result = self.ExtractTypeInfos("base", name, base_infos)
Laurent@814:             if result is None:
Laurent@1290:                 namespace, base_name = DecomposeQualifiedName(base_infos)
Laurent@814:                 if self.AlreadyComputed.get(base_name, False):
Laurent@814:                     self.ComputeAfter.append((name, parent, classinfos))
Laurent@814:                     if self.TargetNamespace is not None:
Laurent@814:                         return "%s:%s" % (self.TargetNamespace, classname)
Laurent@814:                     else:
Laurent@814:                         return classname
Laurent@814:             elif result is not None:
Laurent@814:                 if self.FileName is not None:
Laurent@814:                     classinfos["base"] = self.ComputedClasses[self.FileName].get(result["name"], None)
Laurent@814:                     if classinfos["base"] is None:
Laurent@814:                         for filename, classes in self.ComputedClasses.iteritems():
Laurent@814:                             if filename != self.FileName:
Laurent@814:                                 classinfos["base"] = classes.get(result["name"], None)
Laurent@814:                                 if classinfos["base"] is not None:
Laurent@814:                                     break
Laurent@814:                 else:
Laurent@814:                     classinfos["base"] = self.ComputedClasses.get(result["name"], None)
Laurent@814:                 if classinfos["base"] is None:
Laurent@814:                     raise ValueError("No class found for base type")
Laurent@814:                 bases.append(classinfos["base"])
Laurent@1290:         bases.append(DefaultElementClass)
Laurent@814:         bases = tuple(bases)
Laurent@814:         classmembers = {"__doc__": classinfos.get("doc", ""), "IsBaseClass": baseclass}
andrej@1730: 
Laurent@814:         self.AlreadyComputed[classname] = True
andrej@1730: 
Laurent@814:         for attribute in classinfos["attributes"]:
Laurent@814:             infos = self.ExtractTypeInfos(attribute["name"], name, attribute["attr_type"])
andrej@1730:             if infos is not None:
Laurent@814:                 if infos["type"] != SIMPLETYPE:
Laurent@814:                     raise ValueError("\"%s\" type is not a simple type!" % attribute["attr_type"])
Laurent@814:                 attrname = attribute["name"]
Laurent@814:                 if attribute["use"] == "optional":
andrej@1734:                     classmembers["add%s" % attrname] = generateAddMethod(attrname, self, attribute)
andrej@1734:                     classmembers["delete%s" % attrname] = generateDeleteMethod(attrname)
andrej@1734:                 classmembers["set%s" % attrname] = generateSetMethod(attrname)
andrej@1734:                 classmembers["get%s" % attrname] = generateGetMethod(attrname)
Laurent@814:             else:
Laurent@814:                 raise ValueError("\"%s\" type unrecognized!" % attribute["attr_type"])
Laurent@814:             attribute["attr_type"] = infos
andrej@1730: 
Laurent@814:         for element in classinfos["elements"]:
Laurent@814:             if element["type"] == CHOICE:
Laurent@814:                 elmtname = element["name"]
Laurent@814:                 choices = ComputeContentChoices(self, name, element)
andrej@1734:                 classmembers["get%schoices" % elmtname] = generateGetChoicesMethod(element["choices"])
Laurent@814:                 if element["maxOccurs"] == "unbounded" or element["maxOccurs"] > 1:
Laurent@814:                     classmembers["append%sbytype" % elmtname] = generateAppendChoiceByTypeMethod(element["maxOccurs"], self, element["choices"])
Laurent@814:                     classmembers["insert%sbytype" % elmtname] = generateInsertChoiceByTypeMethod(element["maxOccurs"], self, element["choices"])
Laurent@814:                 else:
Laurent@814:                     classmembers["set%sbytype" % elmtname] = generateSetChoiceByTypeMethod(self, element["choices"])
Laurent@814:                 infos = GenerateContentInfos(self, name, choices)
Laurent@814:             elif element["type"] == ANY:
Laurent@1291:                 elmtname = element["name"] = "anyText"
Laurent@814:                 element["minOccurs"] = element["maxOccurs"] = 1
Laurent@814:                 infos = GenerateAnyInfos(element)
Laurent@814:             else:
Laurent@814:                 elmtname = element["name"]
Laurent@814:                 if element["elmt_type"] == "tag":
Laurent@814:                     infos = GenerateTagInfos(element)
Laurent@1291:                     self.AddToLookupClass(element["name"], name, DefaultElementClass)
Laurent@814:                 else:
Laurent@814:                     infos = self.ExtractTypeInfos(element["name"], name, element["elmt_type"])
Laurent@814:             if infos is not None:
Laurent@814:                 element["elmt_type"] = infos
Laurent@814:             if element["maxOccurs"] == "unbounded" or element["maxOccurs"] > 1:
Laurent@814:                 classmembers["append%s" % elmtname] = generateAppendMethod(elmtname, element["maxOccurs"], self, element)
Laurent@814:                 classmembers["insert%s" % elmtname] = generateInsertMethod(elmtname, element["maxOccurs"], self, element)
Laurent@814:                 classmembers["remove%s" % elmtname] = generateRemoveMethod(elmtname, element["minOccurs"])
Laurent@814:                 classmembers["count%s" % elmtname] = generateCountMethod(elmtname)
Laurent@814:             else:
Laurent@814:                 if element["minOccurs"] == 0:
Laurent@814:                     classmembers["add%s" % elmtname] = generateAddMethod(elmtname, self, element)
Laurent@814:                     classmembers["delete%s" % elmtname] = generateDeleteMethod(elmtname)
Laurent@814:             classmembers["set%s" % elmtname] = generateSetMethod(elmtname)
Laurent@814:             classmembers["get%s" % elmtname] = generateGetMethod(elmtname)
andrej@1730: 
Laurent@1315:         classmembers["_init_"] = generateInitMethod(self, classinfos)
Laurent@1322:         classmembers["StructurePattern"] = GetStructurePattern(classinfos)
Laurent@814:         classmembers["getElementAttributes"] = generateGetElementAttributes(self, classinfos)
Laurent@814:         classmembers["getElementInfos"] = generateGetElementInfos(self, classinfos)
Laurent@814:         classmembers["setElementValue"] = generateSetElementValue(self, classinfos)
andrej@1730: 
andrej@2431:         class_definition = type(str(name), bases, classmembers)
Laurent@1290:         setattr(class_definition, "__getattr__", generateGetattrMethod(self, class_definition, classinfos))
Laurent@814:         setattr(class_definition, "__setattr__", generateSetattrMethod(self, class_definition, classinfos))
andrej@1773:         class_infos = {
andrej@1773:             "type": COMPILEDCOMPLEXTYPE,
andrej@1773:             "name": classname,
andrej@2297:             "initial": generateClassCreateFunction(self, class_definition),
Laurent@1303:         }
Laurent@814:         if self.FileName is not None:
Laurent@814:             self.ComputedClasses[self.FileName][classname] = class_definition
Laurent@814:         else:
Laurent@814:             self.ComputedClasses[classname] = class_definition
Laurent@814:         self.ComputedClassesInfos[classname] = class_infos
andrej@1730: 
Laurent@1290:         self.AddToLookupClass(name, parent, class_definition)
Laurent@1290:         self.AddToLookupClass(classname, None, class_definition)
andrej@1730: 
Laurent@814:         return class_infos
Laurent@814: 
Laurent@814:     def PrintClasses(self):
andrej@1837:         """
andrej@1837:         Method that print the classes generated
andrej@1837:         """
Laurent@814:         items = self.ComputedClasses.items()
Laurent@814:         items.sort()
Laurent@814:         if self.FileName is not None:
Laurent@814:             for filename, classes in items:
andrej@1826:                 print("File '%s':" % filename)
Laurent@814:                 class_items = classes.items()
Laurent@814:                 class_items.sort()
Laurent@814:                 for classname, xmlclass in class_items:
andrej@1826:                     print("%s: %s" % (classname, str(xmlclass)))
Laurent@814:         else:
Laurent@814:             for classname, xmlclass in items:
andrej@1826:                 print("%s: %s" % (classname, str(xmlclass)))
andrej@1730: 
Laurent@814:     def PrintClassNames(self):
Laurent@814:         classnames = self.XMLClassDefinitions.keys()
Laurent@814:         classnames.sort()
Laurent@814:         for classname in classnames:
andrej@1826:             print(classname)
Laurent@814: 
andrej@1736: 
Laurent@1322: def ComputeMultiplicity(name, infos):
andrej@1736:     """
andrej@1736:     Method that generate the method for generating the xml tree structure model by
andrej@1736:     following the attributes list defined
andrej@1736:     """
Laurent@1322:     if infos["minOccurs"] == 0:
Laurent@1322:         if infos["maxOccurs"] == "unbounded":
Laurent@1322:             return "(?:%s)*" % name
Laurent@1322:         elif infos["maxOccurs"] == 1:
Laurent@1322:             return "(?:%s)?" % name
Laurent@1322:         else:
Laurent@1322:             return "(?:%s){,%d}" % (name, infos["maxOccurs"])
Laurent@1322:     elif infos["minOccurs"] == 1:
Laurent@1322:         if infos["maxOccurs"] == "unbounded":
Laurent@1322:             return "(?:%s)+" % name
Laurent@1322:         elif infos["maxOccurs"] == 1:
Laurent@1322:             return "(?:%s)" % name
Laurent@1322:         else:
Laurent@1322:             return "(?:%s){1,%d}" % (name, infos["maxOccurs"])
Laurent@1322:     else:
Laurent@1322:         if infos["maxOccurs"] == "unbounded":
andrej@1860:             return "(?:%s){%d,}" % (name, infos["minOccurs"])
Laurent@1322:         else:
andrej@1767:             return "(?:%s){%d,%d}" % (name,
andrej@1767:                                       infos["minOccurs"],
andrej@1767:                                       infos["maxOccurs"])
Laurent@1322: 
andrej@1736: 
Laurent@1322: def GetStructurePattern(classinfos):
Laurent@1322:     base_structure_pattern = (
Laurent@1322:         classinfos["base"].StructurePattern.pattern[:-1]
andrej@1763:         if "base" in classinfos else "")
Laurent@1322:     elements = []
Laurent@1322:     for element in classinfos["elements"]:
Laurent@1322:         if element["type"] == ANY:
Laurent@1322:             infos = element.copy()
Laurent@1322:             infos["minOccurs"] = 0
andrej@2439:             elements.append(ComputeMultiplicity(r"#text |#cdata-section |\w* ", infos))
Laurent@1322:         elif element["type"] == CHOICE:
Laurent@1322:             choices = []
Laurent@1322:             for infos in element["choices"]:
Laurent@1322:                 if infos["type"] == "sequence":
Laurent@1322:                     structure = "(?:%s)" % GetStructurePattern(infos)
Laurent@1322:                 else:
Laurent@1322:                     structure = "%s " % infos["name"]
Laurent@1322:                 choices.append(ComputeMultiplicity(structure, infos))
Laurent@1322:             elements.append(ComputeMultiplicity("|".join(choices), element))
Laurent@1322:         elif element["name"] == "content" and element["elmt_type"]["type"] == SIMPLETYPE:
Laurent@1322:             elements.append("(?:#text |#cdata-section )?")
Laurent@1322:         else:
Laurent@1322:             elements.append(ComputeMultiplicity("%s " % element["name"], element))
Laurent@1322:     if classinfos.get("order", True) or len(elements) == 0:
Laurent@1322:         return re.compile(base_structure_pattern + "".join(elements) + "$")
Laurent@1322:     else:
Laurent@1322:         raise ValueError("XSD structure not yet supported!")
Laurent@1322: 
andrej@1736: 
andrej@2297: def generateClassCreateFunction(factory, class_definition):
andrej@1736:     """
andrej@1736:     Method that generate the method for creating a class instance
andrej@1736:     """
Laurent@814:     def classCreatefunction():
andrej@2297:         return factory.Parser.CreateElementFromClass(class_definition)
Laurent@814:     return classCreatefunction
Laurent@814: 
andrej@1736: 
Laurent@1290: def generateGetattrMethod(factory, class_definition, classinfos):
Laurent@1290:     attributes = dict([(attr["name"], attr) for attr in classinfos["attributes"] if attr["use"] != "prohibited"])
Laurent@1290:     elements = dict([(element["name"], element) for element in classinfos["elements"]])
andrej@1730: 
Laurent@1290:     def getattrMethod(self, name):
andrej@1763:         if name in attributes:
Laurent@1290:             attribute_infos = attributes[name]
Laurent@1290:             attribute_infos["attr_type"] = FindTypeInfos(factory, attribute_infos["attr_type"])
Laurent@1290:             value = self.get(name)
Laurent@1290:             if value is not None:
Laurent@1290:                 return attribute_infos["attr_type"]["extract"](value, extract=False)
andrej@1763:             elif "fixed" in attribute_infos:
Laurent@1290:                 return attribute_infos["attr_type"]["extract"](attribute_infos["fixed"], extract=False)
andrej@1763:             elif "default" in attribute_infos:
Laurent@1294:                 return attribute_infos["attr_type"]["extract"](attribute_infos["default"], extract=False)
Laurent@1294:             return None
andrej@1730: 
andrej@1763:         elif name in elements:
Laurent@1290:             element_infos = elements[name]
Laurent@1290:             element_infos["elmt_type"] = FindTypeInfos(factory, element_infos["elmt_type"])
Laurent@1291:             if element_infos["type"] == CHOICE:
Laurent@1305:                 content = element_infos["elmt_type"]["choices_xpath"](self)
Laurent@1290:                 if element_infos["maxOccurs"] == "unbounded" or element_infos["maxOccurs"] > 1:
Laurent@1290:                     return content
Laurent@1290:                 elif len(content) > 0:
Laurent@1290:                     return content[0]
andrej@1730:                 return None
Laurent@1291:             elif element_infos["type"] == ANY:
Laurent@1291:                 return element_infos["elmt_type"]["extract"](self)
Laurent@1322:             elif name == "content" and element_infos["elmt_type"]["type"] == SIMPLETYPE:
Laurent@1322:                 return element_infos["elmt_type"]["extract"](self.text, extract=False)
Laurent@1290:             else:
Laurent@1290:                 element_name = factory.etreeNamespaceFormat % name
Laurent@1290:                 if element_infos["maxOccurs"] == "unbounded" or element_infos["maxOccurs"] > 1:
Laurent@1322:                     values = self.findall(element_name)
Laurent@1322:                     if element_infos["elmt_type"]["type"] == SIMPLETYPE:
Laurent@1322:                         return map(lambda value:
andrej@1768:                                    element_infos["elmt_type"]["extract"](value.text, extract=False),
andrej@1768:                                    values)
Laurent@1322:                     return values
Laurent@1290:                 else:
Laurent@1322:                     value = self.find(element_name)
Laurent@1322:                     if element_infos["elmt_type"]["type"] == SIMPLETYPE:
Laurent@1322:                         return element_infos["elmt_type"]["extract"](value.text, extract=False)
Laurent@1322:                     return value
andrej@1730: 
andrej@1763:         elif "base" in classinfos:
Laurent@1290:             return classinfos["base"].__getattr__(self, name)
andrej@1730: 
Laurent@1290:         return DefaultElementClass.__getattribute__(self, name)
andrej@1730: 
Laurent@1290:     return getattrMethod
Laurent@1290: 
andrej@1736: 
Laurent@814: def generateSetattrMethod(factory, class_definition, classinfos):
Laurent@814:     attributes = dict([(attr["name"], attr) for attr in classinfos["attributes"] if attr["use"] != "prohibited"])
Laurent@814:     optional_attributes = dict([(attr["name"], True) for attr in classinfos["attributes"] if attr["use"] == "optional"])
Laurent@1290:     elements = OrderedDict([(element["name"], element) for element in classinfos["elements"]])
andrej@1730: 
Laurent@814:     def setattrMethod(self, name, value):
andrej@1763:         if name in attributes:
Laurent@1290:             attribute_infos = attributes[name]
Laurent@1290:             attribute_infos["attr_type"] = FindTypeInfos(factory, attribute_infos["attr_type"])
Laurent@1290:             if optional_attributes.get(name, False):
Laurent@1290:                 default = attribute_infos.get("default", None)
Laurent@1290:                 if value is None or value == default:
Laurent@1291:                     self.attrib.pop(name, None)
Laurent@1290:                     return
andrej@1763:             elif "fixed" in attribute_infos:
Laurent@1290:                 return
Laurent@1290:             return self.set(name, attribute_infos["attr_type"]["generate"](value))
andrej@1730: 
andrej@1763:         elif name in elements:
Laurent@1290:             element_infos = elements[name]
Laurent@1290:             element_infos["elmt_type"] = FindTypeInfos(factory, element_infos["elmt_type"])
Laurent@1291:             if element_infos["type"] == ANY:
Laurent@1291:                 element_infos["elmt_type"]["generate"](self, value)
andrej@1730: 
Laurent@1322:             elif name == "content" and element_infos["elmt_type"]["type"] == SIMPLETYPE:
Laurent@1322:                 self.text = element_infos["elmt_type"]["generate"](value)
andrej@1730: 
Laurent@1291:             else:
Laurent@1315:                 prefix = ("%s:" % factory.TargetNamespace
Laurent@1315:                           if factory.TargetNamespace is not None else "")
Laurent@1315:                 element_xpath = (prefix + name
Laurent@1291:                                  if name != "content"
Laurent@1305:                                  else elements["content"]["elmt_type"]["choices_xpath"].path)
andrej@1730: 
Laurent@1291:                 for element in self.xpath(element_xpath, namespaces=factory.NSMAP):
Laurent@1291:                     self.remove(element)
andrej@1730: 
Laurent@1291:                 if value is not None:
Laurent@1294:                     element_idx = elements.keys().index(name)
Laurent@1294:                     if element_idx > 0:
Laurent@1294:                         previous_elements_xpath = "|".join(map(
Laurent@1315:                             lambda x: prefix + x
andrej@1777:                             if x != "content"
andrej@1777:                             else elements["content"]["elmt_type"]["choices_xpath"].path,
Laurent@1294:                             elements.keys()[:element_idx]))
andrej@1730: 
Laurent@1294:                         insertion_point = len(self.xpath(previous_elements_xpath, namespaces=factory.NSMAP))
Laurent@1294:                     else:
Laurent@1294:                         insertion_point = 0
andrej@1730: 
andrej@2450:                     if not isinstance(value, list):
Laurent@1291:                         value = [value]
andrej@1730: 
Laurent@1291:                     for element in reversed(value):
Laurent@1322:                         if element_infos["elmt_type"]["type"] == SIMPLETYPE:
andrej@2297:                             tmp_element = factory.Parser.makeelement(factory.etreeNamespaceFormat % name)
Laurent@1322:                             tmp_element.text = element_infos["elmt_type"]["generate"](element)
Laurent@1322:                             element = tmp_element
Laurent@1291:                         self.insert(insertion_point, element)
andrej@1730: 
andrej@1763:         elif "base" in classinfos:
Laurent@814:             return classinfos["base"].__setattr__(self, name, value)
andrej@1730: 
Laurent@814:         else:
Laurent@814:             raise AttributeError("'%s' can't have an attribute '%s'." % (self.__class__.__name__, name))
andrej@1730: 
Laurent@814:     return setattrMethod
Laurent@814: 
andrej@1736: 
Laurent@814: def gettypeinfos(name, facets):
andrej@1763:     if "enumeration" in facets and facets["enumeration"][0] is not None:
Laurent@814:         return facets["enumeration"][0]
andrej@1763:     elif "maxInclusive" in facets:
andrej@1739:         limits = {"max": None, "min": None}
Laurent@814:         if facets["maxInclusive"][0] is not None:
Laurent@814:             limits["max"] = facets["maxInclusive"][0]
Laurent@814:         elif facets["maxExclusive"][0] is not None:
Laurent@814:             limits["max"] = facets["maxExclusive"][0] - 1
Laurent@814:         if facets["minInclusive"][0] is not None:
Laurent@814:             limits["min"] = facets["minInclusive"][0]
Laurent@814:         elif facets["minExclusive"][0] is not None:
Laurent@814:             limits["min"] = facets["minExclusive"][0] + 1
Laurent@814:         if limits["max"] is not None or limits["min"] is not None:
Laurent@814:             return limits
Laurent@814:     return name
Laurent@814: 
andrej@1736: 
Laurent@814: def generateGetElementAttributes(factory, classinfos):
Laurent@814:     def getElementAttributes(self):
Laurent@814:         attr_list = []
andrej@1763:         if "base" in classinfos:
Laurent@814:             attr_list.extend(classinfos["base"].getElementAttributes(self))
Laurent@814:         for attr in classinfos["attributes"]:
Laurent@814:             if attr["use"] != "prohibited":
andrej@1768:                 attr_params = {
andrej@1768:                     "name": attr["name"],
andrej@1768:                     "use": attr["use"],
andrej@1739:                     "type": gettypeinfos(attr["attr_type"]["basename"], attr["attr_type"]["facets"]),
andrej@1739:                     "value": getattr(self, attr["name"], "")}
Laurent@814:                 attr_list.append(attr_params)
Laurent@814:         return attr_list
Laurent@814:     return getElementAttributes
Laurent@814: 
andrej@1736: 
Laurent@814: def generateGetElementInfos(factory, classinfos):
Laurent@814:     attributes = dict([(attr["name"], attr) for attr in classinfos["attributes"] if attr["use"] != "prohibited"])
Laurent@814:     elements = dict([(element["name"], element) for element in classinfos["elements"]])
andrej@1730: 
Laurent@814:     def getElementInfos(self, name, path=None, derived=False):
Laurent@814:         attr_type = "element"
Laurent@814:         value = None
Laurent@814:         use = "required"
Laurent@814:         children = []
Laurent@814:         if path is not None:
Laurent@814:             parts = path.split(".", 1)
andrej@1763:             if parts[0] in attributes:
Laurent@1179:                 if len(parts) != 1:
Laurent@814:                     raise ValueError("Wrong path!")
andrej@1730:                 attr_type = gettypeinfos(attributes[parts[0]]["attr_type"]["basename"],
Laurent@814:                                          attributes[parts[0]]["attr_type"]["facets"])
Laurent@814:                 value = getattr(self, parts[0], "")
andrej@1763:             elif parts[0] in elements:
Laurent@814:                 if elements[parts[0]]["elmt_type"]["type"] == SIMPLETYPE:
Laurent@1179:                     if len(parts) != 1:
Laurent@814:                         raise ValueError("Wrong path!")
andrej@1730:                     attr_type = gettypeinfos(elements[parts[0]]["elmt_type"]["basename"],
Laurent@814:                                              elements[parts[0]]["elmt_type"]["facets"])
Laurent@814:                     value = getattr(self, parts[0], "")
Laurent@814:                 elif parts[0] == "content":
Laurent@1315:                     return self.content.getElementInfos(self.content.getLocalTag(), path)
Laurent@814:                 else:
Laurent@814:                     attr = getattr(self, parts[0], None)
Laurent@814:                     if attr is None:
Laurent@814:                         raise ValueError("Wrong path!")
Laurent@814:                     if len(parts) == 1:
Laurent@814:                         return attr.getElementInfos(parts[0])
Laurent@814:                     else:
Laurent@814:                         return attr.getElementInfos(parts[0], parts[1])
andrej@1763:             elif "content" in elements:
Laurent@1179:                 if len(parts) > 0:
Laurent@1315:                     return self.content.getElementInfos(name, path)
andrej@1763:             elif "base" in classinfos:
Laurent@1179:                 classinfos["base"].getElementInfos(name, path)
Laurent@814:             else:
Laurent@814:                 raise ValueError("Wrong path!")
Laurent@814:         else:
Laurent@814:             if not derived:
Laurent@814:                 children.extend(self.getElementAttributes())
andrej@1763:             if "base" in classinfos:
Laurent@814:                 children.extend(classinfos["base"].getElementInfos(self, name, derived=True)["children"])
Laurent@814:             for element_name, element in elements.items():
Laurent@814:                 if element["minOccurs"] == 0:
Laurent@814:                     use = "optional"
Laurent@814:                 if element_name == "content" and element["type"] == CHOICE:
Laurent@814:                     attr_type = [(choice["name"], None) for choice in element["choices"]]
Laurent@814:                     if self.content is None:
Laurent@814:                         value = ""
Laurent@814:                     else:
Laurent@1315:                         value = self.content.getLocalTag()
Laurent@1315:                         if self.content is not None:
Laurent@1315:                             children.extend(self.content.getElementInfos(value)["children"])
Laurent@814:                 elif element["elmt_type"]["type"] == SIMPLETYPE:
andrej@1768:                     children.append({
andrej@1768:                         "name": element_name,
andrej@1768:                         "require": element["minOccurs"] != 0,
andrej@1730:                         "type": gettypeinfos(element["elmt_type"]["basename"],
Laurent@814:                                              element["elmt_type"]["facets"]),
Laurent@814:                         "value": getattr(self, element_name, None)})
Laurent@814:                 else:
Laurent@814:                     instance = getattr(self, element_name, None)
Laurent@814:                     if instance is None:
Laurent@814:                         instance = element["elmt_type"]["initial"]()
Laurent@814:                     children.append(instance.getElementInfos(element_name))
Laurent@814:         return {"name": name, "type": attr_type, "value": value, "use": use, "children": children}
Laurent@814:     return getElementInfos
Laurent@814: 
andrej@1736: 
Laurent@814: def generateSetElementValue(factory, classinfos):
Laurent@814:     attributes = dict([(attr["name"], attr) for attr in classinfos["attributes"] if attr["use"] != "prohibited"])
Laurent@814:     elements = dict([(element["name"], element) for element in classinfos["elements"]])
andrej@1730: 
Laurent@814:     def setElementValue(self, path, value):
Laurent@814:         if path is not None:
Laurent@814:             parts = path.split(".", 1)
andrej@1763:             if parts[0] in attributes:
Laurent@814:                 if len(parts) != 1:
Laurent@814:                     raise ValueError("Wrong path!")
Laurent@814:                 if attributes[parts[0]]["attr_type"]["basename"] == "boolean":
Laurent@814:                     setattr(self, parts[0], value)
Edouard@3288:                 elif attributes[parts[0]]["use"] == "optional" and value == None:
andrej@1763:                     if "default" in attributes[parts[0]]:
andrej@1730:                         setattr(self, parts[0],
andrej@1768:                                 attributes[parts[0]]["attr_type"]["extract"](
andrej@1768:                                     attributes[parts[0]]["default"], False))
Laurent@1022:                     else:
Laurent@1022:                         setattr(self, parts[0], None)
Laurent@814:                 else:
Laurent@814:                     setattr(self, parts[0], attributes[parts[0]]["attr_type"]["extract"](value, False))
andrej@1763:             elif parts[0] in elements:
Laurent@814:                 if elements[parts[0]]["elmt_type"]["type"] == SIMPLETYPE:
Laurent@814:                     if len(parts) != 1:
Laurent@814:                         raise ValueError("Wrong path!")
Laurent@814:                     if elements[parts[0]]["elmt_type"]["basename"] == "boolean":
Laurent@814:                         setattr(self, parts[0], value)
Laurent@1017:                     elif attributes[parts[0]]["minOccurs"] == 0 and value == "":
Laurent@1017:                         setattr(self, parts[0], None)
Laurent@814:                     else:
Laurent@814:                         setattr(self, parts[0], elements[parts[0]]["elmt_type"]["extract"](value, False))
Laurent@814:                 else:
Laurent@814:                     instance = getattr(self, parts[0], None)
Laurent@814:                     if instance is None and elements[parts[0]]["minOccurs"] == 0:
Laurent@814:                         instance = elements[parts[0]]["elmt_type"]["initial"]()
Laurent@814:                         setattr(self, parts[0], instance)
andrej@1743:                     if instance is not None:
Laurent@814:                         if len(parts) > 1:
Laurent@814:                             instance.setElementValue(parts[1], value)
Laurent@814:                         else:
Laurent@814:                             instance.setElementValue(None, value)
andrej@1763:             elif "content" in elements:
Laurent@814:                 if len(parts) > 0:
Laurent@1315:                     self.content.setElementValue(path, value)
andrej@1763:             elif "base" in classinfos:
Laurent@814:                 classinfos["base"].setElementValue(self, path, value)
andrej@1763:         elif "content" in elements:
Laurent@814:             if value == "":
Laurent@814:                 if elements["content"]["minOccurs"] == 0:
Laurent@1315:                     self.setcontent([])
Laurent@814:                 else:
Laurent@814:                     raise ValueError("\"content\" element is required!")
Laurent@814:             else:
Laurent@814:                 self.setcontentbytype(value)
Laurent@814:     return setElementValue
Laurent@814: 
andrej@1736: 
Laurent@814: def generateInitMethod(factory, classinfos):
andrej@1736:     """
andrej@1736:     Methods that generates the different methods for setting and getting the attributes
andrej@1736:     """
andrej@1736: 
Laurent@814:     def initMethod(self):
andrej@1763:         if "base" in classinfos:
Laurent@1315:             classinfos["base"]._init_(self)
Laurent@814:         for attribute in classinfos["attributes"]:
Laurent@814:             attribute["attr_type"] = FindTypeInfos(factory, attribute["attr_type"])
Laurent@1291:             if attribute["use"] == "required":
Laurent@1290:                 self.set(attribute["name"], attribute["attr_type"]["generate"](attribute["attr_type"]["initial"]()))
Laurent@814:         for element in classinfos["elements"]:
Laurent@1291:             if element["type"] != CHOICE:
Laurent@1291:                 initial = GetElementInitialValue(factory, element)
Laurent@1291:                 if initial is not None:
Laurent@1293:                     map(self.append, initial)
Laurent@814:     return initMethod
Laurent@814: 
andrej@1736: 
Laurent@814: def generateSetMethod(attr):
Laurent@814:     def setMethod(self, value):
Laurent@814:         setattr(self, attr, value)
Laurent@814:     return setMethod
Laurent@814: 
andrej@1736: 
Laurent@814: def generateGetMethod(attr):
Laurent@814:     def getMethod(self):
Laurent@814:         return getattr(self, attr, None)
Laurent@814:     return getMethod
Laurent@814: 
andrej@1736: 
Laurent@814: def generateAddMethod(attr, factory, infos):
Laurent@814:     def addMethod(self):
Laurent@814:         if infos["type"] == ATTRIBUTE:
Laurent@814:             infos["attr_type"] = FindTypeInfos(factory, infos["attr_type"])
andrej@1775:             if "default" not in infos:
Laurent@1293:                 setattr(self, attr, infos["attr_type"]["initial"]())
Laurent@814:         elif infos["type"] == ELEMENT:
Laurent@814:             infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
Laurent@1293:             value = infos["elmt_type"]["initial"]()
Laurent@1293:             DefaultElementClass.__setattr__(value, "tag", factory.etreeNamespaceFormat % attr)
Laurent@1293:             setattr(self, attr, value)
Laurent@1315:             value._init_()
Laurent@814:         else:
Laurent@814:             raise ValueError("Invalid class attribute!")
Laurent@814:     return addMethod
Laurent@814: 
andrej@1736: 
Laurent@814: def generateDeleteMethod(attr):
Laurent@814:     def deleteMethod(self):
Laurent@814:         setattr(self, attr, None)
Laurent@814:     return deleteMethod
Laurent@814: 
andrej@1736: 
Laurent@814: def generateAppendMethod(attr, maxOccurs, factory, infos):
Laurent@814:     def appendMethod(self, value):
Laurent@814:         infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
Laurent@814:         attr_list = getattr(self, attr)
Laurent@814:         if maxOccurs == "unbounded" or len(attr_list) < maxOccurs:
Laurent@1290:             if len(attr_list) == 0:
Laurent@1290:                 setattr(self, attr, [value])
Laurent@814:             else:
Laurent@1290:                 attr_list[-1].addnext(value)
Laurent@814:         else:
Laurent@814:             raise ValueError("There can't be more than %d values in \"%s\"!" % (maxOccurs, attr))
Laurent@814:     return appendMethod
Laurent@814: 
andrej@1736: 
Laurent@814: def generateInsertMethod(attr, maxOccurs, factory, infos):
Laurent@814:     def insertMethod(self, index, value):
Laurent@814:         infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"])
Laurent@814:         attr_list = getattr(self, attr)
Laurent@814:         if maxOccurs == "unbounded" or len(attr_list) < maxOccurs:
Laurent@1290:             if len(attr_list) == 0:
Laurent@1290:                 setattr(self, attr, [value])
Laurent@1290:             elif index == 0:
Laurent@1290:                 attr_list[0].addprevious(value)
Laurent@814:             else:
Laurent@1290:                 attr_list[min(index - 1, len(attr_list) - 1)].addnext(value)
Laurent@814:         else:
Laurent@814:             raise ValueError("There can't be more than %d values in \"%s\"!" % (maxOccurs, attr))
Laurent@814:     return insertMethod
Laurent@814: 
andrej@1736: 
Laurent@814: def generateGetChoicesMethod(choice_types):
Laurent@814:     def getChoicesMethod(self):
Laurent@814:         return [choice["name"] for choice in choice_types]
Laurent@814:     return getChoicesMethod
Laurent@814: 
andrej@1736: 
Laurent@814: def generateSetChoiceByTypeMethod(factory, choice_types):
Laurent@814:     choices = dict([(choice["name"], choice) for choice in choice_types])
andrej@1750: 
Laurent@1290:     def setChoiceMethod(self, content_type):
andrej@1775:         if content_type not in choices:
Laurent@1290:             raise ValueError("Unknown \"%s\" choice type for \"content\"!" % content_type)
Laurent@1290:         choices[content_type]["elmt_type"] = FindTypeInfos(factory, choices[content_type]["elmt_type"])
Laurent@1290:         new_content = choices[content_type]["elmt_type"]["initial"]()
Laurent@1315:         DefaultElementClass.__setattr__(new_content, "tag", factory.etreeNamespaceFormat % content_type)
Laurent@1290:         self.content = new_content
Laurent@1290:         return new_content
Laurent@814:     return setChoiceMethod
Laurent@814: 
andrej@1736: 
Laurent@814: def generateAppendChoiceByTypeMethod(maxOccurs, factory, choice_types):
Laurent@814:     choices = dict([(choice["name"], choice) for choice in choice_types])
andrej@1750: 
Laurent@1290:     def appendChoiceMethod(self, content_type):
andrej@1775:         if content_type not in choices:
Laurent@1290:             raise ValueError("Unknown \"%s\" choice type for \"content\"!" % content_type)
Laurent@1290:         choices[content_type]["elmt_type"] = FindTypeInfos(factory, choices[content_type]["elmt_type"])
Laurent@814:         if maxOccurs == "unbounded" or len(self.content) < maxOccurs:
Laurent@1290:             new_element = choices[content_type]["elmt_type"]["initial"]()
Laurent@1315:             DefaultElementClass.__setattr__(new_element, "tag", factory.etreeNamespaceFormat % content_type)
Laurent@1290:             self.appendcontent(new_element)
Laurent@814:             return new_element
Laurent@814:         else:
Laurent@814:             raise ValueError("There can't be more than %d values in \"content\"!" % maxOccurs)
Laurent@814:     return appendChoiceMethod
Laurent@814: 
andrej@1736: 
Laurent@814: def generateInsertChoiceByTypeMethod(maxOccurs, factory, choice_types):
Laurent@814:     choices = dict([(choice["name"], choice) for choice in choice_types])
andrej@1750: 
Laurent@1290:     def insertChoiceMethod(self, index, content_type):
andrej@1775:         if content_type not in choices:
Laurent@1290:             raise ValueError("Unknown \"%s\" choice type for \"content\"!" % content_type)
Laurent@1290:         choices[type]["elmt_type"] = FindTypeInfos(factory, choices[content_type]["elmt_type"])
Laurent@814:         if maxOccurs == "unbounded" or len(self.content) < maxOccurs:
Laurent@1290:             new_element = choices[content_type]["elmt_type"]["initial"]()
Laurent@1315:             DefaultElementClass.__setattr__(new_element, "tag", factory.etreeNamespaceFormat % content_type)
Laurent@1290:             self.insertcontent(index, new_element)
Laurent@814:             return new_element
Laurent@814:         else:
Laurent@814:             raise ValueError("There can't be more than %d values in \"content\"!" % maxOccurs)
Laurent@814:     return insertChoiceMethod
Laurent@814: 
andrej@1736: 
Laurent@814: def generateRemoveMethod(attr, minOccurs):
Laurent@814:     def removeMethod(self, index):
Laurent@814:         attr_list = getattr(self, attr)
Laurent@814:         if len(attr_list) > minOccurs:
Laurent@1290:             self.remove(attr_list[index])
Laurent@814:         else:
Laurent@814:             raise ValueError("There can't be less than %d values in \"%s\"!" % (minOccurs, attr))
Laurent@814:     return removeMethod
Laurent@814: 
andrej@1736: 
Laurent@814: def generateCountMethod(attr):
Laurent@814:     def countMethod(self):
Laurent@814:         return len(getattr(self, attr))
Laurent@814:     return countMethod
Laurent@814: 
andrej@1749: 
andrej@2439: NAMESPACE_PATTERN = re.compile(r"xmlns(?:\:[^\=]*)?=\"[^\"]*\" ")
Laurent@1300: 
andrej@1736: 
Laurent@1290: class DefaultElementClass(etree.ElementBase):
andrej@1730: 
Laurent@1322:     StructurePattern = re.compile("$")
andrej@1730: 
Laurent@1315:     def _init_(self):
Laurent@1291:         pass
andrej@1730: 
Laurent@1290:     def getLocalTag(self):
Laurent@1290:         return etree.QName(self.tag).localname
andrej@1730: 
Laurent@1290:     def tostring(self):
andrej@1505:         return NAMESPACE_PATTERN.sub("", etree.tostring(self, pretty_print=True, encoding='utf-8')).decode('utf-8')
Laurent@1290: 
andrej@1736: 
Laurent@1290: class XMLElementClassLookUp(etree.PythonElementClassLookup):
andrej@1730: 
Laurent@1291:     def __init__(self, classes, *args, **kwargs):
Laurent@1290:         etree.PythonElementClassLookup.__init__(self, *args, **kwargs)
Laurent@1290:         self.LookUpClasses = classes
andrej@2297:         self.ElementTag = None
andrej@2297:         self.ElementClass = None
andrej@1730: 
Laurent@1290:     def GetElementClass(self, element_tag, parent_tag=None, default=DefaultElementClass):
Laurent@1290:         element_class = self.LookUpClasses.get(element_tag, (default, None))
andrej@2450:         if not isinstance(element_class, dict):
andrej@2450:             if isinstance(element_class[0], string_types):
Laurent@1290:                 return self.GetElementClass(element_class[0], default=default)
Laurent@1290:             return element_class[0]
andrej@1730: 
Laurent@1290:         element_with_parent_class = element_class.get(parent_tag, default)
andrej@2450:         if isinstance(element_with_parent_class, string_types):
Laurent@1290:             return self.GetElementClass(element_with_parent_class, default=default)
Laurent@1290:         return element_with_parent_class
andrej@1730: 
andrej@2297:     def SetLookupResult(self, element, element_class):
andrej@2297:         """
andrej@2297:         Set lookup result for the next 'lookup' callback made by lxml backend.
andrej@2297:         Lookup result is used only if element matches with tag's name submited to 'lookup'.
andrej@2297:         This is done, because there is no way to submit extra search parameters for
andrej@2297:         etree.PythonElementClassLookup.lookup() from etree.XMLParser.makeelement()
andrej@2297:         It's valid only for a signle 'lookup' call.
andrej@2297: 
andrej@2297:         :param element:
andrej@2297:             element's tag name
andrej@2297:         :param element_class:
andrej@2297:             element class that should be returned on
andrej@2297:             match in the next 'lookup' call.
andrej@2297:         :return:
andrej@2297:             Nothing
andrej@2297:         """
andrej@2297:         self.ElementTag = element
andrej@2297:         self.ElementClass = element_class
andrej@2297: 
andrej@2297:     def ResetLookupResult(self):
andrej@2297:         """Reset lookup result, so it don't influence next lookups"""
andrej@2297:         self.ElementTag = None
andrej@2297:         self.ElementClass = None
andrej@2297: 
andrej@2297:     def GetLookupResult(self, element):
andrej@2297:         """Returns previously set SetLookupResult() lookup result"""
andrej@2297:         element_class = None
andrej@2297:         if self.ElementTag is not None and self.ElementTag == element.tag:
andrej@2297:             element_class = self.ElementClass
andrej@2297:         self.ResetLookupResult()
andrej@2297:         return element_class
andrej@2297: 
Laurent@1290:     def lookup(self, document, element):
andrej@2297:         """
andrej@2297:         Lookup for element class for given element tag.
andrej@2297:         If return None from this method, the fallback is called.
andrej@2297: 
andrej@2297:         :param document:
andrej@2297:             opaque document instance that contains the Element
andrej@2297:         :param element:
andrej@2297:             lightweight Element proxy implementation that is only valid during the lookup.
andrej@2297:             Do not try to keep a reference to it.
andrej@2297:             Once the lookup is done, the proxy will be invalid.
andrej@2297:         :return:
andrej@2297:             Returns element class corresponding to given element.
andrej@2297:         """
andrej@2297:         element_class = self.GetLookupResult(element)
andrej@2297:         if element_class is not None:
andrej@2297:             return element_class
andrej@2297: 
Laurent@1290:         parent = element.getparent()
andrej@1768:         element_class = self.GetElementClass(
andrej@1768:             element.tag, parent.tag if parent is not None else None)
andrej@2450:         if isinstance(element_class, list):
Laurent@1322:             children = "".join([
Laurent@1322:                 "%s " % etree.QName(child.tag).localname
Laurent@1322:                 for child in element])
Laurent@1322:             for possible_class in element_class:
andrej@2450:                 if isinstance(possible_class, string_types):
Laurent@1322:                     possible_class = self.GetElementClass(possible_class)
Laurent@1322:                 if possible_class.StructurePattern.match(children) is not None:
Laurent@1322:                     return possible_class
Laurent@1322:             return element_class[0]
Laurent@1322:         return element_class
Laurent@1290: 
andrej@1736: 
Laurent@1290: class XMLClassParser(etree.XMLParser):
andrej@2297:     def __init__(self, *args, **kwargs):
Laurent@1290:         etree.XMLParser.__init__(self, *args, **kwargs)
andrej@2297: 
andrej@2297:     def initMembers(self, namespaces, default_namespace_format, base_class, xsd_schema):
Laurent@1290:         self.DefaultNamespaceFormat = default_namespace_format
Laurent@1290:         self.NSMAP = namespaces
Laurent@1290:         targetNamespace = etree.QName(default_namespace_format % "d").namespace
Laurent@1290:         if targetNamespace is not None:
Laurent@1290:             self.RootNSMAP = {
Laurent@1290:                 name if targetNamespace != uri else None: uri
Laurent@1290:                 for name, uri in namespaces.iteritems()}
Laurent@1290:         else:
Laurent@1290:             self.RootNSMAP = namespaces
Laurent@1290:         self.BaseClass = base_class
Laurent@1330:         self.XSDSchema = xsd_schema
andrej@1730: 
Laurent@1290:     def set_element_class_lookup(self, class_lookup):
Laurent@1290:         etree.XMLParser.set_element_class_lookup(self, class_lookup)
Laurent@1290:         self.ClassLookup = class_lookup
andrej@1730: 
Laurent@1330:     def LoadXMLString(self, xml_string):
Laurent@1330:         tree = etree.fromstring(xml_string, self)
Laurent@1330:         if not self.XSDSchema.validate(tree):
Laurent@1330:             error = self.XSDSchema.error_log.last_error
Laurent@1330:             return tree, (error.line, error.message)
andrej@1730:         return tree, None
andrej@1730: 
Laurent@1304:     def Dumps(self, xml_obj):
andrej@1563:         return etree.tostring(xml_obj, encoding='utf-8')
andrej@1730: 
Laurent@1304:     def Loads(self, xml_string):
Laurent@1304:         return etree.fromstring(xml_string, self)
andrej@1730: 
Laurent@1290:     def CreateRoot(self):
Laurent@1290:         if self.BaseClass is not None:
Laurent@1291:             root = self.makeelement(
Laurent@1290:                 self.DefaultNamespaceFormat % self.BaseClass[0],
Laurent@1290:                 nsmap=self.RootNSMAP)
Laurent@1315:             root._init_()
Laurent@1291:             return root
Laurent@1290:         return None
andrej@1730: 
Laurent@1290:     def GetElementClass(self, element_tag, parent_tag=None):
Laurent@1290:         return self.ClassLookup.GetElementClass(
andrej@1730:             self.DefaultNamespaceFormat % element_tag,
andrej@1730:             self.DefaultNamespaceFormat % parent_tag
andrej@1730:             if parent_tag is not None else parent_tag,
Laurent@1290:             None)
andrej@1730: 
Laurent@1322:     def CreateElement(self, element_tag, parent_tag=None, class_idx=None):
andrej@2297:         """
andrej@2297:         Create XML element based on elements and parent's tag names.
andrej@2297: 
andrej@2297:         :param element_tag:
andrej@2297:             element's tag name
andrej@2297:         :param parent_tag:
andrej@2297:             optional parent's tag name. Default value is None.
andrej@2297:         :param class_idx:
andrej@2297:             optional index of class in list of founded classes
andrej@2297:             with same element and parent. Default value is None.
andrej@2297:         :return:
andrej@2297:             created XML element
andrej@2297:             (subclass of lxml.etree._Element created by class factory)
andrej@2297:         """
Laurent@1322:         element_class = self.GetElementClass(element_tag, parent_tag)
andrej@2450:         if isinstance(element_class, list):
Laurent@1322:             if class_idx is not None and class_idx < len(element_class):
andrej@2297:                 element_class = element_class[class_idx]
Laurent@1322:             else:
andrej@1765:                 raise ValueError("No corresponding class found!")
andrej@2297:         return self.CreateElementFromClass(element_class, element_tag)
andrej@2297: 
andrej@2297:     def CreateElementFromClass(self, element_class, element_tag=None):
andrej@2297:         """
andrej@2297:         Create XML element instance of submitted element's class.
andrej@2297:         Submitted class should be subclass of lxml.etree._Element.
andrej@2297: 
andrej@2297:         element_class shouldn't be used to create XML element
andrej@2297:         directly using element_class(), because lxml backend
andrej@2297:         should be aware what class handles what xml element,
andrej@2297:         otherwise default lxml.etree._Element will be used.
andrej@2297: 
andrej@2297:         :param element_class:
andrej@2297:             element class
andrej@2297:         :param element_tag:
andrej@2297:             optional element's tag name.
andrej@2297:             If omitted it's calculated from element_class instance.
andrej@2297:         :return:
andrej@2297:             created XML element
andrej@2297:             (subclass of lxml.etree._Element created by class factory)
andrej@2297:         """
andrej@2297:         if element_tag is None:
andrej@2297:             element_tag = element_class().tag
andrej@2297:         etag = self.DefaultNamespaceFormat % element_tag
andrej@2297:         self.ClassLookup.SetLookupResult(etag, element_class)
andrej@2297:         new_element = self.makeelement(etag)
andrej@2297:         self.ClassLookup.ResetLookupResult()
Laurent@1290:         DefaultElementClass.__setattr__(new_element, "tag", self.DefaultNamespaceFormat % element_tag)
Laurent@1315:         new_element._init_()
Laurent@1290:         return new_element
andrej@1730: 
andrej@1736: 
Laurent@1290: def GenerateParser(factory, xsdstring):
andrej@1837:     """
andrej@1837:     This function generate a xml parser from a class factory
andrej@1837:     """
andrej@1837: 
andrej@2297:     parser = XMLClassParser(strip_cdata=False, remove_blank_text=True)
andrej@2297:     factory.Parser = parser
andrej@2297: 
Laurent@814:     ComputedClasses = factory.CreateClasses()
Laurent@1322:     if factory.FileName is not None:
Laurent@1290:         ComputedClasses = ComputedClasses[factory.FileName]
Laurent@1315:     BaseClass = [(name, XSDclass) for name, XSDclass in ComputedClasses.items() if XSDclass.IsBaseClass]
andrej@1730: 
andrej@2297:     parser.initMembers(
Laurent@1290:         factory.NSMAP,
Laurent@1290:         factory.etreeNamespaceFormat,
Laurent@1290:         BaseClass[0] if len(BaseClass) == 1 else None,
andrej@2297:         etree.XMLSchema(etree.fromstring(xsdstring)))
andrej@2297: 
Laurent@1291:     class_lookup = XMLElementClassLookUp(factory.ComputedClassesLookUp)
Laurent@1290:     parser.set_element_class_lookup(class_lookup)
andrej@1730: 
Laurent@1290:     return parser