plcopen/plcopen.py
changeset 1911 c1298e7ffe3a
parent 1629 80eee3c5a057
child 1680 6db967480b7d
--- a/plcopen/plcopen.py	Sun Mar 05 00:38:25 2017 +0000
+++ b/plcopen/plcopen.py	Fri Mar 24 12:07:47 2017 +0000
@@ -1,26 +1,26 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
-#based on the plcopen standard. 
+# This file is part of Beremiz, a Integrated Development Environment for
+# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
 #
-#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
 #
-#See COPYING file for copyrights details.
+# See COPYING file for copyrights details.
 #
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU General Public
-#License as published by the Free Software Foundation; either
-#version 2.1 of the License, or (at your option) any later version.
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
 #
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-#General Public License for more details.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
 #
-#You should have received a copy of the GNU General Public
-#License along with this library; if not, write to the Free Software
-#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 from xmlclass import *
 from types import *
@@ -111,10 +111,15 @@
     lines = text.split("\n")
     return len(lines) - 1, len(lines[-1])
 
+def CompilePattern(criteria):
+    flag = 0 if criteria["case_sensitive"] else re.IGNORECASE
+    find_pattern = criteria["find_pattern"]
+    if not criteria["regular_expression"]:
+        find_pattern = re.escape(find_pattern)
+    criteria["pattern"] = re.compile(find_pattern, flag)
+
 def TestTextElement(text, criteria):
     lines = text.splitlines()
-    if not criteria["case_sensitive"]:
-        text = text.upper()
     test_result = []
     result = criteria["pattern"].search(text)
     while result is not None:
@@ -124,6 +129,9 @@
         result = criteria["pattern"].search(text, result.end())
     return test_result
 
+def TextMatched(str1, str2):
+    return str1 and str2 and (str1.upper() == str2.upper())
+
 PLCOpenParser = GenerateParserFromXSD(os.path.join(os.path.split(__file__)[0], "tc6_xml_v201.xsd"))
 PLCOpen_XPath = lambda xpath: etree.XPath(xpath, namespaces=PLCOpenParser.NSMAP)
 
@@ -285,15 +293,8 @@
 if cls:
     def updateElementName(self, old_name, new_name):
         text = self.getanyText()
-        index = text.find(old_name)
-        while index != -1:
-            if index > 0 and (text[index - 1].isalnum() or text[index - 1] == "_"):
-                index = text.find(old_name, index + len(old_name))
-            elif index < len(text) - len(old_name) and (text[index + len(old_name)].isalnum() or text[index + len(old_name)] == "_"):
-                index = text.find(old_name, index + len(old_name))
-            else:
-                text = text[:index] + new_name + text[index + len(old_name):]
-                index = text.find(old_name, index + len(new_name))
+        pattern = re.compile('\\b' + old_name + '\\b', re.IGNORECASE)
+        text = pattern.sub(new_name, text)
         self.setanyText(text)
     setattr(cls, "updateElementName", updateElementName)
     
@@ -311,14 +312,9 @@
     setattr(cls, "updateElementAddress", updateElementAddress)
     
     def hasblock(self, block_type):
-        text = self.getanyText().upper()
-        index = text.find(block_type.upper())
-        while index != -1:
-            if (not (index > 0 and (text[index - 1].isalnum() or text[index - 1] == "_")) and 
-                not (index < len(text) - len(block_type) and text[index + len(block_type)] != "(")):
-                return True
-            index = text.find(block_type.upper(), index + len(block_type))
-        return False
+        text = self.getanyText()        
+        pattern = re.compile('\\b' + block_type + '\\b', re.IGNORECASE)
+        return pattern.search(text) is not None
     setattr(cls, "hasblock", hasblock)
     
     def Search(self, criteria, parent_infos):
@@ -492,7 +488,8 @@
 
     def addconfigurationResource(self, config_name, name):
         if self.getconfigurationResource(config_name, name) is not None:
-            raise ValueError, _("\"%s\" resource already exists in \"%s\" configuration !!!") % (name, config_name)
+            msg = _("\"{a1}\" resource already exists in \"{a2}\" configuration !!!").format(a1 = name, a2 = config_name)
+            raise ValueError, msg
         configuration = self.getconfiguration(config_name)
         if configuration is not None:
             new_resource = PLCOpenParser.CreateElement("resource", "configuration")
@@ -509,7 +506,8 @@
                 configuration.remove(resource)
                 found = True
         if not found:
-            raise ValueError, _("\"%s\" resource doesn't exist in \"%s\" configuration !!!")%(name, config_name)
+            msg = _("\"{a1}\" resource doesn't exist in \"{a2}\" configuration !!!").format(a1 = name, a2 = config_name)
+            raise ValueError, msg
     setattr(cls, "removeconfigurationResource", removeconfigurationResource)
 
     def updateElementName(self, old_name, new_name):
