merged
authorEdouard Tisserant
Sat, 28 Sep 2013 15:59:50 +0900
changeset 1333 7f264cc6e75d
parent 1329 9d0cb01312f0 (current diff)
parent 1332 ac7d39f4e376 (diff)
child 1336 1054cb01b523
merged
--- a/CodeFileTreeNode.py	Fri Sep 27 09:32:39 2013 +0900
+++ b/CodeFileTreeNode.py	Sat Sep 28 15:59:50 2013 +0900
@@ -1,10 +1,11 @@
-import os, re
+import os, re, traceback
 
 from copy import deepcopy
 from lxml import etree
 from xmlclass import GenerateParserFromXSDstring
 
 from PLCControler import UndoBuffer
+from ConfigTreeNode import XSDSchemaErrorMessage
 
 CODEFILE_XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -84,9 +85,16 @@
                 (re.compile("(?<!<xhtml:p>)(?:<!\[CDATA\[)"), "<xhtml:p><![CDATA["),
                 (re.compile("(?:]]>)(?!</xhtml:p>)"), "]]></xhtml:p>")]:
                 codefile_xml = cre.sub(repl, codefile_xml)
-            self.CodeFile = etree.fromstring(codefile_xml, self.CodeFileParser)    
-            self.CreateCodeFileBuffer(True)
-        
+            
+            try:
+                self.CodeFile, error = self.CodeFileParser.LoadXMLString(codefile_xml)
+                if error is not None:
+                    self.GetCTRoot().logger.write_warning(
+                        XSDSchemaErrorMessage % ((self.CODEFILE_NAME,) + error))
+                self.CreateCodeFileBuffer(True)
+            except Exception, exc:
+                self.GetCTRoot().logger.write_error(_("Couldn't load confnode parameters %s :\n %s") % (CTNName, unicode(exc)))
+                self.GetCTRoot().logger.write_error(traceback.format_exc())
         else:
             self.CodeFile = self.CodeFileParser.CreateRoot()
             self.CreateCodeFileBuffer(False)
--- a/ConfigTreeNode.py	Fri Sep 27 09:32:39 2013 +0900
+++ b/ConfigTreeNode.py	Sat Sep 28 15:59:50 2013 +0900
@@ -29,6 +29,7 @@
         </xsd:schema>""")
 
 NameTypeSeparator = '@'
+XSDSchemaErrorMessage = _("%s XML file doesn't follow XSD schema at line %d:\n%s")
 
 class ConfigTreeNode:
     """
@@ -581,29 +582,36 @@
         if os.path.isfile(methode_name):
             execfile(methode_name)
         
+        ConfNodeName = CTNName if CTNName is not None else self.CTNName()
+        
         # Get the base xml tree
         if self.MandatoryParams:
             try:
                 basexmlfile = open(self.ConfNodeBaseXmlFilePath(CTNName), 'r')
-                self.BaseParams = etree.fromstring(
-                    basexmlfile.read(), _BaseParamsParser)
+                self.BaseParams, error = _BaseParamsParser.LoadXMLString(basexmlfile.read())
+                if error is not None:
+                    self.GetCTRoot().logger.write_warning(
+                        XSDSchemaErrorMessage % ((ConfNodeName + " BaseParams",) + error))
                 self.MandatoryParams = ("BaseParams", self.BaseParams)
                 basexmlfile.close()
             except Exception, exc:
-                self.GetCTRoot().logger.write_error(_("Couldn't load confnode base parameters %s :\n %s") % (CTNName, unicode(exc)))
+                self.GetCTRoot().logger.write_error(_("Couldn't load confnode base parameters %s :\n %s") % (ConfNodeName, unicode(exc)))
                 self.GetCTRoot().logger.write_error(traceback.format_exc())
         
         # Get the xml tree
         if self.CTNParams:
             try:
                 xmlfile = open(self.ConfNodeXmlFilePath(CTNName), 'r')
-                obj = etree.fromstring(xmlfile.read(), self.Parser)
+                obj, error = self.Parser.LoadXMLString(xmlfile.read())
+                if error is not None:
+                    self.GetCTRoot().logger.write_warning(
+                        XSDSchemaErrorMessage % ((ConfNodeName,) + error))
                 name = obj.getLocalTag()
                 setattr(self, name, obj)
                 self.CTNParams = (name, obj)
                 xmlfile.close()
             except Exception, exc:
