xmlclass/xmlclass.py
changeset 2297 96ca6b056c55
parent 2278 a3ac46366b86
child 2343 33071a451021
--- 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)