diff -r a2ab363f9e90 -r 96ca6b056c55 xmlclass/xmlclass.py --- a/xmlclass/xmlclass.py Fri Aug 24 13:25:05 2018 +0300 +++ b/xmlclass/xmlclass.py Fri Aug 24 13:41:43 2018 +0300 @@ -618,8 +618,9 @@ element_name = factory.etreeNamespaceFormat % infos["name"] if infos["elmt_type"]["type"] == SIMPLETYPE: def initial_value(): - value = etree.Element(element_name) + value = factory.Parser.makeelement(element_name) value.text = (infos["elmt_type"]["generate"](infos["elmt_type"]["initial"]())) + value._init_() return value else: def initial_value(): @@ -776,6 +777,7 @@ self.SchemaNamespace = None self.TargetNamespace = None self.etreeNamespaceFormat = "%s" + self.Parser = None self.CurrentCompilations = [] @@ -1162,7 +1164,6 @@ classmembers["get%s" % elmtname] = generateGetMethod(elmtname) classmembers["_init_"] = generateInitMethod(self, classinfos) - classmembers["_tmp_initial_"] = None classmembers["StructurePattern"] = GetStructurePattern(classinfos) classmembers["getElementAttributes"] = generateGetElementAttributes(self, classinfos) classmembers["getElementInfos"] = generateGetElementInfos(self, classinfos) @@ -1174,9 +1175,8 @@ class_infos = { "type": COMPILEDCOMPLEXTYPE, "name": classname, - "initial": generateClassCreateFunction(class_definition), + "initial": generateClassCreateFunction(self, class_definition), } - if self.FileName is not None: self.ComputedClasses[self.FileName][classname] = class_definition else: @@ -1269,12 +1269,12 @@ raise ValueError("XSD structure not yet supported!") -def generateClassCreateFunction(class_definition): +def generateClassCreateFunction(factory, class_definition): """ Method that generate the method for creating a class instance """ def classCreatefunction(): - return class_definition() + return factory.Parser.CreateElementFromClass(class_definition) return classCreatefunction @@ -1387,7 +1387,7 @@ for element in reversed(value): if element_infos["elmt_type"]["type"] == SIMPLETYPE: - tmp_element = etree.Element(factory.etreeNamespaceFormat % name) + tmp_element = factory.Parser.makeelement(factory.etreeNamespaceFormat % name) tmp_element.text = element_infos["elmt_type"]["generate"](element) element = tmp_element self.insert(insertion_point, element) @@ -1582,10 +1582,6 @@ if element["type"] != CHOICE: initial = GetElementInitialValue(factory, element) if initial is not None: - # FIXME: this is looks like dirty hack to fix strange problem with initial[0] - # changing its type after returning from _init_ method to lxml.etree._Element - # As a result all methods generated by class factory are lost. - object.__setattr__(self, "_tmp_initial_", initial) map(self.append, initial) return initMethod @@ -1747,6 +1743,8 @@ def __init__(self, classes, *args, **kwargs): etree.PythonElementClassLookup.__init__(self, *args, **kwargs) self.LookUpClasses = classes + self.ElementTag = None + self.ElementClass = None def GetElementClass(self, element_tag, parent_tag=None, default=DefaultElementClass): element_class = self.LookUpClasses.get(element_tag, (default, None)) @@ -1760,7 +1758,56 @@ return self.GetElementClass(element_with_parent_class, default=default) return element_with_parent_class + def SetLookupResult(self, element, element_class): + """ + Set lookup result for the next 'lookup' callback made by lxml backend. + Lookup result is used only if element matches with tag's name submited to 'lookup'. + This is done, because there is no way to submit extra search parameters for + etree.PythonElementClassLookup.lookup() from etree.XMLParser.makeelement() + It's valid only for a signle 'lookup' call. + + :param element: + element's tag name + :param element_class: + element class that should be returned on + match in the next 'lookup' call. + :return: + Nothing + """ + self.ElementTag = element + self.ElementClass = element_class + + def ResetLookupResult(self): + """Reset lookup result, so it don't influence next lookups""" + self.ElementTag = None + self.ElementClass = None + + def GetLookupResult(self, element): + """Returns previously set SetLookupResult() lookup result""" + element_class = None + if self.ElementTag is not None and self.ElementTag == element.tag: + element_class = self.ElementClass + self.ResetLookupResult() + return element_class + def lookup(self, document, element): + """ + Lookup for element class for given element tag. + If return None from this method, the fallback is called. + + :param document: + opaque document instance that contains the Element + :param element: + lightweight Element proxy implementation that is only valid during the lookup. + Do not try to keep a reference to it. + Once the lookup is done, the proxy will be invalid. + :return: + Returns element class corresponding to given element. + """ + element_class = self.GetLookupResult(element) + if element_class is not None: + return element_class + parent = element.getparent() element_class = self.GetElementClass( element.tag, parent.tag if parent is not None else None) @@ -1778,9 +1825,10 @@ class XMLClassParser(etree.XMLParser): - - def __init__(self, namespaces, default_namespace_format, base_class, xsd_schema, *args, **kwargs): + def __init__(self, *args, **kwargs): etree.XMLParser.__init__(self, *args, **kwargs) + + def initMembers(self, namespaces, default_namespace_format, base_class, xsd_schema): self.DefaultNamespaceFormat = default_namespace_format self.NSMAP = namespaces targetNamespace = etree.QName(default_namespace_format % "d").namespace @@ -1827,14 +1875,53 @@ None) def CreateElement(self, element_tag, parent_tag=None, class_idx=None): + """ + Create XML element based on elements and parent's tag names. + + :param element_tag: + element's tag name + :param parent_tag: + optional parent's tag name. Default value is None. + :param class_idx: + optional index of class in list of founded classes + with same element and parent. Default value is None. + :return: + created XML element + (subclass of lxml.etree._Element created by class factory) + """ element_class = self.GetElementClass(element_tag, parent_tag) if isinstance(element_class, ListType): if class_idx is not None and class_idx < len(element_class): - new_element = element_class[class_idx]() + element_class = element_class[class_idx] else: raise ValueError("No corresponding class found!") - else: - new_element = element_class() + return self.CreateElementFromClass(element_class, element_tag) + + def CreateElementFromClass(self, element_class, element_tag=None): + """ + Create XML element instance of submitted element's class. + Submitted class should be subclass of lxml.etree._Element. + + element_class shouldn't be used to create XML element + directly using element_class(), because lxml backend + should be aware what class handles what xml element, + otherwise default lxml.etree._Element will be used. + + :param element_class: + element class + :param element_tag: + optional element's tag name. + If omitted it's calculated from element_class instance. + :return: + created XML element + (subclass of lxml.etree._Element created by class factory) + """ + if element_tag is None: + element_tag = element_class().tag + etag = self.DefaultNamespaceFormat % element_tag + self.ClassLookup.SetLookupResult(etag, element_class) + new_element = self.makeelement(etag) + self.ClassLookup.ResetLookupResult() DefaultElementClass.__setattr__(new_element, "tag", self.DefaultNamespaceFormat % element_tag) new_element._init_() return new_element @@ -1845,18 +1932,20 @@ This function generate a xml parser from a class factory """ + parser = XMLClassParser(strip_cdata=False, remove_blank_text=True) + factory.Parser = parser + ComputedClasses = factory.CreateClasses() - if factory.FileName is not None: ComputedClasses = ComputedClasses[factory.FileName] BaseClass = [(name, XSDclass) for name, XSDclass in ComputedClasses.items() if XSDclass.IsBaseClass] - parser = XMLClassParser( + parser.initMembers( factory.NSMAP, factory.etreeNamespaceFormat, BaseClass[0] if len(BaseClass) == 1 else None, - etree.XMLSchema(etree.fromstring(xsdstring)), - strip_cdata=False, remove_blank_text=True) + etree.XMLSchema(etree.fromstring(xsdstring))) + class_lookup = XMLElementClassLookUp(factory.ComputedClassesLookUp) parser.set_element_class_lookup(class_lookup)