-                self.GetCTRoot().logger.write_error(_("Couldn't load confnode parameters %s :\n %s") % (CTNName, unicode(exc)))
+                self.GetCTRoot().logger.write_error(_("Couldn't load confnode parameters %s :\n %s") % (ConfNodeName, unicode(exc)))
                 self.GetCTRoot().logger.write_error(traceback.format_exc())
         
     def LoadChildren(self):
--- a/PLCControler.py	Fri Sep 27 09:32:39 2013 +0900
+++ b/PLCControler.py	Sat Sep 28 15:59:50 2013 +0900
@@ -844,8 +844,10 @@
         Adds the POU defined by 'pou_xml' to the current project with type 'pou_type'
         '''
         try:
-            new_pou = LoadPou(pou_xml)
+            new_pou, error = LoadPou(pou_xml)
         except:
+            error = ""
+        if error is not None:
             return _("Couldn't paste non-POU object.")
         
         name = new_pou.getname()
@@ -2207,10 +2209,10 @@
             new_id = {}
             
             try:
-                instances = LoadPouInstances(text.encode("utf-8"), bodytype)
-                if len(instances) == 0:
-                    raise ValueError
+                instances, error = LoadPouInstances(text.encode("utf-8"), bodytype)
             except:
+                instances, error = [], ""
+            if error is not None or len(instances) == 0:
                 return _("Invalid plcopen element(s)!!!")
             
             exclude = {}
@@ -2285,24 +2287,25 @@
                 instance.translate(*diff)
             
             return new_id, connections
-                
+    
+    # Return the current pou editing instances idx
+    def GetEditedElementInstancesIds(self, tagname, debug = False):
+        element = self.GetEditedElement(tagname, debug)
+        if element is not None:
+            return element.getinstancesIds()
+        return []
+    
     # Return the current pou editing informations
-    def GetEditedElementInstanceInfos(self, tagname, id = None, exclude = [], debug = False):
-        infos = {}
-        instance = None
+    def GetEditedElementInstanceInfos(self, tagname, id, debug = False):
         element = self.GetEditedElement(tagname, debug)
         if element is not None:
-            # if id is defined
-            if id is not None:
-                instance = element.getinstance(id)
-            else:
-                instance = element.getrandomInstance(exclude)
-        if instance is not None:
-            infos = instance.getinfos()
-            if infos["type"] in ["input", "output", "inout"]:
-                var_type = self.GetEditedElementVarValueType(tagname, infos["specific_values"]["name"], debug)
-                infos["specific_values"]["value_type"] = var_type
-            return infos
+            instance = element.getinstance(id)
+            if instance is not None:
+                infos = instance.getinfos()
+                if infos["type"] in ["input", "output", "inout"]:
+                    var_type = self.GetEditedElementVarValueType(tagname, infos["specific_values"]["name"], debug)
+                    infos["specific_values"]["value_type"] = var_type
+                return infos
         return None
     
     def ClearEditedElementExecutionOrder(self, tagname):
@@ -3063,10 +3066,9 @@
             return tasks_data, instances_data
 
     def OpenXMLFile(self, filepath):
-        #try:
-        self.Project = LoadProject(filepath)
-        #except Exception, e:
-        #    return _("Project file syntax error:\n\n") + str(e)
+        self.Project, error = LoadProject(filepath)
+        if self.Project is None:
+            return _("Project file syntax error:\n\n") + error
         self.SetFilePath(filepath)
         self.CreateProjectBuffer(True)
         self.ProgramChunks = []
@@ -3075,7 +3077,7 @@
         self.CurrentCompiledProject = None
         self.Buffering = False
         self.CurrentElementEditing = None
-        return None
+        return error
         
     def SaveXMLFile(self, filepath = None):
         if not filepath and self.FilePath == "":
--- a/PLCOpenEditor.py	Fri Sep 27 09:32:39 2013 +0900
+++ b/PLCOpenEditor.py	Sat Sep 28 15:59:50 2013 +0900
@@ -182,12 +182,11 @@
                 # Create a new controller
                 controler = PLCControler()
                 result = controler.OpenXMLFile(fileOpen)
-                if result is None:
-                    self.Controler = controler
-                    self.LibraryPanel.SetController(controler)
-                    self.ProjectTree.Enable(True)
-                    self.PouInstanceVariablesPanel.SetController(controler)
-                    self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+                self.Controler = controler
+                self.LibraryPanel.SetController(controler)
+                self.ProjectTree.Enable(True)
+                self.PouInstanceVariablesPanel.SetController(controler)
+                self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
         
         # Define PLCOpenEditor icon
         self.SetIcon(wx.Icon(os.path.join(CWD, "images", "poe.ico"),wx.BITMAP_TYPE_ICO))
@@ -197,7 +196,8 @@
         self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
         
         if result is not None:
-            self.ShowErrorMessage(result)
+            self.ShowErrorMessage(
+                _("PLC syntax error at line %d:\n%s") % result)
 
     def OnCloseFrame(self, event):
         if self.Controler is None or self.CheckSaveBeforeClosing(_("Close Application")):
@@ -300,17 +300,17 @@
                 self.ResetView()
                 controler = PLCControler()
                 result = controler.OpenXMLFile(filepath)
-                if result is None:
-                    self.Controler = controler
-                    self.LibraryPanel.SetController(controler)
-                    self.ProjectTree.Enable(True)
-                    self.PouInstanceVariablesPanel.SetController(controler)
-                    self._Refresh(PROJECTTREE, LIBRARYTREE)
+                self.Controler = controler
+                self.LibraryPanel.SetController(controler)
+                self.ProjectTree.Enable(True)
+                self.PouInstanceVariablesPanel.SetController(controler)
+                self._Refresh(PROJECTTREE, LIBRARYTREE)
             self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
         dialog.Destroy()
         
         if result is not None:
-            self.ShowErrorMessage(result)
+            self.ShowErrorMessage(
+                _("PLC syntax error at line %d:\n%s") % result)
     
     def OnCloseProjectMenu(self, event):
         if not self.CheckSaveBeforeClosing():
--- a/ProjectController.py	Fri Sep 27 09:32:39 2013 +0900
+++ b/ProjectController.py	Sat Sep 28 15:59:50 2013 +0900
@@ -27,7 +27,7 @@
 from PLCControler import PLCControler
 from plcopen.structures import IEC_KEYWORDS
 from targets.typemapping import DebugTypesSize, LogLevelsCount, LogLevels
-from ConfigTreeNode import ConfigTreeNode
+from ConfigTreeNode import ConfigTreeNode, XSDSchemaErrorMessage
 
 base_folder = os.path.split(sys.path[0])[0]
 
@@ -318,9 +318,13 @@
         if not os.path.isfile(plc_file):
             return _("Chosen folder doesn't contain a program. It's not a valid project!")
         # Load PLCOpen file
-        result = self.OpenXMLFile(plc_file)
-        if result:
-            return result
+        error = self.OpenXMLFile(plc_file)
+        if error is not None:
+            if self.Project is not None:
+                self.logger.write_warning(
+                    XSDSchemaErrorMessage % (("PLC",) + error))
+            else:
+                return error
         if len(self.GetProjectConfigNames()) == 0:
             self.AddProjectDefaultConfiguration()
         # Change XSD into class members
--- a/editors/Viewer.py	Fri Sep 27 09:32:39 2013 +0900
+++ b/editors/Viewer.py	Sat Sep 28 15:59:50 2013 +0900
@@ -1089,10 +1089,11 @@
         self.ResetBuffer()
         instance = {}
         # List of ids of already loaded blocks
-        ids = []
+        ids = self.Controler.GetEditedElementInstancesIds(self.TagName, debug = self.Debug)
         # Load Blocks until they are all loaded
-        while instance is not None:
-            instance = self.Controler.GetEditedElementInstanceInfos(self.TagName, exclude = ids, debug = self.Debug)
+        while len(ids) > 0:
+            instance = self.Controler.GetEditedElementInstanceInfos(
+                self.TagName, ids.popitem(0)[0], debug = self.Debug)
             if instance is not None:
                 self.loadInstance(instance, ids, selection)
         
@@ -1221,7 +1222,6 @@
         
     # Load instance from given informations
     def loadInstance(self, instance, ids, selection):
-        ids.append(instance["id"])
         self.current_id = max(self.current_id, instance["id"])
         creation_function = ElementCreationFunctions.get(instance["type"], None)
         connectors = {"inputs" : [], "outputs" : []}
@@ -1319,7 +1319,7 @@
                 links_connected = False
                 continue
             
-            if refLocalId not in ids:
+            if ids.pop(refLocalId, False):
                 new_instance = self.Controler.GetEditedElementInstanceInfos(self.TagName, refLocalId, debug = self.Debug)
                 if new_instance is not None:
                     self.loadInstance(new_instance, ids, selection)
--- a/plcopen/plcopen.py	Fri Sep 27 09:32:39 2013 +0900
+++ b/plcopen/plcopen.py	Sat Sep 28 15:59:50 2013 +0900
@@ -26,6 +26,8 @@
 from types import *
 import os, re
 from lxml import etree
+from collections import OrderedDict
+
 """
 Dictionary that makes the relation between var names in plcopen and displayed values
 """
@@ -156,35 +158,44 @@
   </body>
 </pou>""" % locals()
 
-def LoadProject(filepath):
-    project_file = open(filepath)
-    project_xml = project_file.read().replace(
+def LoadProjectXML(project_xml):
+    project_xml = project_xml.replace(
         "http://www.plcopen.org/xml/tc6.xsd", 
         "http://www.plcopen.org/xml/tc6_0201")
     for cre, repl in [
         (re.compile("(?<!<xhtml:p>)(?:<!\[CDATA\[)"), "<xhtml:p><![CDATA["),
         (re.compile("(?:]]>)(?!</xhtml:p>)"), "]]></xhtml:p>")]:
         project_xml = cre.sub(repl, project_xml)
+    
+    try:
+        tree, error = PLCOpenParser.LoadXMLString(project_xml)
+        if error is not None:
+            # TODO Validate file according to PLCOpen v1 and modify it for
+            # compatibility with PLCOpen v2
+            return tree, error
+        return tree, None
+    except Exception, e:
+        return None, e.message
+
+def LoadProject(filepath):
+    project_file = open(filepath)
+    project_xml = project_file.read()
     project_file.close()
-    
-    return etree.fromstring(project_xml, PLCOpenParser)
+    return LoadProjectXML(project_xml)
 
 project_pou_xpath = PLCOpen_XPath("/ppx:project/ppx:types/ppx:pous/ppx:pou")
 def LoadPou(xml_string):
-    root = etree.fromstring(
-        LOAD_POU_PROJECT_TEMPLATE % xml_string, 
-        PLCOpenParser)
-    return project_pou_xpath(root)[0]
+    root, error = LoadProjectXML(LOAD_POU_PROJECT_TEMPLATE % xml_string)
+    return project_pou_xpath(root)[0], error
 
 project_pou_instances_xpath = {
     body_type: PLCOpen_XPath(
         "/ppx:project/ppx:types/ppx:pous/ppx:pou[@name='paste_pou']/ppx:body/ppx:%s/*" % body_type)
     for body_type in ["FBD", "LD", "SFC"]}
 def LoadPouInstances(xml_string, body_type):
-    root = etree.fromstring(
-        LOAD_POU_INSTANCES_PROJECT_TEMPLATE(body_type) % xml_string, 
-        PLCOpenParser)
-    return project_pou_instances_xpath[body_type](root)
+    root, error = LoadProjectXML(
+        LOAD_POU_INSTANCES_PROJECT_TEMPLATE(body_type) % xml_string)
+    return project_pou_instances_xpath[body_type](root), error
 
 def SaveProject(project, filepath):
     project_file = open(filepath, 'w')
@@ -1080,11 +1091,11 @@
         return None
     setattr(cls, "getinstance", getinstance)
     
-    def getrandomInstance(self, exclude):
+    def getinstancesIds(self):
         if len(self.body) > 0:
-            return self.body[0].getcontentRandomInstance(exclude)
-        return None
-    setattr(cls, "getrandomInstance", getrandomInstance)
+            return self.body[0].getcontentInstancesIds()
+        return []
+    setattr(cls, "getinstancesIds", getinstancesIds)
     
     def getinstanceByName(self, name):
         if len(self.body) > 0:
@@ -1600,18 +1611,13 @@
             raise TypeError, _("%s body don't have instances!")%self.content.getLocalTag()
     setattr(cls, "getcontentInstance", getcontentInstance)
     
-    def getcontentRandomInstance(self, exclude):
+    def getcontentInstancesIds(self):
         if self.content.getLocalTag() in ["LD","FBD","SFC"]:
-            instance = self.content.xpath("*%s[position()=1]" %  
-                ("[not(%s)]" % " or ".join(
-                    map(lambda x: "@localId=%d" % x, exclude))
-                if len(exclude) > 0 else ""))
-            if len(instance) > 0:
-                return instance[0]
-            return None
+            return OrderedDict([(instance.getlocalId(), True)
+                                for instance in self.content])
         else:
             raise TypeError, _("%s body don't have instances!")%self.content.getLocalTag()
-    setattr(cls, "getcontentRandomInstance", getcontentRandomInstance)
+    setattr(cls, "getcontentInstancesIds", getcontentInstancesIds)
     
     def getcontentInstanceByName(self, name):
         if self.content.getLocalTag() in ["LD","FBD","SFC"]:
--- a/plcopen/structures.py	Fri Sep 27 09:32:39 2013 +0900
+++ b/plcopen/structures.py	Sat Sep 28 15:59:50 2013 +0900
@@ -42,8 +42,10 @@
 
 ScriptDirectory = os.path.split(os.path.realpath(__file__))[0]
 
-StdBlockLibrary = LoadProject(os.path.join(ScriptDirectory, "Standard_Function_Blocks.xml"))
-AddnlBlockLibrary = LoadProject(os.path.join(ScriptDirectory, "Additional_Function_Blocks.xml"))
+StdBlockLibrary, error = LoadProject(
+    os.path.join(ScriptDirectory, "Standard_Function_Blocks.xml"))
+AddnlBlockLibrary, error = LoadProject(
+    os.path.join(ScriptDirectory, "Additional_Function_Blocks.xml"))
 
 StdBlockComments = {
     "SR": _("SR bistable\nThe SR bistable is a latch where the Set dominates."),
--- a/py_ext/PythonFileCTNMixin.py	Fri Sep 27 09:32:39 2013 +0900
+++ b/py_ext/PythonFileCTNMixin.py	Sat Sep 28 15:59:50 2013 +0900
@@ -37,12 +37,20 @@
                 (re.compile("(?<!<xhtml:p>)(?:<!\[CDATA\[)"), "<xhtml:p><![CDATA["),
                 (re.compile("(?:]]>)(?!</xhtml:p>)"), "]]></xhtml:p>")]:
                 pythonfile_xml = cre.sub(repl, pythonfile_xml)
-            python_code = etree.fromstring(pythonfile_xml, PythonParser)
-            
-            self.CodeFile.globals.setanyText(python_code.getanyText())
-            os.remove(filepath)
-            self.CreateCodeFileBuffer(False)
-            self.OnCTNSave()
+            
+            try:
+                python_code, error = PythonParser.LoadXMLString(pythonfile_xml)
+                if error is None:    
+                    self.CodeFile.globals.setanyText(python_code.getanyText())
+                    os.remove(filepath)
+                    self.CreateCodeFileBuffer(False)
+                    self.OnCTNSave()
+            except Exception, exc:
+                error = unicode(exc)
+            
+            if error is not None:
+                self.GetCTRoot().logger.write_error(
+                    _("Couldn't import old %s file.") % CTNName)
     
     def CodeFileName(self):
         return os.path.join(self.CTNPath(), "pyfile.xml")
--- a/xmlclass/xmlclass.py	Fri Sep 27 09:32:39 2013 +0900
+++ b/xmlclass/xmlclass.py	Sat Sep 28 15:59:50 2013 +0900
@@ -1730,7 +1730,7 @@
 
 class XMLClassParser(etree.XMLParser):
 
-    def __init__(self, namespaces, default_namespace_format, base_class, *args, **kwargs):
+    def __init__(self, namespaces, default_namespace_format, base_class, xsd_schema, *args, **kwargs):
         etree.XMLParser.__init__(self, *args, **kwargs)
         self.DefaultNamespaceFormat = default_namespace_format
         self.NSMAP = namespaces
@@ -1742,11 +1742,19 @@
         else:
             self.RootNSMAP = namespaces
         self.BaseClass = base_class
+        self.XSDSchema = xsd_schema
     
     def set_element_class_lookup(self, class_lookup):
         etree.XMLParser.set_element_class_lookup(self, class_lookup)
         self.ClassLookup = class_lookup
     
+    def LoadXMLString(self, xml_string):
+        tree = etree.fromstring(xml_string, self)
+        if not self.XSDSchema.validate(tree):
+            error = self.XSDSchema.error_log.last_error
+            return tree, (error.line, error.message)
+        return tree, None 
+    
     def Dumps(self, xml_obj):
         return etree.tostring(xml_obj)
     
@@ -1793,7 +1801,7 @@
         factory.NSMAP,
         factory.etreeNamespaceFormat,
         BaseClass[0] if len(BaseClass) == 1 else None,
-        schema = etree.XMLSchema(etree.fromstring(xsdstring)),
+        etree.XMLSchema(etree.fromstring(xsdstring)),
         strip_cdata = False, remove_blank_text=True)
     class_lookup = XMLElementClassLookUp(factory.ComputedClassesLookUp)
     parser.set_element_class_lookup(class_lookup)