Fixed xmlclass for working with included files, adding support for SimpleType elements and solving ambiguity in extension class when different elements share the same name and parent name
authorLaurent Bessard
Tue, 24 Sep 2013 00:44:06 +0200
changeset 1322 0a9227f743b3
parent 1321 83f41ea00b97
child 1323 a2b1af39385c
Fixed xmlclass for working with included files, adding support for SimpleType elements and solving ambiguity in extension class when different elements share the same name and parent name
PLCControler.py
PLCGenerator.py
plcopen/plcopen.py
xmlclass/xmlclass.py
xmlclass/xsdschema.py
--- a/PLCControler.py	Mon Sep 23 00:32:39 2013 +0200
+++ b/PLCControler.py	Tue Sep 24 00:44:06 2013 +0200
@@ -1377,8 +1377,8 @@
                 return_type_infos_xslt_tree = etree.XSLT(
                     variables_infos_xslt, extensions = {
                           ("var_infos_ns", "var_tree"): VarTree(self)})
-            return [extract_param(el) 
-                   for el in return_type_infos_xslt_tree(return_type).getroot()]
+                return [extract_param(el) 
+                       for el in return_type_infos_xslt_tree(return_type).getroot()]
                 
         return [None, ([], [])] 
 
@@ -2486,9 +2486,7 @@
                 return 
             for param, value in infos.items():
                 if param == "name":
-                    expression = PLCOpenParser.CreateElement("expression", variable.getLocalTag())
-                    expression.text = value
-                    variable.setexpression(expression)
+                    variable.setexpression(value)
                 elif param == "executionOrder" and variable.getexecutionOrderId() != value:
                     element.setelementExecutionOrder(variable, value)
                 elif param == "height":
@@ -2639,9 +2637,7 @@
                 return
             for param, value in infos.items():
                 if param == "name":
