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