@@ -633,9 +631,9 @@
         for var in varlist.getvariable():
             var_address = var.getaddress()
             if var_address is not None:
-                if var_address == old_name:
+                if TextMatched(var_address, old_name):
                     var.setaddress(new_name)
-                if var.getname() == old_name:
+                if TextMatched(var.getname(), old_name):
                     var.setname(new_name)
 
 def _updateConfigurationResourceElementAddress(self, address_model, new_leading):
@@ -769,9 +767,9 @@
 cls = PLCOpenParser.GetElementClass("task", "resource")
 if cls:
     def updateElementName(self, old_name, new_name):
-        if self.single == old_name:
+        if TextMatched(self.single, old_name):
             self.single = new_name
-        if self.interval == old_name:
+        if TextMatched(self.interval, old_name):
             self.interval = new_name
         for instance in self.getpouInstance():
             instance.updateElementName(old_name, new_name)
@@ -794,7 +792,7 @@
 cls = PLCOpenParser.GetElementClass("pouInstance")
 if cls:
     def updateElementName(self, old_name, new_name):
-        if self.typeName == old_name:
+        if TextMatched(self.typeName, old_name):
             self.typeName = new_name
     setattr(cls, "updateElementName", updateElementName)
 
@@ -856,7 +854,7 @@
     def getdataTypeElement(self, name):
         elements = self.dataTypes.getdataType()
         for element in elements:
-            if element.getname() == name:
+            if TextMatched(element.getname(), name):
                 return element
         return None
     setattr(cls, "getdataTypeElement", getdataTypeElement)
@@ -875,7 +873,7 @@
     def removedataTypeElement(self, name):
         found = False
         for element in self.dataTypes.getdataType():
-            if element.getname() == name:
+            if TextMatched(element.getname(), name):
                 self.dataTypes.remove(element)
                 found = True
                 break
@@ -890,14 +888,14 @@
     def getpouElement(self, name):
         elements = self.pous.getpou()
         for element in elements:
-            if element.getname() == name:
+            if TextMatched(element.getname(), name):
                 return element
         return None
     setattr(cls, "getpouElement", getpouElement)
 
     def appendpouElement(self, name, pou_type, body_type):
         for element in self.pous.getpou():
-            if element.getname() == name:
+            if TextMatched(element.getname(), name):
                 raise ValueError, _("\"%s\" POU already exists !!!")%name
         new_pou = PLCOpenParser.CreateElement("pou", "pous")
         self.pous.appendpou(new_pou)
@@ -914,7 +912,7 @@
     def removepouElement(self, name):
         found = False
         for element in self.pous.getpou():
-            if element.getname() == name:
+            if TextMatched(element.getname(), name):
                 self.pous.remove(element)
                 found = True
                 break
@@ -981,7 +979,7 @@
 cls = PLCOpenParser.GetElementClass("derived", "dataType")
 if cls:
     def updateElementName(self, old_name, new_name):
-        if self.name == old_name:
+        if TextMatched(self.name, old_name):
             self.name = new_name
     setattr(cls, "updateElementName", updateElementName)
     
@@ -1226,9 +1224,9 @@
             for varlist in content:
                 variables = varlist.getvariable()
                 for var in variables:
-                    if var.getname() == old_name:
+                    if TextMatched(var.getname(), old_name):
                         vartype_content = var.gettype().getcontent()
-                        if vartype_content.getLocalTag() == "derived" and vartype_content.getname() == old_type:
+                        if vartype_content.getLocalTag() == "derived" and TextMatched(vartype_content.getname(), old_type):
                             var.setname(new_name)
                             vartype_content.setname(new_type)
                             return
@@ -1239,21 +1237,28 @@
             content = self.interface.getcontent()
             for varlist in content:
                 for var in varlist.getvariable():
-                    if var.getname() == name:
+                    if TextMatched(var.getname(), name):
                         vartype_content = var.gettype().getcontent()
-                        if vartype_content.getLocalTag() == "derived" and vartype_content.getname() == var_type:
+                        if vartype_content.getLocalTag() == "derived" and TextMatched(vartype_content.getname(), var_type):
                             varlist.remove(var)
                             if len(varlist.getvariable()) == 0:
                                 self.interface.remove(varlist)
                             break
     setattr(cls, "removepouVar", removepouVar)