-                    variable = PLCOpenParser.CreateElement("variable", "contact")
-                    variable.text = value
-                    contact.setvariable(variable)
+                    contact.setvariable(value)
                 elif param == "type":
                     negated, edge = {
                         CONTACT_NORMAL: (False, "none"),
@@ -2684,9 +2680,7 @@
                 return
             for param, value in infos.items():
                 if param == "name":
-                    variable = PLCOpenParser.CreateElement("variable", "coil")
-                    variable.text = value
-                    coil.setvariable(variable)
+                    coil.setvariable(value)
                 elif param == "type":
                     negated, storage, edge = {
                         COIL_NORMAL: (False, "none", "none"),
--- a/PLCGenerator.py	Mon Sep 23 00:32:39 2013 +0200
+++ b/PLCGenerator.py	Tue Sep 24 00:44:06 2013 +0200
@@ -701,7 +701,7 @@
             for instance in body.getcontentInstances():
                 if isinstance(instance, (InVariableClass, OutVariableClass, 
                                          InOutVariableClass)):
-                    expression = instance.getexpression().text
+                    expression = instance.getexpression()
                     var_type = self.GetVariableType(expression)
                     if (isinstance(pou, TransitionObjClass) 
                         and expression == pou.getname()):
@@ -933,7 +933,7 @@
                         expression = self.ComputeExpression(body, connections)
                         if expression is not None:
                             self.Program += [(self.CurrentIndent, ()),
-                                             (instance.getexpression().text, (self.TagName, "io_variable", instance.getlocalId(), "expression")),
+                                             (instance.getexpression(), (self.TagName, "io_variable", instance.getlocalId(), "expression")),
                                              (" := ", ())]
                             self.Program += expression
                             self.Program += [(";\n", ())]
@@ -964,7 +964,7 @@
                         if expression is not None:
                             expression = self.ExtractModifier(instance, expression, coil_info)
                             self.Program += [(self.CurrentIndent, ())]
-                            self.Program += [(instance.getvariable().text, coil_info + ("reference",))]
+                            self.Program += [(instance.getvariable(), coil_info + ("reference",))]
                             self.Program += [(" := ", ())] + expression + [(";\n", ())]
                         
     def FactorizePaths(self, paths):
@@ -1187,7 +1187,7 @@
             if isinstance(next, LeftPowerRailClass):
                 paths.append(None)
             elif isinstance(next, (InVariableClass, InOutVariableClass)):
-                paths.append(str([(next.getexpression().text, (self.TagName, "io_variable", localId, "expression"))]))
+                paths.append(str([(next.getexpression(), (self.TagName, "io_variable", localId, "expression"))]))
             elif isinstance(next, BlockClass):
                 block_type = next.gettypeName()
                 self.ParentGenerator.GeneratePouProgram(block_type)
@@ -1223,7 +1223,7 @@
                         raise PLCGenException, _("No connector found corresponding to \"%s\" continuation in \"%s\" POU")%(name, self.Name)
             elif isinstance(next, ContactClass):
                 contact_info = (self.TagName, "contact", next.getlocalId())
-                variable = str(self.ExtractModifier(next, [(next.getvariable().text, contact_info + ("reference",))], contact_info))
+                variable = str(self.ExtractModifier(next, [(next.getvariable(), contact_info + ("reference",))], contact_info))
                 result = self.GeneratePaths(next.connectionPointIn.getconnections(), body, order)
                 if len(result) > 1:
                     factorized_paths = self.FactorizePaths(result)
@@ -1482,8 +1482,8 @@
                                                    (ReIndentText(transitionBody.getanyText(), len(self.CurrentIndent)), (self.TagName, "body", len(self.CurrentIndent)))]
                 else:
                     for instance in transitionBody.getcontentInstances():
-                        if isinstance(instance, OutVariableClass) and instance.getexpression().text == transitionValues["value"]\
-                            or isinstance(instance, CoilClass) and instance.getvariable().text == transitionValues["value"]:
+                        if isinstance(instance, OutVariableClass) and instance.getexpression() == transitionValues["value"]\
+                            or isinstance(instance, CoilClass) and instance.getvariable() == transitionValues["value"]:
                             connections = instance.connectionPointIn.getconnections()
                             if connections is not None:
                                 expression = self.ComputeExpression(transitionBody, connections)
--- a/plcopen/plcopen.py	Mon Sep 23 00:32:39 2013 +0200
+++ b/plcopen/plcopen.py	Tue Sep 24 00:44:06 2013 +0200
@@ -220,7 +220,7 @@
             new_address = groups[0] + new_leading + groups[2]
             text = text[:result.start()] + new_address + text[result.end():]
             startpos = result.start() + len(new_address)
-            result = address_model.search(self.text, startpos)
+            result = address_model.search(text, startpos)
         self.setanyText(text)
     setattr(cls, "updateElementAddress", updateElementAddress)
     
@@ -1876,7 +1876,7 @@
         infos = _getelementinfos(self)
         infos["type"] = type
         specific_values = infos["specific_values"]
-        specific_values["name"] = self.getexpression().text
+        specific_values["name"] = self.getexpression()
         _getexecutionOrder(self, specific_values)
         if input and output:
             infos["inputs"].append(_getconnectioninfos(self, self.connectionPointIn, True, "input"))
@@ -1920,7 +1920,7 @@
         infos = _getelementinfos(self)
         infos["type"] = ld_element_type
         specific_values = infos["specific_values"]
-        specific_values["name"] = self.getvariable().text
+        specific_values["name"] = self.getvariable()
         _getexecutionOrder(self, specific_values)
         specific_values["negated"] = self.getnegated()
         specific_values["edge"] = self.getedge()
@@ -2050,15 +2050,15 @@
     setattr(cls, "getinfos", _getpowerrailinfosFunction("rightPowerRail"))
 
 def _UpdateLDElementName(self, old_name, new_name):
-    if self.variable.text == old_name:
-        self.variable.text = new_name
+    if self.variable == old_name:
+        self.variable = new_name
 
 def _UpdateLDElementAddress(self, address_model, new_leading):
-    self.variable.text = update_address(self.variable.text, address_model, new_leading)
+    self.variable = update_address(self.variable, address_model, new_leading)
 
 def _getSearchInLDElement(ld_element_type):
     def SearchInLDElement(self, criteria, parent_infos=[]):
-        return _Search([("reference", self.variable.text)], criteria, parent_infos + [ld_element_type, self.getlocalId()])
+        return _Search([("reference", self.variable)], criteria, parent_infos + [ld_element_type, self.getlocalId()])
     return SearchInLDElement
 
 cls = _initElementClass("contact", "ldObjects", "single")
@@ -2412,14 +2412,14 @@
     setattr(cls, "Search", Search)
 
 def _SearchInIOVariable(self, criteria, parent_infos=[]):
-    return _Search([("expression", self.expression.text)], criteria, parent_infos + ["io_variable", self.getlocalId()])
+    return _Search([("expression", self.expression)], criteria, parent_infos + ["io_variable", self.getlocalId()])
 
 def _UpdateIOElementName(self, old_name, new_name):
-    if self.expression.text == old_name:
-        self.expression.text = new_name
+    if self.expression == old_name:
+        self.expression = new_name
 
 def _UpdateIOElementAddress(self, old_name, new_name):
-    self.expression.text = update_address(self.expression.text, address_model, new_leading)
+    self.expression = update_address(self.expression, address_model, new_leading)
 
 cls = _initElementClass("inVariable", "fbdObjects")
 if cls:
--- a/xmlclass/xmlclass.py	Mon Sep 23 00:32:39 2013 +0200
+++ b/xmlclass/xmlclass.py	Tue Sep 24 00:44:06 2013 +0200
@@ -925,7 +925,21 @@
         if name != base:
             equivalences = self.EquivalentClassesParent.setdefault(self.etreeNamespaceFormat % base, {})
             equivalences[self.etreeNamespaceFormat % name] = True
-        
+    
+    def AddDistinctionBetweenParentsInLookupClass(
+                                    self, lookup_classes, parent, typeinfos):
+        parent = (self.etreeNamespaceFormat % parent 
+                  if parent is not None else None)
+        parent_class = lookup_classes.get(parent)
+        if parent_class is not None:
+            if isinstance(parent_class, ListType):
+                if typeinfos not in parent_class:
+                    lookup_classes[parent].append(typeinfos)
+            elif parent_class != typeinfos:
+                lookup_classes[parent] = [parent_class, typeinfos]
+        else:
+            lookup_classes[parent] = typeinfos
+    
     def AddToLookupClass(self, name, parent, typeinfos):
         lookup_name = self.etreeNamespaceFormat % name
         if isinstance(typeinfos, (StringType, UnicodeType)):
@@ -935,13 +949,14 @@
         if lookup_classes is None:
             self.ComputedClassesLookUp[lookup_name] = (typeinfos, parent)
         elif isinstance(lookup_classes, DictType):
-            lookup_classes[self.etreeNamespaceFormat % parent 
-                           if parent is not None else None] = typeinfos
-        else:
-            lookup_classes = {self.etreeNamespaceFormat % lookup_classes[1]
-                              if lookup_classes[1] is not None else None: lookup_classes[0]}
-            lookup_classes[self.etreeNamespaceFormat % parent
-                           if parent is not None else None] = typeinfos
+            self.AddDistinctionBetweenParentsInLookupClass(
+                lookup_classes, parent, typeinfos)
+        else:
+            lookup_classes = {
+                self.etreeNamespaceFormat % lookup_classes[1]
+                if lookup_classes[1] is not None else None: lookup_classes[0]}
+            self.AddDistinctionBetweenParentsInLookupClass(
+                lookup_classes, parent, typeinfos)
             self.ComputedClassesLookUp[lookup_name] = lookup_classes
     
     def ExtractTypeInfos(self, name, parent, typeinfos):
@@ -1130,6 +1145,7 @@
             classmembers["get%s" % elmtname] = generateGetMethod(elmtname)
             
         classmembers["_init_"] = generateInitMethod(self, classinfos)
+        classmembers["StructurePattern"] = GetStructurePattern(classinfos)
         classmembers["getElementAttributes"] = generateGetElementAttributes(self, classinfos)
         classmembers["getElementInfos"] = generateGetElementInfos(self, classinfos)
         classmembers["setElementValue"] = generateSetElementValue(self, classinfos)
@@ -1177,6 +1193,60 @@
             print classname
 
 """
+Method that generate the method for generating the xml tree structure model by 
+following the attributes list defined
+"""
+def ComputeMultiplicity(name, infos):
+    if infos["minOccurs"] == 0:
+        if infos["maxOccurs"] == "unbounded":
+            return "(?:%s)*" % name
+        elif infos["maxOccurs"] == 1:
+            return "(?:%s)?" % name
+        else:
+            return "(?:%s){,%d}" % (name, infos["maxOccurs"])
+    elif infos["minOccurs"] == 1:
+        if infos["maxOccurs"] == "unbounded":
+            return "(?:%s)+" % name
+        elif infos["maxOccurs"] == 1:
+            return "(?:%s)" % name
+        else:
+            return "(?:%s){1,%d}" % (name, infos["maxOccurs"])
+    else:
+        if infos["maxOccurs"] == "unbounded":
+            return "(?:%s){%d,}" % (name, infos["minOccurs"], name)
+        else:
+            return "(?:%s){%d,%d}" % (name, infos["minOccurs"], 
+                                       infos["maxOccurs"])
+
+def GetStructurePattern(classinfos):
+    base_structure_pattern = (
+        classinfos["base"].StructurePattern.pattern[:-1]
+        if classinfos.has_key("base") else "")
+    elements = []
+    for element in classinfos["elements"]:
+        if element["type"] == ANY:
+            infos = element.copy()
+            infos["minOccurs"] = 0
+            elements.append(ComputeMultiplicity("#text |#cdata-section |\w* ", infos))
+        elif element["type"] == CHOICE:
+            choices = []
+            for infos in element["choices"]:
+                if infos["type"] == "sequence":
+                    structure = "(?:%s)" % GetStructurePattern(infos)
+                else:
+                    structure = "%s " % infos["name"]
+                choices.append(ComputeMultiplicity(structure, infos))
+            elements.append(ComputeMultiplicity("|".join(choices), element))
+        elif element["name"] == "content" and element["elmt_type"]["type"] == SIMPLETYPE:
+            elements.append("(?:#text |#cdata-section )?")
+        else:
+            elements.append(ComputeMultiplicity("%s " % element["name"], element))
+    if classinfos.get("order", True) or len(elements) == 0:
+        return re.compile(base_structure_pattern + "".join(elements) + "$")
+    else:
+        raise ValueError("XSD structure not yet supported!")
+
+"""
 Method that generate the method for creating a class instance
 """
 def generateClassCreateFunction(class_definition):
@@ -1214,12 +1284,22 @@
                 return None 
             elif element_infos["type"] == ANY:
                 return element_infos["elmt_type"]["extract"](self)
+            elif name == "content" and element_infos["elmt_type"]["type"] == SIMPLETYPE:
+                return element_infos["elmt_type"]["extract"](self.text, extract=False)
             else:
                 element_name = factory.etreeNamespaceFormat % name
                 if element_infos["maxOccurs"] == "unbounded" or element_infos["maxOccurs"] > 1:
-                    return self.findall(element_name)
+                    values = self.findall(element_name)
+                    if element_infos["elmt_type"]["type"] == SIMPLETYPE:
+                        return map(lambda value:
+                            element_infos["elmt_type"]["extract"](value.text, extract=False), 
+                            values)
+                    return values
                 else:
-                    return self.find(element_name)
+                    value = self.find(element_name)
+                    if element_infos["elmt_type"]["type"] == SIMPLETYPE:
+                        return element_infos["elmt_type"]["extract"](value.text, extract=False)
+                    return value
             
         elif classinfos.has_key("base"):
             return classinfos["base"].__getattr__(self, name)
@@ -1252,6 +1332,9 @@
             if element_infos["type"] == ANY:
                 element_infos["elmt_type"]["generate"](self, value)
             
+            elif name == "content" and element_infos["elmt_type"]["type"] == SIMPLETYPE:
+                self.text = element_infos["elmt_type"]["generate"](value)
+            
             else:
                 prefix = ("%s:" % factory.TargetNamespace
                           if factory.TargetNamespace is not None else "")
@@ -1277,8 +1360,12 @@
                     
                     if not isinstance(value, ListType):
                         value = [value]
-                        
+                    
                     for element in reversed(value):
+                        if element_infos["elmt_type"]["type"] == SIMPLETYPE:
+                            tmp_element = etree.Element(factory.etreeNamespaceFormat % name)
+                            tmp_element.text = element_infos["elmt_type"]["generate"](element)
+                            element = tmp_element
                         self.insert(insertion_point, element)
         
         elif classinfos.has_key("base"):
@@ -1596,6 +1683,8 @@
 
 class DefaultElementClass(etree.ElementBase):
     
+    StructurePattern = re.compile("$")
+    
     def _init_(self):
         pass
     
@@ -1625,8 +1714,19 @@
         
     def lookup(self, document, element):
         parent = element.getparent()
-        return self.GetElementClass(element.tag, 
+        element_class = self.GetElementClass(element.tag, 
             parent.tag if parent is not None else None)
+        if isinstance(element_class, ListType):
+            children = "".join([
+                "%s " % etree.QName(child.tag).localname
+                for child in element])
+            for possible_class in element_class:
+                if isinstance(possible_class, (StringType, UnicodeType)):
+                    possible_class = self.GetElementClass(possible_class)
+                if possible_class.StructurePattern.match(children) is not None:
+                    return possible_class
+            return element_class[0]
+        return element_class
 
 class XMLClassParser(etree.XMLParser):
 
@@ -1669,19 +1769,26 @@
             if parent_tag is not None else parent_tag, 
             None)
     
-    def CreateElement(self, element_tag, parent_tag=None):
-        new_element = self.GetElementClass(element_tag, parent_tag)()
+    def CreateElement(self, element_tag, parent_tag=None, class_idx=None):
+        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]()
+            else:
+                raise ValueError, "No corresponding class found!"
+        else:
+            new_element = element_class()
         DefaultElementClass.__setattr__(new_element, "tag", self.DefaultNamespaceFormat % element_tag)
         new_element._init_()
         return new_element
     
 def GenerateParser(factory, xsdstring):
     ComputedClasses = factory.CreateClasses()
