# HG changeset patch # User Edouard Tisserant # Date 1380351590 -32400 # Node ID 7f264cc6e75d39713245554a364e0ea89124ace3 # Parent 9d0cb01312f0f35740251ddceb811fb04f8e2f35# Parent ac7d39f4e37650d457b64b13c3b79e7a97159be5 merged diff -r 9d0cb01312f0 -r 7f264cc6e75d CodeFileTreeNode.py --- 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 = """ )(?:)(?!)"), "]]>")]: 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) diff -r 9d0cb01312f0 -r 7f264cc6e75d ConfigTreeNode.py --- 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 @@ """) 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): diff -r 9d0cb01312f0 -r 7f264cc6e75d PLCControler.py --- 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 == "": diff -r 9d0cb01312f0 -r 7f264cc6e75d PLCOpenEditor.py --- 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(): diff -r 9d0cb01312f0 -r 7f264cc6e75d ProjectController.py --- 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 diff -r 9d0cb01312f0 -r 7f264cc6e75d editors/Viewer.py --- 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) diff -r 9d0cb01312f0 -r 7f264cc6e75d plcopen/plcopen.py --- 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 @@ """ % 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("(?)(?:)(?!)"), "]]>")]: 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"]: diff -r 9d0cb01312f0 -r 7f264cc6e75d plcopen/structures.py --- 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."), diff -r 9d0cb01312f0 -r 7f264cc6e75d py_ext/PythonFileCTNMixin.py --- 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("(?)(?:)(?!)"), "]]>")]: 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") diff -r 9d0cb01312f0 -r 7f264cc6e75d xmlclass/xmlclass.py --- 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)