+
+    def hasstep(self, name=None):
+        if self.getbodyType() in ["SFC"]:
+            for instance in self.getinstances():
+                if isinstance(instance, PLCOpenParser.GetElementClass("step", "sfcObjects")) and TextMatched(instance.getname(), name):
+                    return True         
+        return False
+    setattr(cls, "hasstep", hasstep)
     
     def hasblock(self, name=None, block_type=None):
         if self.getbodyType() in ["FBD", "LD", "SFC"]:
             for instance in self.getinstances():
                 if (isinstance(instance, PLCOpenParser.GetElementClass("block", "fbdObjects")) and 
-                    (name and instance.getinstanceName() == name or
-                     block_type and instance.gettypeName() == block_type)):
+                    (TextMatched(instance.getinstanceName(), name) or TextMatched(instance.gettypeName(), block_type))):
                     return True
             if self.transitions:
                 for transition in self.transitions.gettransition():
@@ -1279,15 +1284,13 @@
         transition.setname(name)
         transition.setbodyType(body_type)
         if body_type == "ST":
-            transition.setanyText(":= ;")
-        elif body_type == "IL":
-            transition.setanyText("\tST\t%s"%name)
+            transition.settext(":= ;")
     setattr(cls, "addtransition", addtransition)
     
     def gettransition(self, name):
         if self.transitions is not None:
             for transition in self.transitions.gettransition():
-                if transition.getname() == name:
+                if TextMatched(transition.getname(), name):
                     return transition
         return None
     setattr(cls, "gettransition", gettransition)
@@ -1302,7 +1305,7 @@
         if self.transitions is not None:
             removed = False
             for transition in self.transitions.gettransition():
-                if transition.getname() == name:
+                if TextMatched(transition.getname(), name):
                     if transition.getbodyType() in ["FBD", "LD", "SFC"]:
                         for instance in transition.getinstances():
                             if isinstance(instance, PLCOpenParser.GetElementClass("block", "fbdObjects")):
@@ -1328,7 +1331,7 @@
     def getaction(self, name):
         if self.actions is not None:
             for action in self.actions.getaction():
-                if action.getname() == name:
+                if TextMatched(action.getname(), name):
                     return action
         return None
     setattr(cls, "getaction", getaction)
@@ -1343,7 +1346,7 @@
         if self.actions is not None:
             removed = False
             for action in self.actions.getaction():
-                if action.getname() == name:
+                if TextMatched(action.getname(), name):
                     if action.getbodyType() in ["FBD", "LD", "SFC"]:
                         for instance in action.getinstances():
                             if isinstance(instance, PLCOpenParser.GetElementClass("block", "fbdObjects")):
@@ -1362,13 +1365,13 @@
                 for var in content.getvariable():
                     var_address = var.getaddress()
                     if var_address is not None:
-                        if var_address == old_name:
+                        if TextMatched(var_address, old_name):
                             var.setaddress(new_name)
-                        if var.getname() == old_name:
+                        if TextMatched(var.getname(), old_name):
                             var.setname(new_name)
                     var_type_content = var.gettype().getcontent()
                     if var_type_content.getLocalTag() == "derived":
-                        if var_type_content.getname() == old_name:
+                        if TextMatched(var_type_content.getname(), old_name):
                             var_type_content.setname(new_name)
         self.body[0].updateElementName(old_name, new_name)
         for action in self.getactionList():
@@ -1395,7 +1398,7 @@
         if self.interface is not None:
             for content in self.interface.getcontent():
                 for variable in content.getvariable():
-                    if variable.getaddress() == address:
+                    if TextMatched(variable.getaddress(), address):
                         content.remove(variable)
     setattr(cls, "removeVariableByAddress", removeVariableByAddress)
 
@@ -1414,7 +1417,8 @@
         search_result = []
         filter = criteria["filter"]
         if filter == "all" or self.getpouType() in filter:
-            parent_infos = parent_infos + ["P::%s" % self.getname()]
+            if parent_infos == []:
+                parent_infos = parent_infos + ["P::%s" % self.getname()]
             search_result.extend(_Search([("name", self.getname())], criteria, parent_infos))
             if self.interface is not None:
                 var_number = 0
@@ -1486,8 +1490,7 @@
     if self.getbodyType() in ["FBD", "LD", "SFC"]:
         for instance in self.getinstances():
             if (isinstance(instance, PLCOpenParser.GetElementClass("block", "fbdObjects")) and 
-                (name and instance.getinstanceName() == name or
-                 block_type and instance.gettypeName() == block_type)):
+                (TextMatched(instance.getinstanceName(), name) or TextMatched(instance.gettypeName(), block_type))):
                 return True
     elif block_type is not None:
         return self.body.hasblock(block_type)
@@ -1559,7 +1562,7 @@
 cls = PLCOpenParser.GetElementClass("body")
 if cls:
     cls.currentExecutionOrderId = 0
-    
+    cls.checkedBlocksDict = {}
     def resetcurrentExecutionOrderId(self):
         object.__setattr__(self, "currentExecutionOrderId", 0)
     setattr(cls, "resetcurrentExecutionOrderId", resetcurrentExecutionOrderId)
