--- a +++ b/PlcOpenXmlLadderToAsciiLadder.py @@ -0,0 +1,275 @@ +# PlcOpenXmlLadderToAsciiLadder +import sys +import xml.dom.minidom + +def howCloseAmIToLeftPowerRail(num, ladder, element): + # Function to work out how close an element is to the left... + # element is a dom element of contact, coil, block + # ladder is the whole ladder + # num is the current distance, initially zero + if element.tagName == "contact" or element.tagName == "coil" or element.tagName == "leftPowerRail": + _num = num + 1 + else: # "block", maybe "execute"? + _num = num + 3 + highestnum = _num + for conny in element.getElementsByTagName("connection"): + for _elly in ladder.childNodes: + if _elly.nodeType != _elly.TEXT_NODE: + if _elly.getAttribute("localId") == conny.getAttribute("refLocalId"): + # pass how close this element is + connysNum = howCloseAmIToLeftPowerRail(_num, ladder, _elly) + if connysNum > highestnum: + highestnum = connysNum + return highestnum + +# main +if len(sys.argv) == 2: + filepathin = sys.argv[1] + print(filepathin) + doc = xml.dom.minidom.parse(filepathin) + # get the first (currently only) entire declaration header as a string + declarationheader = doc.getElementsByTagName("InterfaceAsPlainText")[0].getElementsByTagName("xhtml")[0].childNodes[0].data + + # get the first (currently only) ladder element + ladder = doc.getElementsByTagName("LD")[0] + widthOfLadder = 4 # will grow by itself if the ladder needs it + foundANewLabel = False + networkNumber = 0 + rowId = 0 + + gridladder = xml.dom.minidom.Document() + gridladder.appendChild(gridladder.createElement("gridLD")) + + for elly in ladder.childNodes: + #Close a branch + # <contact> + # <connectionPointIn> + # <connection refLocalId="39" /> + # <connection refLocalId="40" /> + # </connectionPointIn> + #Open a branch + # <contact > + # <connectionPointIn> + # <connection refLocalId="0" /> + # </connectionPointIn> + # </contact> + # <contact > + # <connectionPointIn> + # <connection refLocalId="0" /> + # </connectionPointIn> + #leftPowerRail + #comment + #label + if elly.nodeName == "label": + print(elly.getAttribute("label")) + # create new <row>, add this label to the zeroth column + networkNumber += 1 + gridladder.firstChild.appendChild(gridladder.createElement("row")) + gridladder.firstChild.lastChild.setAttribute("rowId",str(rowId)) + rowId += 1 + gridladder.firstChild.lastChild.appendChild(gridladder.createElement("Column")) + gridladder.firstChild.lastChild.lastChild.setAttribute("columnId","0") + gridladder.firstChild.lastChild.lastChild.setAttribute("networkId",str(networkNumber)) + gridladder.firstChild.lastChild.lastChild.setAttribute("label",elly.getAttribute("label")) + foundANewLabel = True + #vendorElement (both new network and parallel branches) + if elly.nodeName == "vendorElement": + if elly.getElementsByTagName("ElementType").length > 0: + print("NewNetwork") + if foundANewLabel:# already found a label + foundANewLabel = False + else: + networkNumber += 1 + gridladder.firstChild.appendChild(gridladder.createElement("row")) + gridladder.firstChild.lastChild.setAttribute("rowId",str(rowId)) + rowId += 1 + + gridladder.firstChild.lastChild.appendChild(gridladder.createElement("Column")) + gridladder.firstChild.lastChild.lastChild.setAttribute("columnId","0") + gridladder.firstChild.lastChild.lastChild.setAttribute("networkId",str(networkNumber)) + elif elly.getElementsByTagName("ParallelBranch"): + print("New ParallelBranch Short Circuit") + #inVariable (these are your inputs to blocks) + if elly.nodeName == "inVariable": + print("NewInvariable") + #block (FB, F etc) + if elly.nodeName == "block": + print("NewBlock") + mydistance = howCloseAmIToLeftPowerRail(0,ladder,elly) + if mydistance > widthOfLadder: + widthOfLadder = mydistance + + if elly.getElementsByTagName("connection")[0].getAttribute("refLocalId") == "0": + # that means it's leftmost object + if gridladder.firstChild.lastChild.lastChild.getAttribute("columnId") != "0": + # that means we parallel branch and need a new row + gridladder.firstChild.appendChild(gridladder.createElement("row")) + gridladder.firstChild.lastChild.setAttribute("rowId",str(rowId)) + rowId += 1 + gridladder.firstChild.lastChild.appendChild(gridladder.createElement("Column")) + gridladder.firstChild.lastChild.lastChild.setAttribute("columnId","1") + gridladder.firstChild.lastChild.lastChild.setAttribute("localId",elly.getAttribute("localId")) + gridladder.firstChild.lastChild.lastChild.setAttribute("width","3") + else: # that means it points behind another object + highestRowId = "65535" + widestColumn = "0" + for ellysUpstream in elly.getElementsByTagName("connection"): + # may be more than one upstream. + # The first downstream will be in the highest row of the upstream and one column back from the furthest back upstream + ellysUpstreamId = ellysUpstream.getAttribute("refLocalId") + for gridellys in gridladder.firstChild.getElementsByTagName("Column"): + if gridellys.getAttribute("localId") == ellysUpstreamId: + if highestRowId > gridellys.parentNode.getAttribute("rowId"): + highestRowId = gridellys.parentNode.getAttribute("rowId") + highestRow = gridellys.parentNode + widestColumn = max(widestColumn, str(int(gridellys.getAttribute("columnId")) + int(gridellys.getAttribute("width")))) + # Is there anyone at that RowColumn? + someoneIsThereAlready = False + for existingColumns in highestRow.getElementsByTagName("Column"): + if existingColumns.getAttribute("columnId") >= widestColumn: + someoneIsThereAlready = True + if not someoneIsThereAlready : + highestRow.appendChild(gridladder.createElement("Column")) + highestRow.lastChild.setAttribute("columnId",widestColumn) + highestRow.lastChild.setAttribute("localId",elly.getAttribute("localId")) + highestRow.lastChild.setAttribute("width","3") + # TODO Do something else if someone is already there + # TODO You will have to flag that it is parallel + #contact + if elly.nodeName == "contact": + if elly.getAttribute("negated") == "true": + print("NewNegatedContact") + else: + print("NewContact") + + mydistance = howCloseAmIToLeftPowerRail(0,ladder,elly) + if mydistance > widthOfLadder: + widthOfLadder = mydistance + + if elly.getElementsByTagName("connection")[0].getAttribute("refLocalId") == "0": + # that means it's leftmost object + if gridladder.firstChild.lastChild.lastChild.getAttribute("columnId") != "0": + # that means we parallel branch and need a new row + gridladder.firstChild.appendChild(gridladder.createElement("row")) + gridladder.firstChild.lastChild.setAttribute("rowId",str(rowId)) + rowId += 1 + gridladder.firstChild.lastChild.appendChild(gridladder.createElement("Column")) + gridladder.firstChild.lastChild.lastChild.setAttribute("columnId","1") + gridladder.firstChild.lastChild.lastChild.setAttribute("localId",elly.getAttribute("localId")) + gridladder.firstChild.lastChild.lastChild.setAttribute("width","1") + else: # that means it points behind another object + highestRowId = "65535" + widestColumn = "0" + for ellysUpstream in elly.getElementsByTagName("connection"): + # may be more than one upstream. + # The first downstream will be in the highest row of the upstream and one column back from the furthest back upstream + ellysUpstreamId = ellysUpstream.getAttribute("refLocalId") + for gridellys in gridladder.firstChild.getElementsByTagName("Column"): + if gridellys.getAttribute("localId") == ellysUpstreamId: + if highestRowId > gridellys.parentNode.getAttribute("rowId"): + highestRowId = gridellys.parentNode.getAttribute("rowId") + highestRow = gridellys.parentNode + widestColumn = max(widestColumn, str(int(gridellys.getAttribute("columnId")) + int(gridellys.getAttribute("width")))) + # Is there anyone at that RowColumn? + someoneIsThereAlready = False + for existingColumns in highestRow.getElementsByTagName("Column"): + if existingColumns.getAttribute("columnId") >= widestColumn: + someoneIsThereAlready = True + if not someoneIsThereAlready : + highestRow.appendChild(gridladder.createElement("Column")) + highestRow.lastChild.setAttribute("columnId",widestColumn) + highestRow.lastChild.setAttribute("localId",elly.getAttribute("localId")) + highestRow.lastChild.setAttribute("width","1") + # TODO Do something else if someone is already there + # TODO You will have to flag that it is parallel + + + + #coil + if elly.nodeName == "coil": + if elly.getAttribute("storage") == "none": + print("NewCoil") + elif elly.getAttribute("storage") == "set": + print("NewSetCoil") + elif elly.getAttribute("storage") == "reset": + print("NewResetCoil") + + mydistance = howCloseAmIToLeftPowerRail(0,ladder,elly) + if mydistance > widthOfLadder: + widthOfLadder = mydistance + + if elly.getElementsByTagName("connection")[0].getAttribute("refLocalId") == "0": + # that means it's leftmost object + if gridladder.firstChild.lastChild.lastChild.getAttribute("columnId") != "0": + # that means we parallel branch and need a new row + gridladder.firstChild.appendChild(gridladder.createElement("row")) + gridladder.firstChild.lastChild.setAttribute("rowId",str(rowId)) + rowId += 1 + gridladder.firstChild.lastChild.appendChild(gridladder.createElement("Column")) + gridladder.firstChild.lastChild.lastChild.setAttribute("columnId","1") + gridladder.firstChild.lastChild.lastChild.setAttribute("localId",elly.getAttribute("localId")) + gridladder.firstChild.lastChild.lastChild.setAttribute("width","1") + else: # that means it points behind another object + highestRowId = "65535" + widestColumn = "0" + for ellysUpstream in elly.getElementsByTagName("connection"): + # may be more than one upstream. + # The first downstream will be in the highest row of the upstream and one column back from the furthest back upstream + ellysUpstreamId = ellysUpstream.getAttribute("refLocalId") + for gridellys in gridladder.firstChild.getElementsByTagName("Column"): + if gridellys.getAttribute("localId") == ellysUpstreamId: + if highestRowId > gridellys.parentNode.getAttribute("rowId"): + highestRowId = gridellys.parentNode.getAttribute("rowId") + highestRow = gridellys.parentNode + widestColumn = max(widestColumn, str(int(gridellys.getAttribute("columnId")) + int(gridellys.getAttribute("width")))) + # Is there anyone at that RowColumn? + someoneIsThereAlready = False + for existingColumns in highestRow.getElementsByTagName("Column"): + if existingColumns.getAttribute("columnId") >= widestColumn: + someoneIsThereAlready = True + if not someoneIsThereAlready : + highestRow.appendChild(gridladder.createElement("Column")) + highestRow.lastChild.setAttribute("columnId",widestColumn) + highestRow.lastChild.setAttribute("localId",elly.getAttribute("localId")) + highestRow.lastChild.setAttribute("width","1") + # TODO Do something else if someone is already there + # TODO You will have to flag that it is parallel + + #rightPowerRail (think CODESYS were having a laugh) + # How wide to make network? well: + # 1. contacts and coils = 1 + # 2. blocks and executes = 3 + # 3. branches themselves don't take up additional room + # 4. but... you have to work out the longest path on each branch + # Data is presented localId + refLocalId [+ refLocalId + ...] + # So I guess it is easiest to work our way backwards. (from right to left) + # We will naïvely look at every localId and how long it takes to get back to refLocalId=0 + # We also naïvely look at every possible localId, even if we've already found him + # We've actually already calculated that above, yay for me! + print(widthOfLadder) + + # Array of networks + # Network is an array of Contact / Block / Coil + # So we know it is (widthOfLadder) wide... so we could have an array.. no that's not right + # 1. Pass through every element + # 2. hmm, lets make a new ladder xml, which is the rows... input variables and labels can be added last + # 3. <Row> + # <Column columnId="0" refLocalId=(localID of vendorElement new network) /> + # <Column columnId="1" refLocalId=(localID of next contact/block/coil) /> + # 4. The next element comes in. It asks if anybody already has the row right behind its refLocalId + # If someone is already there, it starts a new Row, starting at the columnId just behind + # 5. If it has two refLocalIds, it tries to get the furthest back column + print (str(gridladder.toprettyxml())) + + # should now have a declaration section + # a bunch of inputs + # a bunch of outputs + # and a bunch of blocks + # convert them all to a nice easy format + print ("{Declaration}") + print (declarationheader) + print ("{/Declaration}") +else: + print("Maybe I want one argument, whch is the PLCOPEN XML.") + print("Will output to current directory as Ladder0.ladder") +