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@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 kinsamanka@3752: 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, kinsamanka@3750: ] = list(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 kinsamanka@3750: return '\t'.expandtabs(first), '\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: kinsamanka@3752: return str(unescape(attr.childNodes[0].data)) Laurent@814: else: Laurent@814: # content is a CDATA kinsamanka@3750: txt = '' Laurent@814: for node in attr.childNodes: kinsamanka@3750: if not (node.nodeName == "#text" and node.data.strip() == ''): kinsamanka@3752: txt += str(unescape(node.data)) kinsamanka@3752: return txt 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, kinsamanka@3752: "check": lambda x: isinstance(x, (str, 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: edouard@3589: def Initial(): edouard@3589: p = etree.Element(infos["name"]) edouard@3589: return p edouard@3589: Laurent@814: return { andrej@1730: "type": TAG, Laurent@814: "extract": ExtractTag, Laurent@814: "generate": GenerateTag, edouard@3589: "initial": Initial, 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): kinsamanka@3752: if isinstance(infos, str): 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 kinsamanka@3750: return [initial_value() for dummy in range(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 "") kinsamanka@3750: choices_xpath = "|".join([prefix + x for x in list(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 = [] kinsamanka@3750: for dummy in range(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] kinsamanka@3750: for space, elements in self.Namespaces.items(): 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 kinsamanka@3750: for space, elements in list(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 = {} kinsamanka@3761: if node._attrs: kinsamanka@3761: for qualified_name, attr in list(node._attrs.items()): kinsamanka@3761: namespace, name = DecomposeQualifiedName(qualified_name) kinsamanka@3761: if name in valid_attrs: kinsamanka@3761: infos = self.GetQualifiedNameInfos(name, namespace) kinsamanka@3761: if infos["type"] != SYNTAXATTRIBUTE: kinsamanka@3761: raise ValueError("\"%s\" can't be a member attribute!" % name) kinsamanka@3761: elif name in attrs: kinsamanka@3761: raise ValueError("\"%s\" attribute has been twice!" % name) kinsamanka@3761: elif element_name in infos["extract"]: kinsamanka@3761: attrs[name] = infos["extract"][element_name](attr) kinsamanka@3761: else: kinsamanka@3761: attrs[name] = infos["extract"]["default"](attr) kinsamanka@3761: elif namespace == "xmlns": kinsamanka@3761: infos = self.GetQualifiedNameInfos("anyURI", self.SchemaNamespace) kinsamanka@3761: value = infos["extract"](attr) kinsamanka@3761: self.DefinedNamespaces[value] = name kinsamanka@3761: self.NSMAP[name] = value Laurent@814: else: kinsamanka@3761: raise ValueError("Invalid attribute \"%s\" for member \"%s\"!" % kinsamanka@3761: (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 kinsamanka@3752: if isinstance(typeinfos, str): 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): kinsamanka@3752: if isinstance(typeinfos, str): 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) kinsamanka@3752: if result is not None and not isinstance(result, str): 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"]) kinsamanka@3752: if result is not None and not isinstance(result, str): 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) kinsamanka@3750: for p in list(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() kinsamanka@3750: for name, infos in list(self.Namespaces[self.TargetNamespace].items()): Laurent@814: if infos["type"] == ELEMENT: kinsamanka@3752: if not isinstance(infos["elmt_type"], str) 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)) kinsamanka@3752: if result is not None and not isinstance(result, str): 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 \ kinsamanka@3752: not isinstance(result, str): 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: kinsamanka@3752: if not isinstance(element["elmt_type"], str) 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 \ kinsamanka@3752: not isinstance(result, str): Laurent@814: self.Namespaces[self.TargetNamespace][result["name"]] = result andrej@1730: kinsamanka@3750: for name, parents in self.ComputedClassesLookUp.items(): andrej@2450: if isinstance(parents, dict): kinsamanka@3750: computed_classes = list(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: kinsamanka@3750: for filename, classes in self.ComputedClasses.items(): 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: """ kinsamanka@3750: items = list(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) kinsamanka@3750: class_items = list(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): kinsamanka@3750: classnames = list(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: kinsamanka@3750: return [element_infos["elmt_type"]["extract"](value.text, extract=False) for value in 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: kinsamanka@3750: element_idx = list(elements.keys()).index(name) Laurent@1294: if element_idx > 0: kinsamanka@3750: previous_elements_xpath = "|".join([prefix + x andrej@1777: if x != "content" kinsamanka@3750: else elements["content"]["elmt_type"]["choices_xpath"].path for x in list(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: edouard@3589: raise ValueError("Wrong path: "+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: edouard@3589: raise ValueError("Wrong path: "+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: edouard@3589: raise ValueError("Wrong path: "+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: edouard@3589: raise ValueError("Wrong path: "+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"]) kinsamanka@3750: for element_name, element in list(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: edouard@3589: raise ValueError("Wrong path: "+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: edouard@3589: raise ValueError("Wrong path: "+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: kinsamanka@3750: list(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): kinsamanka@3755: return NAMESPACE_PATTERN.sub("", etree.tostring(self, encoding='unicode')) Laurent@1290: edouard@3589: def getElementInfos(self, name, path=None, derived=False): edouard@3589: return {"name": name, "type": TAG, "value": None, "use": None, "children": []} 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): kinsamanka@3752: if isinstance(element_class[0], str): 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) kinsamanka@3752: if isinstance(element_with_parent_class, str): 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: kinsamanka@3752: if isinstance(possible_class, str): 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 kinsamanka@3750: for name, uri in namespaces.items()} 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): kinsamanka@3755: tree = etree.fromstring(xml_string.encode(), 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] kinsamanka@3750: BaseClass = [(name, XSDclass) for name, XSDclass in list(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, kinsamanka@3755: etree.XMLSchema(etree.fromstring(xsdstring.encode()))) andrej@2297: Laurent@1291: class_lookup = XMLElementClassLookUp(factory.ComputedClassesLookUp) Laurent@1290: parser.set_element_class_lookup(class_lookup) andrej@1730: Laurent@1290: return parser