@@ -1576,6 +1579,7 @@
                                             PLCOpenParser.GetElementClass("connector", "commonObjects"), 
                                             PLCOpenParser.GetElementClass("continuation", "commonObjects"))):
                     element.setexecutionOrderId(0)
+            self.checkedBlocksDict.clear()
         else:
             raise TypeError, _("Can only generate execution order on FBD networks!")
     setattr(cls, "resetexecutionOrder", resetexecutionOrder)
@@ -1598,16 +1602,19 @@
         if self.content.getLocalTag() == "FBD":
             localid = link.getrefLocalId()
             instance = self.getcontentInstance(localid)
+            self.checkedBlocksDict[localid] = True
             if isinstance(instance, PLCOpenParser.GetElementClass("block", "fbdObjects")) and instance.getexecutionOrderId() == 0:
                 for variable in instance.inputVariables.getvariable():
                     connections = variable.connectionPointIn.getconnections()
                     if connections and len(connections) == 1:
-                        self.compileelementExecutionOrder(connections[0])
-                instance.setexecutionOrderId(self.getnewExecutionOrderId())
+                        if (self.checkedBlocksDict.has_key(connections[0].getrefLocalId()) == False):
+                            self.compileelementExecutionOrder(connections[0])
+                if instance.getexecutionOrderId() == 0:
+                    instance.setexecutionOrderId(self.getnewExecutionOrderId())
             elif isinstance(instance, PLCOpenParser.GetElementClass("continuation", "commonObjects")) and instance.getexecutionOrderId() == 0:
-                name = instance.getname()
                 for tmp_instance in self.getcontentInstances():
-                    if isinstance(tmp_instance, PLCOpenParser.GetElementClass("connector", "commonObjects")) and tmp_instance.getname() == name and tmp_instance.getexecutionOrderId() == 0:
+                    if (isinstance(tmp_instance, PLCOpenParser.GetElementClass("connector", "commonObjects")) and
+                        TextMatched(tmp_instance.getname(), instance.getname()) and tmp_instance.getexecutionOrderId() == 0):
                         connections = tmp_instance.connectionPointIn.getconnections()
                         if connections and len(connections) == 1:
                             self.compileelementExecutionOrder(connections[0])
@@ -1907,7 +1914,7 @@
     setattr(cls, "getBoundingBox", getBoundingBox)
 
     def updateElementName(self, old_name, new_name):
-        if self.typeName == old_name:
+        if TextMatched(self.typeName, old_name):
             self.typeName = new_name
     setattr(cls, "updateElementName", updateElementName)
 
@@ -1947,7 +1954,7 @@
 _initElementClass("rightPowerRail", "ldObjects", "multiple")
 
 def _UpdateLDElementName(self, old_name, new_name):
-    if self.variable == old_name:
+    if TextMatched(self.variable, old_name):
         self.variable = new_name
 
 def _UpdateLDElementAddress(self, address_model, new_leading):
@@ -2053,7 +2060,7 @@
             content = self.condition.getcontent()
             content_name = content.getLocalTag()
             if content_name == "reference":
-                if content.getname() == old_name:
+                if TextMatched(content.getname(), old_name):
                     content.setname(new_name)
             elif content_name == "inline":
                 content.updateElementName(old_name, new_name)
@@ -2125,7 +2132,7 @@
     setattr(cls, "getinlineContent", getinlineContent)
 
     def updateElementName(self, old_name, new_name):
-        if self.reference is not None and self.reference.getname() == old_name:
+        if self.reference is not None and TextMatched(self.reference.getname(), old_name):
             self.reference.setname(new_name)
         if self.inline is not None:
             self.inline.updateElementName(old_name, new_name)
@@ -2215,7 +2222,7 @@
     return _Search([("expression", self.expression)], criteria, parent_infos + ["io_variable", self.getlocalId()])
 
 def _UpdateIOElementName(self, old_name, new_name):
-    if self.expression == old_name:
+    if TextMatched(self.expression, old_name):
         self.expression = new_name
 
 def _UpdateIOElementAddress(self, address_model, new_leading):
@@ -2248,7 +2255,7 @@
     setattr(cls, "Search", _SearchInConnector)
 
     def updateElementName(self, old_name, new_name):
-        if self.name == old_name:
+        if TextMatched(self.name, old_name):
             self.name = new_name
     setattr(cls, "updateElementName", updateElementName)
 
@@ -2257,7 +2264,7 @@
     setattr(cls, "Search", _SearchInConnector)
 
     def updateElementName(self, old_name, new_name):
-        if self.name == old_name:
+        if TextMatched(self.name, old_name):
             self.name = new_name
     setattr(cls, "updateElementName", updateElementName)