Laurent@814: #!/usr/bin/env python Laurent@814: # -*- coding: utf-8 -*- Laurent@814: Laurent@814: #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor Laurent@814: #based on the plcopen standard. Laurent@814: # Laurent@814: #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD Laurent@814: # Laurent@814: #See COPYING file for copyrights details. Laurent@814: # Laurent@814: #This library is free software; you can redistribute it and/or Laurent@814: #modify it under the terms of the GNU General Public Laurent@814: #License as published by the Free Software Foundation; either Laurent@814: #version 2.1 of the License, or (at your option) any later version. Laurent@814: # Laurent@814: #This library is distributed in the hope that it will be useful, Laurent@814: #but WITHOUT ANY WARRANTY; without even the implied warranty of Laurent@814: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Laurent@814: #General Public License for more details. Laurent@814: # Laurent@814: #You should have received a copy of the GNU General Public Laurent@814: #License along with this library; if not, write to the Free Software Laurent@814: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Laurent@814: Laurent@814: import os, sys Laurent@814: import re Laurent@814: import datetime Laurent@814: from types import * Laurent@814: from xml.dom import minidom Laurent@814: from xml.sax.saxutils import escape, unescape, quoteattr Laurent@814: from new import classobj Laurent@814: 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: Laurent@814: def NodeRenameAttr(node, old_name, new_name): Laurent@814: node._attrs[new_name] = node._attrs.pop(old_name) Laurent@814: Laurent@814: def NodeSetAttr(node, name, value): Laurent@814: attr = minidom.Attr(name) Laurent@814: text = minidom.Text() Laurent@814: text.data = value Laurent@814: attr.childNodes[0] = text Laurent@814: node._attrs[name] = attr Laurent@814: Laurent@814: """ Laurent@814: Regular expression models for checking all kind of string values defined in XML Laurent@814: standard Laurent@814: """ Laurent@814: Name_model = re.compile('([a-zA-Z_\:][\w\.\-\:]*)$') Laurent@814: Names_model = re.compile('([a-zA-Z_\:][\w\.\-\:]*(?: [a-zA-Z_\:][\w\.\-\:]*)*)$') Laurent@814: NMToken_model = re.compile('([\w\.\-\:]*)$') Laurent@814: NMTokens_model = re.compile('([\w\.\-\:]*(?: [\w\.\-\:]*)*)$') Laurent@814: QName_model = re.compile('((?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*)$') Laurent@814: QNames_model = re.compile('((?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*(?: (?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*)*)$') Laurent@814: NCName_model = re.compile('([a-zA-Z_][\w]*)$') Laurent@814: URI_model = re.compile('((?:http://|/)?(?:[\w.-]*/?)*)$') Laurent@814: LANGUAGE_model = re.compile('([a-zA-Z]{1,8}(?:-[a-zA-Z0-9]{1,8})*)$') Laurent@814: Laurent@814: ONLY_ANNOTATION = re.compile("((?:annotation )?)") Laurent@814: Laurent@814: """ Laurent@814: Regular expression models for extracting dates and times from a string Laurent@814: """ Laurent@814: time_model = re.compile('([0-9]{2}):([0-9]{2}):([0-9]{2}(?:\.[0-9]*)?)(?:Z)?$') Laurent@814: date_model = re.compile('([0-9]{4})-([0-9]{2})-([0-9]{2})((?:[\-\+][0-9]{2}:[0-9]{2})|Z)?$') Laurent@814: datetime_model = re.compile('([0-9]{4})-([0-9]{2})-([0-9]{2})[ T]([0-9]{2}):([0-9]{2}):([0-9]{2}(?:\.[0-9]*)?)((?:[\-\+][0-9]{2}:[0-9]{2})|Z)?$') Laurent@814: Laurent@814: class xml_timezone(datetime.tzinfo): Laurent@814: Laurent@814: def SetOffset(self, offset): Laurent@814: if offset == "Z": Laurent@814: self.__offset = timedelta(minutes = 0) Laurent@814: self.__name = "UTC" Laurent@814: else: Laurent@814: sign = {"-" : -1, "+" : 1}[offset[0]] Laurent@814: hours, minutes = [int(val) for val in offset[1:].split(":")] Laurent@814: self.__offset = 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): Laurent@814: return ZERO Laurent@814: Laurent@814: [SYNTAXELEMENT, SYNTAXATTRIBUTE, SIMPLETYPE, COMPLEXTYPE, COMPILEDCOMPLEXTYPE, Laurent@814: ATTRIBUTESGROUP, ELEMENTSGROUP, ATTRIBUTE, ELEMENT, CHOICE, ANY, TAG, CONSTRAINT, Laurent@814: ] = range(13) Laurent@814: 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): Laurent@814: raise ValueError("\"%s\" type isn't supported by \"xmlclass\" yet!" % \ Laurent@814: type) Laurent@814: return GetUnknownValue Laurent@814: Laurent@814: """ Laurent@814: This function calculates the number of whitespace for indentation Laurent@814: """ Laurent@814: def getIndent(indent, balise): Laurent@814: first = indent * 2 Laurent@814: second = first + len(balise) + 1 Laurent@814: return u'\t'.expandtabs(first), u'\t'.expandtabs(second) Laurent@814: Laurent@814: Laurent@814: def GetAttributeValue(attr, extract=True): Laurent@814: """ Laurent@814: Function that extracts data from a tree node Laurent@814: @param attr: tree node containing data to extract Laurent@814: @param extract: attr is a tree node or not Laurent@814: @return: data extracted as string Laurent@814: """ Laurent@814: if not extract: Laurent@814: return attr Laurent@814: if len(attr.childNodes) == 1: Laurent@814: return unicode(unescape(attr.childNodes[0].data)) Laurent@814: else: Laurent@814: # content is a CDATA Laurent@814: text = u'' Laurent@814: for node in attr.childNodes: Laurent@814: if not (node.nodeName == "#text" and node.data.strip() == u''): Laurent@814: text += unicode(unescape(node.data)) Laurent@814: return text Laurent@814: Laurent@814: Laurent@814: def GetNormalizedString(attr, extract=True): Laurent@814: """ Laurent@814: 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: """ Laurent@814: Function that tokenizes a string according to XML 1.0. Remove any leading Laurent@814: 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: """ Laurent@814: 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) Laurent@814: except: Laurent@814: raise ValueError("\"%s\" isn't a valid hexadecimal integer!" % value) Laurent@814: Laurent@814: Laurent@814: 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) Laurent@814: except: Laurent@814: raise ValueError("\"%s\" isn't a valid integer!" % value) Laurent@814: if minInclusive is not None and value < minInclusive: Laurent@814: raise ValueError("\"%d\" isn't greater or equal to %d!" % \ Laurent@814: (value, minInclusive)) Laurent@814: if maxInclusive is not None and value > maxInclusive: Laurent@814: raise ValueError("\"%d\" isn't lesser or equal to %d!" % \ Laurent@814: (value, maxInclusive)) Laurent@814: if minExclusive is not None and value <= minExclusive: Laurent@814: raise ValueError("\"%d\" isn't greater than %d!" % \ Laurent@814: (value, minExclusive)) Laurent@814: if maxExclusive is not None and value >= maxExclusive: Laurent@814: raise ValueError("\"%d\" isn't lesser than %d!" % \ Laurent@814: (value, maxExclusive)) Laurent@814: return value Laurent@814: return GetInteger Laurent@814: Laurent@814: Laurent@814: def GenerateFloatExtraction(type, extra_values=[]): 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: """ Laurent@814: 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) Laurent@814: except: 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) Laurent@814: except: 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: Laurent@814: raise ValueError("\"%s\" isn't a valid value for %s!" % \ Laurent@814: (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: Laurent@814: raise ValueError("\"%s\" isn't a valid value for %s!" % \ Laurent@814: (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 Laurent@814: @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: Laurent@814: raise ValueError("\"%s\" isn't a valid value for %s!" % \ Laurent@814: (value, type)) Laurent@814: return values Laurent@814: return GetModelNameList Laurent@814: Laurent@814: def GenerateAnyInfos(infos): Laurent@814: def ExtractAny(tree): Laurent@814: if tree.nodeName in ["#text", "#cdata-section"]: Laurent@814: return unicode(unescape(tree.data)) Laurent@814: else: Laurent@814: return tree Laurent@814: Laurent@814: def GenerateAny(value, name=None, indent=0): Laurent@814: if isinstance(value, (StringType, UnicodeType)): Laurent@814: try: Laurent@814: value = value.decode("utf-8") Laurent@814: except: Laurent@814: pass Laurent@814: return u'\n' % value Laurent@814: else: Laurent@814: return value.toprettyxml(indent=" "*indent, encoding="utf-8") Laurent@814: Laurent@814: return { Laurent@814: "type": COMPLEXTYPE, Laurent@814: "extract": ExtractAny, Laurent@814: "generate": GenerateAny, Laurent@814: "initial": lambda: "", Laurent@814: "check": lambda x: isinstance(x, (StringType, UnicodeType, minidom.Node)) Laurent@814: } Laurent@814: 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 Laurent@814: 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): Laurent@814: ind1, ind2 = getIndent(indent, name) Laurent@814: return ind1 + "<%s/>\n" % name Laurent@814: else: Laurent@814: return "" Laurent@814: Laurent@814: return { Laurent@814: "type": TAG, Laurent@814: "extract": ExtractTag, Laurent@814: "generate": GenerateTag, Laurent@814: "initial": lambda: None, Laurent@814: "check": lambda x: x == None or infos["minOccurs"] == 0 and value == True Laurent@814: } Laurent@814: Laurent@814: def FindTypeInfos(factory, infos): Laurent@814: if isinstance(infos, (UnicodeType, StringType)): Laurent@814: namespace, name = DecomposeQualifiedName(infos) Laurent@814: return factory.GetQualifiedNameInfos(name, namespace) Laurent@814: return infos Laurent@814: Laurent@814: def GetElementInitialValue(factory, infos): Laurent@814: infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"]) Laurent@814: if infos["minOccurs"] == 0 and infos["maxOccurs"] == 1: Laurent@814: if infos.has_key("default"): Laurent@814: return infos["elmt_type"]["extract"](infos["default"], False) Laurent@814: else: Laurent@814: return None Laurent@814: elif infos["minOccurs"] == 1 and infos["maxOccurs"] == 1: Laurent@814: return infos["elmt_type"]["initial"]() Laurent@814: else: Laurent@814: return [infos["elmt_type"]["initial"]() for i in xrange(infos["minOccurs"])] Laurent@814: Laurent@814: def HandleError(message, raise_exception): Laurent@814: if raise_exception: Laurent@814: raise ValueError(message) Laurent@814: return False Laurent@814: Laurent@814: def CheckElementValue(factory, name, infos, value, raise_exception=True): Laurent@814: infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"]) Laurent@814: if value is None and raise_exception: Laurent@814: if not (infos["minOccurs"] == 0 and infos["maxOccurs"] == 1): Laurent@814: return HandleError("Attribute '%s' isn't optional." % name, raise_exception) Laurent@814: elif infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1: Laurent@814: if not isinstance(value, ListType): Laurent@814: return HandleError("Attribute '%s' must be a list." % name, raise_exception) Laurent@814: if len(value) < infos["minOccurs"] or infos["maxOccurs"] != "unbounded" and len(value) > infos["maxOccurs"]: Laurent@814: return HandleError("List out of bounds for attribute '%s'." % name, raise_exception) Laurent@814: if not reduce(lambda x, y: x and y, map(infos["elmt_type"]["check"], value), True): Laurent@814: return HandleError("Attribute '%s' must be a list of valid elements." % name, raise_exception) Laurent@814: elif infos.has_key("fixed") and value != infos["fixed"]: Laurent@814: return HandleError("Value of attribute '%s' can only be '%s'." % (name, str(infos["fixed"])), raise_exception) Laurent@814: else: Laurent@814: return infos["elmt_type"]["check"](value) Laurent@814: return True Laurent@814: Laurent@814: def GetContentInfos(name, choices): Laurent@814: for choice_infos in choices: Laurent@814: if choices_infos["type"] == "sequence": Laurent@814: for element_infos in choices_infos["elements"]: Laurent@814: if element_infos["type"] == CHOICE: Laurent@814: if GetContentInfos(name, element_infos["choices"]): Laurent@814: return choices_infos Laurent@814: elif element_infos["name"] == name: Laurent@814: return choices_infos Laurent@814: elif choice_infos["name"] == name: Laurent@814: return choices_infos Laurent@814: return None Laurent@814: 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@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: Laurent@814: def ExtractContentElement(factory, tree, infos, content): Laurent@814: infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"]) Laurent@814: if infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1: Laurent@814: if isinstance(content, ListType) and len(content) > 0 and \ Laurent@814: content[-1]["name"] == tree.nodeName: Laurent@814: content_item = content.pop(-1) Laurent@814: content_item["value"].append(infos["elmt_type"]["extract"](tree)) Laurent@814: return content_item Laurent@814: elif not isinstance(content, ListType) and \ Laurent@814: content is not None and \ Laurent@814: content["name"] == tree.nodeName: Laurent@814: return {"name": tree.nodeName, Laurent@814: "value": content["value"] + [infos["elmt_type"]["extract"](tree)]} Laurent@814: else: Laurent@814: return {"name": tree.nodeName, Laurent@814: "value": [infos["elmt_type"]["extract"](tree)]} Laurent@814: else: Laurent@814: return {"name": tree.nodeName, Laurent@814: "value": infos["elmt_type"]["extract"](tree)} Laurent@814: 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)) Laurent@814: elif choices_dict.has_key(element["name"]): 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: Laurent@814: if choices_dict.has_key(choice_name): Laurent@814: raise ValueError("'%s' element defined two times in choice" % choice_name) Laurent@814: choices_dict[choice_name] = infos Laurent@814: Laurent@814: def GetContentInitial(): Laurent@814: content_name, infos = choices[0] Laurent@814: if content_name == "sequence": Laurent@814: content_value = [] Laurent@814: for i in xrange(infos["minOccurs"]): Laurent@814: for element_infos in infos["elements"]: Laurent@814: value = GetElementInitialValue(factory, element_infos) Laurent@814: if value is not None: Laurent@814: if element_infos["type"] == CHOICE: Laurent@814: content_value.append(value) Laurent@814: else: Laurent@814: content_value.append({"name": element_infos["name"], "value": value}) Laurent@814: else: Laurent@814: content_value = GetElementInitialValue(factory, infos) Laurent@814: return {"name": content_name, "value": content_value} Laurent@814: Laurent@814: def CheckContent(value): Laurent@814: if value["name"] != "sequence": Laurent@814: infos = choices_dict.get(value["name"], None) Laurent@814: if infos is not None: Laurent@814: return CheckElementValue(factory, value["name"], infos, value["value"], False) Laurent@814: elif len(value["value"]) > 0: Laurent@814: infos = choices_dict.get(value["value"][0]["name"], None) Laurent@814: if infos is None: Laurent@814: for choice_name, infos in choices: Laurent@814: if infos["type"] == "sequence": Laurent@814: for element_infos in infos["elements"]: Laurent@814: if element_infos["type"] == CHOICE: Laurent@814: infos = GetContentInfos(value["value"][0]["name"], element_infos["choices"]) Laurent@814: if infos is not None: Laurent@814: sequence_number = 0 Laurent@814: element_idx = 0 Laurent@814: while element_idx < len(value["value"]): Laurent@814: for element_infos in infos["elements"]: Laurent@814: element_value = None Laurent@814: if element_infos["type"] == CHOICE: Laurent@814: choice_infos = None Laurent@814: if element_idx < len(value["value"]): Laurent@814: for choice in element_infos["choices"]: Laurent@814: if choice["name"] == value["value"][element_idx]["name"]: Laurent@814: choice_infos = choice Laurent@814: element_value = value["value"][element_idx]["value"] Laurent@814: element_idx += 1 Laurent@814: break Laurent@814: if ((choice_infos is not None and Laurent@814: not CheckElementValue(factory, choice_infos["name"], choice_infos, element_value, False)) or Laurent@814: (choice_infos is None and element_infos["minOccurs"] > 0)): Laurent@814: raise ValueError("Invalid sequence value in attribute 'content'") Laurent@814: else: Laurent@814: if element_idx < len(value["value"]) and element_infos["name"] == value["value"][element_idx]["name"]: Laurent@814: element_value = value["value"][element_idx]["value"] Laurent@814: element_idx += 1 Laurent@814: if not CheckElementValue(factory, element_infos["name"], element_infos, element_value, False): Laurent@814: raise ValueError("Invalid sequence value in attribute 'content'") Laurent@814: sequence_number += 1 Laurent@814: if sequence_number < infos["minOccurs"] or infos["maxOccurs"] != "unbounded" and sequence_number > infos["maxOccurs"]: Laurent@814: raise ValueError("Invalid sequence value in attribute 'content'") Laurent@814: return True Laurent@814: else: Laurent@814: for element_name, infos in choices: Laurent@814: if element_name == "sequence": Laurent@814: required = 0 Laurent@814: for element in infos["elements"]: Laurent@814: if element["minOccurs"] > 0: Laurent@814: required += 1 Laurent@814: if required == 0: Laurent@814: return True Laurent@814: return False Laurent@814: Laurent@814: def ExtractContent(tree, content): Laurent@814: infos = choices_dict.get(tree.nodeName, None) Laurent@814: if infos is not None: Laurent@814: if infos["name"] == "sequence": Laurent@814: sequence_dict = dict([(element_infos["name"], element_infos) for element_infos in infos["elements"] if element_infos["type"] != CHOICE]) Laurent@814: element_infos = sequence_dict.get(tree.nodeName) Laurent@814: if content is not None and \ Laurent@814: content["name"] == "sequence" and \ Laurent@814: len(content["value"]) > 0 and \ Laurent@814: choices_dict.get(content["value"][-1]["name"]) == infos: Laurent@814: return {"name": "sequence", Laurent@814: "value": content["value"] + [ExtractContentElement(factory, tree, element_infos, content["value"][-1])]} Laurent@814: else: Laurent@814: return {"name": "sequence", Laurent@814: "value": [ExtractContentElement(factory, tree, element_infos, None)]} Laurent@814: else: Laurent@814: return ExtractContentElement(factory, tree, infos, content) Laurent@814: else: Laurent@814: for choice_name, infos in choices: Laurent@814: if infos["type"] == "sequence": Laurent@814: for element_infos in infos["elements"]: Laurent@814: if element_infos["type"] == CHOICE: Laurent@814: try: Laurent@814: if content is not None and \ Laurent@814: content["name"] == "sequence" and \ Laurent@814: len(content["value"]) > 0: Laurent@814: return {"name": "sequence", Laurent@814: "value": content["value"] + [element_infos["elmt_type"]["extract"](tree, content["value"][-1])]} Laurent@814: else: Laurent@814: return {"name": "sequence", Laurent@814: "value": [element_infos["elmt_type"]["extract"](tree, None)]} Laurent@814: except: Laurent@814: pass Laurent@814: raise ValueError("Invalid element \"%s\" for content!" % tree.nodeName) Laurent@814: Laurent@814: def GenerateContent(value, name=None, indent=0): Laurent@814: text = "" Laurent@814: if value["name"] != "sequence": Laurent@814: infos = choices_dict.get(value["name"], None) Laurent@814: if infos is not None: Laurent@814: infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"]) Laurent@814: if infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1: Laurent@814: for item in value["value"]: Laurent@814: text += infos["elmt_type"]["generate"](item, value["name"], indent) Laurent@814: else: Laurent@814: text += infos["elmt_type"]["generate"](value["value"], value["name"], indent) Laurent@814: elif len(value["value"]) > 0: Laurent@814: infos = choices_dict.get(value["value"][0]["name"], None) Laurent@814: if infos is None: Laurent@814: for choice_name, infos in choices: Laurent@814: if infos["type"] == "sequence": Laurent@814: for element_infos in infos["elements"]: Laurent@814: if element_infos["type"] == CHOICE: Laurent@814: infos = GetContentInfos(value["value"][0]["name"], element_infos["choices"]) Laurent@814: if infos is not None: Laurent@814: sequence_dict = dict([(element_infos["name"], element_infos) for element_infos in infos["elements"]]) Laurent@814: for element_value in value["value"]: Laurent@814: element_infos = sequence_dict.get(element_value["name"]) Laurent@814: if element_infos["maxOccurs"] == "unbounded" or element_infos["maxOccurs"] > 1: Laurent@814: for item in element_value["value"]: Laurent@814: text += element_infos["elmt_type"]["generate"](item, element_value["name"], indent) Laurent@814: else: Laurent@814: text += element_infos["elmt_type"]["generate"](element_value["value"], element_infos["name"], indent) Laurent@814: return text Laurent@814: Laurent@814: return { Laurent@814: "type": COMPLEXTYPE, Laurent@814: "initial": GetContentInitial, Laurent@814: "check": CheckContent, Laurent@814: "extract": ExtractContent, Laurent@814: "generate": GenerateContent Laurent@814: } Laurent@814: Laurent@814: #------------------------------------------------------------------------------- Laurent@814: # Structure extraction functions Laurent@814: #------------------------------------------------------------------------------- Laurent@814: Laurent@814: Laurent@814: def DecomposeQualifiedName(name): Laurent@814: result = QName_model.match(name) Laurent@814: if not result: Laurent@814: 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 Laurent@814: Laurent@814: 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_infos = [] 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) Laurent@814: 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: Laurent@814: raise ValueError("\"%s\" can't be a member child!" % name) Laurent@814: if infos["extract"].has_key(element_name): 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: Laurent@814: """ Laurent@814: Class that generate class from an XML Tree Laurent@814: """ Laurent@814: class ClassFactory: 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 Laurent@814: Laurent@814: # Dictionary for stocking Classes and Types definitions created from Laurent@814: # the XML tree Laurent@814: self.XMLClassDefinitions = {} Laurent@814: Laurent@814: self.DefinedNamespaces = {} Laurent@814: self.Namespaces = {} Laurent@814: self.SchemaNamespace = None Laurent@814: self.TargetNamespace = None Laurent@814: Laurent@814: self.CurrentCompilations = [] Laurent@814: 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@814: self.AlreadyComputed = {} Laurent@814: Laurent@814: def GetQualifiedNameInfos(self, name, namespace=None, canbenone=False): Laurent@814: if namespace is None: Laurent@814: if self.Namespaces[self.SchemaNamespace].has_key(name): Laurent@814: return self.Namespaces[self.SchemaNamespace][name] Laurent@814: for space, elements in self.Namespaces.iteritems(): Laurent@814: if space != self.SchemaNamespace and elements.has_key(name): 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 = [] Laurent@814: if group.has_key("elements"): Laurent@814: elements = group["elements"] Laurent@814: elif group.has_key("choices"): 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) Laurent@814: elif self.Namespaces.has_key(namespace): Laurent@814: if self.Namespaces[namespace].has_key(name): 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 = [] Laurent@814: if group.has_key("elements"): Laurent@814: elements = group["elements"] Laurent@814: elif group.has_key("choices"): 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: Laurent@814: if self.Namespaces[self.SchemaNamespace].has_key(name): Laurent@814: return name, None Laurent@814: for space, elements in self.Namespaces.items(): Laurent@814: if space != self.SchemaNamespace and elements.has_key(name): 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 = [] Laurent@814: if group.has_key("elements"): Laurent@814: elements = group["elements"] Laurent@814: elif group.has_key("choices"): Laurent@814: elements = group["choices"] Laurent@814: for element in elements: Laurent@814: if element["name"] == parts[1]: Laurent@814: return part[1], part[0] Laurent@814: if not canbenone: Laurent@814: raise ValueError("Unknown element \"%s\" for any defined namespaces!" % name) Laurent@814: elif self.Namespaces.has_key(namespace): Laurent@814: if self.Namespaces[namespace].has_key(name): 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 = [] Laurent@814: if group.has_key("elements"): Laurent@814: elements = group["elements"] Laurent@814: elif group.has_key("choices"): Laurent@814: elements = group["choices"] Laurent@814: for element in elements: Laurent@814: if element["name"] == parts[1]: Laurent@814: return parts[1], parts[0] Laurent@814: if not canbenone: Laurent@814: raise ValueError("Unknown element \"%s\" for namespace \"%s\"!" % (name, namespace)) Laurent@814: elif not canbenone: Laurent@814: raise ValueError("Unknown namespace \"%s\"!" % namespace) Laurent@814: return None, None Laurent@814: Laurent@814: def ExtractNodeAttrs(self, element_name, node, valid_attrs): Laurent@814: attrs = {} Laurent@814: for qualified_name, attr in node._attrs.items(): Laurent@814: namespace, name = DecomposeQualifiedName(qualified_name) Laurent@814: if name in valid_attrs: Laurent@814: infos = self.GetQualifiedNameInfos(name, namespace) Laurent@814: if infos["type"] != SYNTAXATTRIBUTE: Laurent@814: raise ValueError("\"%s\" can't be a member attribute!" % name) Laurent@814: elif name in attrs: Laurent@814: raise ValueError("\"%s\" attribute has been twice!" % name) Laurent@814: elif element_name in infos["extract"]: Laurent@814: attrs[name] = infos["extract"][element_name](attr) Laurent@814: else: Laurent@814: attrs[name] = infos["extract"]["default"](attr) Laurent@814: elif namespace == "xmlns": Laurent@814: infos = self.GetQualifiedNameInfos("anyURI", self.SchemaNamespace) Laurent@814: self.DefinedNamespaces[infos["extract"](attr)] = name Laurent@814: else: Laurent@814: raise ValueError("Invalid attribute \"%s\" for member \"%s\"!" % (qualified_name, node.nodeName)) Laurent@814: for attr in valid_attrs: Laurent@814: if attr not in attrs and \ Laurent@814: self.Namespaces[self.SchemaNamespace].has_key(attr) and \ Laurent@814: self.Namespaces[self.SchemaNamespace][attr].has_key("default"): Laurent@814: if self.Namespaces[self.SchemaNamespace][attr]["default"].has_key(element_name): 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: Laurent@814: if child_infos[1].has_key("name") 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) Laurent@814: if child_infos[1].has_key("name") 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): Laurent@814: if not self.XMLClassDefinitions.has_key(typename): 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 Laurent@814: Laurent@814: def ExtractTypeInfos(self, name, parent, typeinfos): Laurent@814: if isinstance(typeinfos, (StringType, UnicodeType)): Laurent@814: namespace, name = DecomposeQualifiedName(typeinfos) Laurent@814: infos = self.GetQualifiedNameInfos(name, namespace) Laurent@814: if infos["type"] == COMPLEXTYPE: Laurent@814: name, parent = self.SplitQualifiedName(name, namespace) Laurent@814: result = self.CreateClass(name, parent, infos) Laurent@814: if result is not None and not isinstance(result, (UnicodeType, StringType)): 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@814: name, parent = self.SplitQualifiedName(name, namespace) Laurent@814: result = self.CreateClass(name, parent, infos["elmt_type"]) Laurent@814: if result is not None and not isinstance(result, (UnicodeType, StringType)): 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 Laurent@814: Laurent@814: """ Laurent@814: Methods that generates the classes Laurent@814: """ Laurent@814: def CreateClasses(self): Laurent@814: self.ParseSchema() Laurent@814: for name, infos in self.Namespaces[self.TargetNamespace].items(): Laurent@814: if infos["type"] == ELEMENT: Laurent@814: if not isinstance(infos["elmt_type"], (UnicodeType, StringType)) 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)) Laurent@814: if result is not None and not isinstance(result, (UnicodeType, StringType)): 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 \ Laurent@814: not isinstance(result, (UnicodeType, StringType)): Laurent@814: self.Namespaces[self.TargetNamespace][result["name"]] = result Laurent@814: elif infos["type"] == ELEMENTSGROUP: Laurent@814: elements = [] Laurent@814: if infos.has_key("elements"): Laurent@814: elements = infos["elements"] Laurent@814: elif infos.has_key("choices"): Laurent@814: elements = infos["choices"] Laurent@814: for element in elements: Laurent@814: if not isinstance(element["elmt_type"], (UnicodeType, StringType)) 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 \ Laurent@814: not isinstance(result, (UnicodeType, StringType)): Laurent@814: self.Namespaces[self.TargetNamespace][result["name"]] = result Laurent@814: return self.ComputedClasses Laurent@814: Laurent@814: 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 Laurent@814: Laurent@814: # Checks that classe haven't been generated yet Laurent@814: if self.AlreadyComputed.get(classname, False): Laurent@814: if baseclass: Laurent@814: self.AlreadyComputed[classname].IsBaseClass = baseclass Laurent@814: return self.ComputedClassesInfos.get(classname, None) Laurent@814: 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@814: result = self.ExtractTypeInfos("base", name, base_infos) Laurent@814: if result is None: Laurent@814: namespace, base_name = DecomposeQualifiedName(base_infos) Laurent@814: if self.AlreadyComputed.get(base_name, False): Laurent@814: self.ComputeAfter.append((name, parent, classinfos)) Laurent@814: if self.TargetNamespace is not None: Laurent@814: return "%s:%s" % (self.TargetNamespace, classname) Laurent@814: else: Laurent@814: return classname Laurent@814: elif result is not None: Laurent@814: if self.FileName is not None: Laurent@814: classinfos["base"] = self.ComputedClasses[self.FileName].get(result["name"], None) Laurent@814: if classinfos["base"] is None: Laurent@814: for filename, classes in self.ComputedClasses.iteritems(): Laurent@814: if filename != self.FileName: Laurent@814: classinfos["base"] = classes.get(result["name"], None) Laurent@814: if classinfos["base"] is not None: Laurent@814: break Laurent@814: else: Laurent@814: classinfos["base"] = self.ComputedClasses.get(result["name"], None) Laurent@814: if classinfos["base"] is None: Laurent@814: raise ValueError("No class found for base type") Laurent@814: bases.append(classinfos["base"]) Laurent@814: bases.append(object) Laurent@814: bases = tuple(bases) Laurent@814: classmembers = {"__doc__": classinfos.get("doc", ""), "IsBaseClass": baseclass} Laurent@814: Laurent@814: self.AlreadyComputed[classname] = True Laurent@814: Laurent@814: for attribute in classinfos["attributes"]: Laurent@814: infos = self.ExtractTypeInfos(attribute["name"], name, attribute["attr_type"]) Laurent@814: 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": Laurent@814: classmembers[attrname] = None Laurent@814: classmembers["add%s"%attrname] = generateAddMethod(attrname, self, attribute) Laurent@814: classmembers["delete%s"%attrname] = generateDeleteMethod(attrname) Laurent@814: else: Laurent@814: classmembers[attrname] = infos["initial"]() Laurent@814: classmembers["set%s"%attrname] = generateSetMethod(attrname) Laurent@814: 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 Laurent@814: 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) Laurent@814: 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@814: elmtname = element["name"] = "text" 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@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[elmtname] = [] 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[elmtname] = None Laurent@814: classmembers["add%s" % elmtname] = generateAddMethod(elmtname, self, element) Laurent@814: classmembers["delete%s" % elmtname] = generateDeleteMethod(elmtname) Laurent@814: elif not isinstance(element["elmt_type"], (UnicodeType, StringType)): Laurent@814: classmembers[elmtname] = element["elmt_type"]["initial"]() Laurent@814: else: Laurent@814: classmembers[elmtname] = None Laurent@814: classmembers["set%s" % elmtname] = generateSetMethod(elmtname) Laurent@814: classmembers["get%s" % elmtname] = generateGetMethod(elmtname) Laurent@814: Laurent@814: classmembers["__init__"] = generateInitMethod(self, classinfos) Laurent@814: classmembers["getStructure"] = generateStructureMethod(classinfos) Laurent@814: classmembers["loadXMLTree"] = generateLoadXMLTree(self, classinfos) Laurent@814: classmembers["generateXMLText"] = generateGenerateXMLText(self, classinfos) Laurent@814: classmembers["getElementAttributes"] = generateGetElementAttributes(self, classinfos) Laurent@814: classmembers["getElementInfos"] = generateGetElementInfos(self, classinfos) Laurent@814: classmembers["setElementValue"] = generateSetElementValue(self, classinfos) Laurent@814: classmembers["singleLineAttributes"] = True Laurent@814: classmembers["compatibility"] = lambda x, y: None Laurent@814: classmembers["extraAttrs"] = {} Laurent@814: Laurent@814: class_definition = classobj(str(classname), bases, classmembers) Laurent@814: setattr(class_definition, "__setattr__", generateSetattrMethod(self, class_definition, classinfos)) Laurent@814: class_infos = {"type": COMPILEDCOMPLEXTYPE, Laurent@814: "name": classname, Laurent@814: "check": generateClassCheckFunction(class_definition), Laurent@814: "initial": generateClassCreateFunction(class_definition), Laurent@814: "extract": generateClassExtractFunction(class_definition), Laurent@814: "generate": class_definition.generateXMLText} Laurent@814: 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 Laurent@814: Laurent@814: return class_infos Laurent@814: Laurent@814: """ Laurent@814: Methods that print the classes generated Laurent@814: """ Laurent@814: def PrintClasses(self): Laurent@814: items = self.ComputedClasses.items() Laurent@814: items.sort() Laurent@814: if self.FileName is not None: Laurent@814: for filename, classes in items: Laurent@814: print "File '%s':" % filename Laurent@814: class_items = classes.items() Laurent@814: class_items.sort() Laurent@814: for classname, xmlclass in class_items: Laurent@814: print "%s: %s" % (classname, str(xmlclass)) Laurent@814: else: Laurent@814: for classname, xmlclass in items: Laurent@814: print "%s: %s" % (classname, str(xmlclass)) Laurent@814: Laurent@814: def PrintClassNames(self): Laurent@814: classnames = self.XMLClassDefinitions.keys() Laurent@814: classnames.sort() Laurent@814: for classname in classnames: Laurent@814: print classname Laurent@814: Laurent@814: """ Laurent@814: Method that generate the method for checking a class instance Laurent@814: """ Laurent@814: def generateClassCheckFunction(class_definition): Laurent@814: def classCheckfunction(instance): Laurent@814: return isinstance(instance, class_definition) Laurent@814: return classCheckfunction Laurent@814: Laurent@814: """ Laurent@814: Method that generate the method for creating a class instance Laurent@814: """ Laurent@814: def generateClassCreateFunction(class_definition): Laurent@814: def classCreatefunction(): Laurent@814: return class_definition() Laurent@814: return classCreatefunction Laurent@814: Laurent@814: """ Laurent@814: Method that generate the method for extracting a class instance Laurent@814: """ Laurent@814: def generateClassExtractFunction(class_definition): Laurent@814: def classExtractfunction(node): Laurent@814: instance = class_definition() Laurent@814: instance.loadXMLTree(node) Laurent@814: return instance Laurent@814: return classExtractfunction Laurent@814: Laurent@814: """ Laurent@814: Method that generate the method for loading an xml tree by following the Laurent@814: attributes list defined Laurent@814: """ 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@814: elements = dict([(element["name"], element) for element in classinfos["elements"]]) Laurent@814: Laurent@814: def setattrMethod(self, name, value): Laurent@814: if attributes.has_key(name): Laurent@814: attributes[name]["attr_type"] = FindTypeInfos(factory, attributes[name]["attr_type"]) Laurent@814: if value is None: Laurent@814: if optional_attributes.get(name, False): Laurent@814: return object.__setattr__(self, name, None) Laurent@814: else: Laurent@814: raise ValueError("Attribute '%s' isn't optional." % name) Laurent@814: elif attributes[name].has_key("fixed") and value != attributes[name]["fixed"]: Laurent@814: raise ValueError, "Value of attribute '%s' can only be '%s'."%(name, str(attributes[name]["fixed"])) Laurent@814: elif attributes[name]["attr_type"]["check"](value): Laurent@814: return object.__setattr__(self, name, value) Laurent@814: else: Laurent@814: raise ValueError("Invalid value for attribute '%s'." % (name)) Laurent@814: elif elements.has_key(name): Laurent@814: if CheckElementValue(factory, name, elements[name], value): Laurent@814: return object.__setattr__(self, name, value) Laurent@814: else: Laurent@814: raise ValueError("Invalid value for attribute '%s'." % (name)) Laurent@814: elif classinfos.has_key("base"): Laurent@814: return classinfos["base"].__setattr__(self, name, value) Laurent@814: elif class_definition.__dict__.has_key(name): Laurent@814: return object.__setattr__(self, name, value) Laurent@814: else: Laurent@814: raise AttributeError("'%s' can't have an attribute '%s'." % (self.__class__.__name__, name)) Laurent@814: Laurent@814: return setattrMethod Laurent@814: Laurent@814: """ Laurent@814: Method that generate the method for generating the xml tree structure model by Laurent@814: following the attributes list defined Laurent@814: """ Laurent@814: def ComputeMultiplicity(name, infos): Laurent@814: if infos["minOccurs"] == 0: Laurent@814: if infos["maxOccurs"] == "unbounded": Laurent@814: return "(?:%s)*" % name Laurent@814: elif infos["maxOccurs"] == 1: Laurent@814: return "(?:%s)?" % name Laurent@814: else: Laurent@814: return "(?:%s){,%d}" % (name, infos["maxOccurs"]) Laurent@814: elif infos["minOccurs"] == 1: Laurent@814: if infos["maxOccurs"] == "unbounded": Laurent@814: return "(?:%s)+" % name Laurent@814: elif infos["maxOccurs"] == 1: Laurent@814: return "(?:%s)" % name Laurent@814: else: Laurent@814: return "(?:%s){1,%d}" % (name, infos["maxOccurs"]) Laurent@814: else: Laurent@814: if infos["maxOccurs"] == "unbounded": Laurent@814: return "(?:%s){%d,}" % (name, infos["minOccurs"], name) Laurent@814: else: Laurent@814: return "(?:%s){%d,%d}" % (name, infos["minOccurs"], Laurent@814: infos["maxOccurs"]) Laurent@814: Laurent@814: def GetStructure(classinfos): Laurent@814: elements = [] Laurent@814: for element in classinfos["elements"]: Laurent@814: if element["type"] == ANY: Laurent@814: infos = element.copy() Laurent@814: infos["minOccurs"] = 0 Laurent@814: elements.append(ComputeMultiplicity("#text |#cdata-section |\w* ", infos)) Laurent@814: elif element["type"] == CHOICE: Laurent@814: choices = [] Laurent@814: for infos in element["choices"]: Laurent@814: if infos["type"] == "sequence": Laurent@814: structure = "(?:%s)" % GetStructure(infos) Laurent@814: else: Laurent@814: structure = "%s " % infos["name"] Laurent@814: choices.append(ComputeMultiplicity(structure, infos)) Laurent@814: elements.append(ComputeMultiplicity("|".join(choices), element)) Laurent@814: elif element["name"] == "content" and element["elmt_type"]["type"] == SIMPLETYPE: Laurent@814: elements.append("(?:#text |#cdata-section )?") Laurent@814: else: Laurent@814: elements.append(ComputeMultiplicity("%s " % element["name"], element)) Laurent@814: if classinfos.get("order", True) or len(elements) == 0: Laurent@814: return "".join(elements) Laurent@814: else: Laurent@814: raise ValueError("XSD structure not yet supported!") Laurent@814: Laurent@814: def generateStructureMethod(classinfos): Laurent@814: def getStructureMethod(self): Laurent@814: structure = GetStructure(classinfos) Laurent@814: if classinfos.has_key("base"): Laurent@814: return classinfos["base"].getStructure(self) + structure Laurent@814: return structure Laurent@814: return getStructureMethod Laurent@814: Laurent@814: """ Laurent@814: Method that generate the method for loading an xml tree by following the Laurent@814: attributes list defined Laurent@814: """ Laurent@814: def generateLoadXMLTree(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"]]) Laurent@814: Laurent@814: def loadXMLTreeMethod(self, tree, extras=[], derived=False): Laurent@814: self.extraAttrs = {} Laurent@814: self.compatibility(tree) Laurent@814: if not derived: Laurent@814: children_structure = "" Laurent@814: for node in tree.childNodes: Laurent@814: if not (node.nodeName == "#text" and node.data.strip() == "") and node.nodeName != "#comment": Laurent@814: children_structure += "%s " % node.nodeName Laurent@814: structure_pattern = self.getStructure() Laurent@814: if structure_pattern != "": Laurent@814: structure_model = re.compile("(%s)$" % structure_pattern) Laurent@814: result = structure_model.match(children_structure) Laurent@814: if not result: Laurent@814: raise ValueError("Invalid structure for \"%s\" children!." % tree.nodeName) Laurent@814: required_attributes = dict([(attr["name"], True) for attr in classinfos["attributes"] if attr["use"] == "required"]) Laurent@814: if classinfos.has_key("base"): Laurent@814: extras.extend([attr["name"] for attr in classinfos["attributes"] if attr["use"] != "prohibited"]) Laurent@814: classinfos["base"].loadXMLTree(self, tree, extras, True) Laurent@814: for attrname, attr in tree._attrs.iteritems(): Laurent@814: if attributes.has_key(attrname): Laurent@814: attributes[attrname]["attr_type"] = FindTypeInfos(factory, attributes[attrname]["attr_type"]) Laurent@814: object.__setattr__(self, attrname, attributes[attrname]["attr_type"]["extract"](attr)) Laurent@814: elif not classinfos.has_key("base") and not attrname in extras and not self.extraAttrs.has_key(attrname): Laurent@814: self.extraAttrs[attrname] = GetAttributeValue(attr) Laurent@814: required_attributes.pop(attrname, None) Laurent@814: if len(required_attributes) > 0: Laurent@814: raise ValueError("Required attributes %s missing for \"%s\" element!" % (", ".join(["\"%s\""%name for name in required_attributes]), tree.nodeName)) Laurent@814: first = {} Laurent@814: for node in tree.childNodes: Laurent@814: name = node.nodeName Laurent@814: if name == "#text" and node.data.strip() == "" or name == "#comment": Laurent@814: continue Laurent@814: elif elements.has_key(name): Laurent@814: elements[name]["elmt_type"] = FindTypeInfos(factory, elements[name]["elmt_type"]) Laurent@814: if elements[name]["maxOccurs"] == "unbounded" or elements[name]["maxOccurs"] > 1: Laurent@814: if first.get(name, True): Laurent@814: object.__setattr__(self, name, [elements[name]["elmt_type"]["extract"](node)]) Laurent@814: first[name] = False Laurent@814: else: Laurent@814: getattr(self, name).append(elements[name]["elmt_type"]["extract"](node)) Laurent@814: else: Laurent@814: object.__setattr__(self, name, elements[name]["elmt_type"]["extract"](node)) Laurent@814: elif elements.has_key("text"): Laurent@814: if elements["text"]["maxOccurs"] == "unbounded" or elements["text"]["maxOccurs"] > 1: Laurent@814: if first.get("text", True): Laurent@814: object.__setattr__(self, "text", [elements["text"]["elmt_type"]["extract"](node)]) Laurent@814: first["text"] = False Laurent@814: else: Laurent@814: getattr(self, "text").append(elements["text"]["elmt_type"]["extract"](node)) Laurent@814: else: Laurent@814: object.__setattr__(self, "text", elements["text"]["elmt_type"]["extract"](node)) Laurent@814: elif elements.has_key("content"): Laurent@814: if name in ["#cdata-section", "#text"]: Laurent@814: if elements["content"]["elmt_type"]["type"] == SIMPLETYPE: Laurent@814: object.__setattr__(self, "content", elements["content"]["elmt_type"]["extract"](node.data, False)) Laurent@814: else: Laurent@814: content = getattr(self, "content") Laurent@814: if elements["content"]["maxOccurs"] == "unbounded" or elements["content"]["maxOccurs"] > 1: Laurent@814: if first.get("content", True): Laurent@814: object.__setattr__(self, "content", [elements["content"]["elmt_type"]["extract"](node, None)]) Laurent@814: first["content"] = False Laurent@814: else: Laurent@814: content.append(elements["content"]["elmt_type"]["extract"](node, content)) Laurent@814: else: Laurent@814: object.__setattr__(self, "content", elements["content"]["elmt_type"]["extract"](node, content)) Laurent@814: return loadXMLTreeMethod Laurent@814: Laurent@814: Laurent@814: """ Laurent@814: Method that generates the method for generating an xml text by following the Laurent@814: attributes list defined Laurent@814: """ Laurent@814: def generateGenerateXMLText(factory, classinfos): Laurent@814: def generateXMLTextMethod(self, name, indent=0, extras={}, derived=False): Laurent@814: ind1, ind2 = getIndent(indent, name) Laurent@814: if not derived: Laurent@814: text = ind1 + u'<%s' % name Laurent@814: else: Laurent@814: text = u'' Laurent@814: Laurent@814: first = True Laurent@814: Laurent@814: if not classinfos.has_key("base"): Laurent@814: extras.update(self.extraAttrs) Laurent@814: for attr, value in extras.iteritems(): Laurent@814: if not first and not self.singleLineAttributes: Laurent@814: text += u'\n%s' % (ind2) Laurent@814: text += u' %s=%s' % (attr, quoteattr(value)) Laurent@814: first = False Laurent@814: extras.clear() Laurent@814: for attr in classinfos["attributes"]: Laurent@814: if attr["use"] != "prohibited": Laurent@814: attr["attr_type"] = FindTypeInfos(factory, attr["attr_type"]) Laurent@814: value = getattr(self, attr["name"], None) Laurent@814: if value != None: Laurent@814: computed_value = attr["attr_type"]["generate"](value) Laurent@814: else: Laurent@814: computed_value = None Laurent@814: if attr["use"] != "optional" or (value != None and \ Laurent@814: computed_value != attr.get("default", attr["attr_type"]["generate"](attr["attr_type"]["initial"]()))): Laurent@814: if classinfos.has_key("base"): Laurent@814: extras[attr["name"]] = computed_value Laurent@814: else: Laurent@814: if not first and not self.singleLineAttributes: Laurent@814: text += u'\n%s' % (ind2) Laurent@814: text += ' %s=%s' % (attr["name"], quoteattr(computed_value)) Laurent@814: first = False Laurent@814: if classinfos.has_key("base"): Laurent@814: first, new_text = classinfos["base"].generateXMLText(self, name, indent, extras, True) Laurent@814: text += new_text Laurent@814: else: Laurent@814: first = True Laurent@814: for element in classinfos["elements"]: Laurent@814: element["elmt_type"] = FindTypeInfos(factory, element["elmt_type"]) Laurent@814: value = getattr(self, element["name"], None) Laurent@814: if element["minOccurs"] == 0 and element["maxOccurs"] == 1: Laurent@814: if value is not None: Laurent@814: if first: Laurent@814: text += u'>\n' Laurent@814: first = False Laurent@814: text += element["elmt_type"]["generate"](value, element["name"], indent + 1) Laurent@814: elif element["minOccurs"] == 1 and element["maxOccurs"] == 1: Laurent@814: if first: Laurent@814: text += u'>\n' Laurent@814: first = False Laurent@814: if element["name"] == "content" and element["elmt_type"]["type"] == SIMPLETYPE: Laurent@814: text += element["elmt_type"]["generate"](value) Laurent@814: else: Laurent@814: text += element["elmt_type"]["generate"](value, element["name"], indent + 1) Laurent@814: else: Laurent@814: if first and len(value) > 0: Laurent@814: text += u'>\n' Laurent@814: first = False Laurent@814: for item in value: Laurent@814: text += element["elmt_type"]["generate"](item, element["name"], indent + 1) Laurent@814: if not derived: Laurent@814: if first: Laurent@814: text += u'/>\n' Laurent@814: else: Laurent@814: text += ind1 + u'\n' % (name) Laurent@814: return text Laurent@814: else: Laurent@814: return first, text Laurent@814: return generateXMLTextMethod Laurent@814: Laurent@814: def gettypeinfos(name, facets): Laurent@814: if facets.has_key("enumeration") and facets["enumeration"][0] is not None: Laurent@814: return facets["enumeration"][0] Laurent@814: elif facets.has_key("maxInclusive"): Laurent@814: 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: Laurent@814: def generateGetElementAttributes(factory, classinfos): Laurent@814: def getElementAttributes(self): Laurent@814: attr_list = [] Laurent@814: if classinfos.has_key("base"): Laurent@814: attr_list.extend(classinfos["base"].getElementAttributes(self)) Laurent@814: for attr in classinfos["attributes"]: Laurent@814: if attr["use"] != "prohibited": Laurent@814: attr_params = {"name" : attr["name"], "use" : attr["use"], Laurent@814: "type" : gettypeinfos(attr["attr_type"]["basename"], attr["attr_type"]["facets"]), Laurent@814: "value" : getattr(self, attr["name"], "")} Laurent@814: attr_list.append(attr_params) Laurent@814: return attr_list Laurent@814: return getElementAttributes Laurent@814: 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"]]) Laurent@814: 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) Laurent@814: if attributes.has_key(parts[0]): Laurent@814: if len(parts) != 0: Laurent@814: raise ValueError("Wrong path!") Laurent@814: attr_type = gettypeinfos(attributes[parts[0]]["attr_type"]["basename"], Laurent@814: attributes[parts[0]]["attr_type"]["facets"]) Laurent@814: value = getattr(self, parts[0], "") Laurent@814: elif elements.has_key(parts[0]): Laurent@814: if elements[parts[0]]["elmt_type"]["type"] == SIMPLETYPE: Laurent@814: if len(parts) != 0: Laurent@814: raise ValueError("Wrong path!") Laurent@814: 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@814: return self.content["value"].getElementInfos(self.content["name"], path) Laurent@814: else: Laurent@814: attr = getattr(self, parts[0], None) Laurent@814: if attr is None: Laurent@814: raise ValueError("Wrong path!") Laurent@814: if len(parts) == 1: Laurent@814: return attr.getElementInfos(parts[0]) Laurent@814: else: Laurent@814: return attr.getElementInfos(parts[0], parts[1]) Laurent@814: else: Laurent@814: raise ValueError("Wrong path!") Laurent@814: else: Laurent@814: if not derived: Laurent@814: children.extend(self.getElementAttributes()) Laurent@814: if classinfos.has_key("base"): Laurent@814: children.extend(classinfos["base"].getElementInfos(self, name, derived=True)["children"]) Laurent@814: for element_name, element in elements.items(): Laurent@814: if element["minOccurs"] == 0: Laurent@814: use = "optional" Laurent@814: if element_name == "content" and element["type"] == CHOICE: Laurent@814: attr_type = [(choice["name"], None) for choice in element["choices"]] Laurent@814: if self.content is None: Laurent@814: value = "" Laurent@814: else: Laurent@814: value = self.content["name"] Laurent@814: if self.content["value"] is not None: Laurent@814: if self.content["name"] == "sequence": Laurent@814: choices_dict = dict([(choice["name"], choice) for choice in element["choices"]]) Laurent@814: sequence_infos = choices_dict.get("sequence", None) Laurent@814: if sequence_infos is not None: Laurent@814: children.extend([item.getElementInfos(infos["name"]) for item, infos in zip(self.content["value"], sequence_infos["elements"])]) Laurent@814: else: Laurent@814: children.extend(self.content["value"].getElementInfos(self.content["name"])["children"]) Laurent@814: elif element["elmt_type"]["type"] == SIMPLETYPE: Laurent@814: children.append({"name": element_name, "require": element["minOccurs"] != 0, Laurent@814: "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: 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"]]) Laurent@814: Laurent@814: def setElementValue(self, path, value): Laurent@814: if path is not None: Laurent@814: parts = path.split(".", 1) Laurent@814: if attributes.has_key(parts[0]): Laurent@814: if len(parts) != 1: Laurent@814: raise ValueError("Wrong path!") Laurent@814: if attributes[parts[0]]["attr_type"]["basename"] == "boolean": Laurent@814: setattr(self, parts[0], value) Laurent@1017: elif attributes[parts[0]]["use"] == "optional" and value == "": Laurent@1022: if attributes[parts[0]].has_key("default"): Laurent@1022: setattr(self, parts[0], Laurent@1022: attributes[parts[0]]["attr_type"]["extract"]( Laurent@1022: 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)) Laurent@814: elif elements.has_key(parts[0]): Laurent@814: if elements[parts[0]]["elmt_type"]["type"] == SIMPLETYPE: Laurent@814: if len(parts) != 1: Laurent@814: raise ValueError("Wrong path!") Laurent@814: if elements[parts[0]]["elmt_type"]["basename"] == "boolean": Laurent@814: setattr(self, parts[0], value) Laurent@1017: elif attributes[parts[0]]["minOccurs"] == 0 and value == "": Laurent@1017: setattr(self, parts[0], None) Laurent@814: else: Laurent@814: setattr(self, parts[0], elements[parts[0]]["elmt_type"]["extract"](value, False)) Laurent@814: else: Laurent@814: instance = getattr(self, parts[0], None) Laurent@814: if instance is None and elements[parts[0]]["minOccurs"] == 0: Laurent@814: instance = elements[parts[0]]["elmt_type"]["initial"]() Laurent@814: setattr(self, parts[0], instance) Laurent@814: if instance != None: Laurent@814: if len(parts) > 1: Laurent@814: instance.setElementValue(parts[1], value) Laurent@814: else: Laurent@814: instance.setElementValue(None, value) Laurent@814: elif elements.has_key("content"): Laurent@814: if len(parts) > 0: Laurent@814: self.content["value"].setElementValue(path, value) Laurent@814: elif classinfos.has_key("base"): Laurent@814: classinfos["base"].setElementValue(self, path, value) Laurent@814: elif elements.has_key("content"): Laurent@814: if value == "": Laurent@814: if elements["content"]["minOccurs"] == 0: Laurent@814: self.setcontent(None) 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: Laurent@814: """ Laurent@814: Methods that generates the different methods for setting and getting the attributes Laurent@814: """ Laurent@814: def generateInitMethod(factory, classinfos): Laurent@814: def initMethod(self): Laurent@814: self.extraAttrs = {} Laurent@814: if classinfos.has_key("base"): Laurent@814: classinfos["base"].__init__(self) Laurent@814: for attribute in classinfos["attributes"]: Laurent@814: attribute["attr_type"] = FindTypeInfos(factory, attribute["attr_type"]) Laurent@814: if attribute["use"] == "required": Laurent@814: setattr(self, attribute["name"], attribute["attr_type"]["initial"]()) Laurent@814: elif attribute["use"] == "optional": Laurent@814: if attribute.has_key("default"): Laurent@814: setattr(self, attribute["name"], attribute["attr_type"]["extract"](attribute["default"], False)) Laurent@814: else: Laurent@814: setattr(self, attribute["name"], None) Laurent@814: for element in classinfos["elements"]: Laurent@814: setattr(self, element["name"], GetElementInitialValue(factory, element)) Laurent@814: return initMethod Laurent@814: Laurent@814: def generateSetMethod(attr): Laurent@814: def setMethod(self, value): Laurent@814: setattr(self, attr, value) Laurent@814: return setMethod Laurent@814: Laurent@814: def generateGetMethod(attr): Laurent@814: def getMethod(self): Laurent@814: return getattr(self, attr, None) Laurent@814: return getMethod Laurent@814: 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"]) Laurent@814: initial = infos["attr_type"]["initial"] Laurent@814: extract = infos["attr_type"]["extract"] Laurent@814: elif infos["type"] == ELEMENT: Laurent@814: infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"]) Laurent@814: initial = infos["elmt_type"]["initial"] Laurent@814: extract = infos["elmt_type"]["extract"] Laurent@814: else: Laurent@814: raise ValueError("Invalid class attribute!") Laurent@814: if infos.has_key("default"): Laurent@814: setattr(self, attr, extract(infos["default"], False)) Laurent@814: else: Laurent@814: setattr(self, attr, initial()) Laurent@814: return addMethod Laurent@814: Laurent@814: def generateDeleteMethod(attr): Laurent@814: def deleteMethod(self): Laurent@814: setattr(self, attr, None) Laurent@814: return deleteMethod Laurent@814: 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@814: if infos["elmt_type"]["check"](value): Laurent@814: attr_list.append(value) Laurent@814: else: Laurent@814: raise ValueError("\"%s\" value isn't valid!" % attr) 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: 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@814: if infos["elmt_type"]["check"](value): Laurent@814: attr_list.insert(index, value) Laurent@814: else: Laurent@814: raise ValueError("\"%s\" value isn't valid!" % attr) 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: 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: Laurent@814: def generateSetChoiceByTypeMethod(factory, choice_types): Laurent@814: choices = dict([(choice["name"], choice) for choice in choice_types]) Laurent@814: def setChoiceMethod(self, type): Laurent@814: if not choices.has_key(type): Laurent@814: raise ValueError("Unknown \"%s\" choice type for \"content\"!" % type) Laurent@814: choices[type]["elmt_type"] = FindTypeInfos(factory, choices[type]["elmt_type"]) Laurent@814: new_element = choices[type]["elmt_type"]["initial"]() Laurent@814: self.content = {"name": type, "value": new_element} Laurent@814: return new_element Laurent@814: return setChoiceMethod Laurent@814: Laurent@814: def generateAppendChoiceByTypeMethod(maxOccurs, factory, choice_types): Laurent@814: choices = dict([(choice["name"], choice) for choice in choice_types]) Laurent@814: def appendChoiceMethod(self, type): Laurent@814: if not choices.has_key(type): Laurent@814: raise ValueError("Unknown \"%s\" choice type for \"content\"!" % type) Laurent@814: choices[type]["elmt_type"] = FindTypeInfos(factory, choices[type]["elmt_type"]) Laurent@814: if maxOccurs == "unbounded" or len(self.content) < maxOccurs: Laurent@814: new_element = choices[type]["elmt_type"]["initial"]() Laurent@814: self.content.append({"name": type, "value": 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: Laurent@814: def generateInsertChoiceByTypeMethod(maxOccurs, factory, choice_types): Laurent@814: choices = dict([(choice["name"], choice) for choice in choice_types]) Laurent@814: def insertChoiceMethod(self, index, type): Laurent@814: if not choices.has_key(type): Laurent@814: raise ValueError("Unknown \"%s\" choice type for \"content\"!" % type) Laurent@814: choices[type]["elmt_type"] = FindTypeInfos(factory, choices[type]["elmt_type"]) Laurent@814: if maxOccurs == "unbounded" or len(self.content) < maxOccurs: Laurent@814: new_element = choices[type]["elmt_type"]["initial"]() Laurent@814: self.content.insert(index, {"name" : type, "value" : 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: 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@814: getattr(self, attr).pop(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: Laurent@814: def generateCountMethod(attr): Laurent@814: def countMethod(self): Laurent@814: return len(getattr(self, attr)) Laurent@814: return countMethod Laurent@814: Laurent@814: """ Laurent@814: This function generate the classes from a class factory Laurent@814: """ Laurent@814: def GenerateClasses(factory): Laurent@814: ComputedClasses = factory.CreateClasses() Laurent@814: if factory.FileName is not None and len(ComputedClasses) == 1: Laurent@1124: UpdateXMLClassGlobals(ComputedClasses[factory.FileName]) Laurent@814: return ComputedClasses[factory.FileName] Laurent@814: else: Laurent@1124: UpdateXMLClassGlobals(ComputedClasses) Laurent@814: return ComputedClasses Laurent@814: Laurent@1124: def UpdateXMLClassGlobals(classes): Laurent@1124: globals().update(classes)