-    if factory.FileName is not None and len(ComputedClasses) == 1:
+    
+    if factory.FileName is not None:
         ComputedClasses = ComputedClasses[factory.FileName]
     BaseClass = [(name, XSDclass) for name, XSDclass in ComputedClasses.items() if XSDclass.IsBaseClass]
-    UpdateXMLClassGlobals(ComputedClasses)
-    
+       
     parser = XMLClassParser(
         factory.NSMAP,
         factory.etreeNamespaceFormat,
@@ -1693,6 +1800,3 @@
     
     return parser
 
-def UpdateXMLClassGlobals(classes):
-    globals().update(classes)
-
--- a/xmlclass/xsdschema.py	Mon Sep 23 00:32:39 2013 +0200
+++ b/xmlclass/xsdschema.py	Tue Sep 24 00:44:06 2013 +0200
@@ -924,6 +924,8 @@
     else:
         factory.Namespaces[include_factory.TargetNamespace] = include_factory.Namespaces[include_factory.TargetNamespace]
     factory.ComputedClasses.update(include_factory.ComputedClasses)
+    factory.ComputedClassesLookUp.update(include_factory.ComputedClassesLookUp)
+    factory.EquivalentClassesParent.update(include_factory.EquivalentClassesParent)
     return None
     
 def ReduceRedefine(factory, attributes, elements):
@@ -1092,7 +1094,11 @@
     xsdfile = open(filepath, 'r')
     xsdstring = xsdfile.read()
     xsdfile.close()
-    return GenerateParser(XSDClassFactory(minidom.parseString(xsdstring), filepath), xsdstring)
+    cwd = os.getcwd()
+    os.chdir(os.path.dirname(filepath))
+    parser = GenerateParser(XSDClassFactory(minidom.parseString(xsdstring), filepath), xsdstring)
+    os.chdir(cwd)
+    return parser
 
 """
 This function generate a xml from the xsd given as a string