250 lines
9.4 KiB
Python
250 lines
9.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Wandelt TIA-Openness SimaticML XML (StatementList) in lesbaren AWL-Text um."""
|
|
import re
|
|
import sys
|
|
import xml.etree.ElementTree as ET
|
|
|
|
NS_IF = "{http://www.siemens.com/automation/Openness/SW/Interface/v5}"
|
|
NS_SL = "{http://www.siemens.com/automation/Openness/SW/NetworkSource/StatementList/v5}"
|
|
|
|
TOKEN_MAP = {
|
|
"A_BRACK": "A(", "O_BRACK": "O(", "AN_BRACK": "AN(", "ON_BRACK": "ON(",
|
|
"X_BRACK": "X(", "XN_BRACK": "XN(", "BRACKET": ")",
|
|
"NE_I": "<>I", "EQ_I": "==I", "GT_I": ">I", "LT_I": "<I", "GE_I": ">=I", "LE_I": "<=I",
|
|
"NE_D": "<>D", "EQ_D": "==D", "GT_D": ">D", "LT_D": "<D", "GE_D": ">=D", "LE_D": "<=D",
|
|
"NE_R": "<>R", "EQ_R": "==R", "GT_R": ">R", "LT_R": "<R", "GE_R": ">=R", "LE_R": "<=R",
|
|
"ADD_I": "+I", "SUB_I": "-I", "MUL_I": "*I", "DIV_I": "/I",
|
|
"ADD_D": "+D", "SUB_D": "-D", "MUL_D": "*D", "DIV_D": "/D",
|
|
"ADD_R": "+R", "SUB_R": "-R", "MUL_R": "*R", "DIV_R": "/R",
|
|
"ADD": "+", "NOP_0": "NOP 0", "NOP_1": "NOP 1",
|
|
"TAR2_ACCU1": "TAR2", "TAR1_ACCU1": "TAR1",
|
|
"LAR1_ACCU1": "LAR1", "LAR2_ACCU1": "LAR2",
|
|
"OPEN_DB": "OPN", "OPEN_DI": "OPN",
|
|
"EMPTY_LINE": "", "COMMENT": "//", "Assign": "=",
|
|
}
|
|
|
|
|
|
def fmt_symbol(access):
|
|
parts = []
|
|
for comp in access.findall(f"./{NS_SL}Symbol/{NS_SL}Component"):
|
|
name = comp.get("Name")
|
|
token = name if re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", name) else f'"{name}"'
|
|
idx = [fmt_access(a) for a in comp.findall(f"{NS_SL}Access")]
|
|
if idx:
|
|
token += "[" + ",".join(idx) + "]"
|
|
if comp.get("SliceAccessModifier"):
|
|
token += "." + comp.get("SliceAccessModifier")
|
|
parts.append(token)
|
|
sym = ".".join(parts)
|
|
if access.get("Scope") == "GlobalVariable" and parts and not parts[0].startswith('"'):
|
|
sym = f'"{parts[0][1:-1] if parts[0].startswith(chr(34)) else parts[0]}"' + (
|
|
"." + ".".join(parts[1:]) if len(parts) > 1 else "")
|
|
return sym
|
|
|
|
|
|
def fmt_address(addr):
|
|
area = addr.get("Area")
|
|
typ = addr.get("Type")
|
|
bit = int(addr.get("BitOffset", 0))
|
|
blk = addr.get("BlockNumber")
|
|
width = {"Bool": 1, "Byte": 8, "Word": 16, "DWord": 32, "Int": 16, "DInt": 32}.get(typ, 0)
|
|
sizech = {1: "", 8: "B", 16: "W", 32: "D"}.get(width, "?")
|
|
areach = {"Local": "L", "Memory": "M", "Input": "I", "Output": "Q", "DB": "DB", "DI": "DI",
|
|
"Timer": "T", "Counter": "C", "PeripheryInput": "PI", "PeripheryOutput": "PQ"}.get(area, area)
|
|
if area in ("DB", "DI"):
|
|
pre = f"DB{blk}." if blk else ""
|
|
core = f"{pre}{'DBX' if width == 1 else 'DB' + sizech}{bit // 8}"
|
|
elif area == "Timer":
|
|
return f"T {bit}"
|
|
elif area == "Counter":
|
|
return f"C {bit}"
|
|
else:
|
|
core = f"{areach}{sizech}{bit // 8}"
|
|
if width == 1:
|
|
core += f".{bit % 8}"
|
|
return core
|
|
|
|
|
|
def fmt_indirect(ind):
|
|
width = ind.get("Width")
|
|
area = ind.get("Area")
|
|
reg = ind.get("Register", "")
|
|
inner = [fmt_access(a) for a in ind.findall(f"{NS_SL}Access")]
|
|
const = ind.find(f"{NS_SL}Constant")
|
|
off = ""
|
|
if const is not None:
|
|
off = const.findtext(f"{NS_SL}ConstantValue", "")
|
|
if width == "Block": # OPN DB[var]
|
|
return f"DB[{inner[0]}]" if inner else f"DB[{off}]"
|
|
sizech = {"Bit": "X", "Byte": "B", "Word": "W", "Double": "D", "DWord": "D"}.get(width, "?")
|
|
areach = {"DB": "DB", "DI": "DI", "Local": "L", "Memory": "M", "Input": "I", "Output": "Q"}.get(area, area or "")
|
|
base = f"{areach}{sizech}" if areach not in ("DB", "DI") else f"{areach}{sizech}"
|
|
if reg: # registerindirekt
|
|
return f"{base} [{reg},{off}]"
|
|
if inner: # speicherindirekt
|
|
return f"{base} [{inner[0]}]"
|
|
return f"{base} [{off}]"
|
|
|
|
|
|
def fmt_access(access):
|
|
scope = access.get("Scope")
|
|
if scope in ("LocalVariable", "GlobalVariable", "LocalConstant", "GlobalConstant"):
|
|
return fmt_symbol(access)
|
|
if scope in ("LiteralConstant", "TypedConstant"):
|
|
c = access.find(f"{NS_SL}Constant")
|
|
val = c.findtext(f"{NS_SL}ConstantValue", "") if c is not None else ""
|
|
typ = c.findtext(f"{NS_SL}ConstantType", "") if c is not None else ""
|
|
pref = {"Word": "W#16#", "DWord": "DW#16#", "Time": "", "S5Time": "", "Byte": "B#16#"}
|
|
if typ in ("Word", "DWord", "Byte") and not val.startswith(("2#", "16#", "W#", "DW#", "B#")):
|
|
return pref[typ] + val
|
|
return val
|
|
if scope == "AddressConstant":
|
|
c = access.find(f"{NS_SL}Constant")
|
|
return "P#" + c.findtext(f"{NS_SL}ConstantValue", "") if c is not None else "P#?"
|
|
if scope == "Address":
|
|
addr = access.find(f"{NS_SL}Address")
|
|
if addr is not None:
|
|
return fmt_address(addr)
|
|
ind = access.find(f"{NS_SL}Indirect")
|
|
if ind is not None:
|
|
return fmt_indirect(ind)
|
|
return "?ADDR?"
|
|
if scope == "Label":
|
|
lab = access.find(f"{NS_SL}Label")
|
|
return lab.get("Name") if lab is not None else "?LBL?"
|
|
if scope == "LabelDefinition":
|
|
lab = access.find(f"{NS_SL}Label")
|
|
return (lab.get("Name") + ":") if lab is not None else "?LBL?:"
|
|
if scope == "Statusword":
|
|
return access.find(f"{NS_SL}Statusword").get("Name", "?SW?")
|
|
if scope == "Call":
|
|
ci = access.find(f"{NS_SL}CallInfo")
|
|
if ci is None:
|
|
ins = access.find(f"{NS_SL}Instruction")
|
|
name = ins.get("Name")
|
|
params = []
|
|
for p in ins.findall(f"{NS_SL}Parameter"):
|
|
pa = p.find(f"{NS_SL}Access")
|
|
params.append(f"{p.get('Name')}:={fmt_access(pa) if pa is not None else '?'}")
|
|
return f"CALLINFO SYS \"{name}\" (" + ", ".join(params) + ")"
|
|
name, btype = ci.get("Name"), ci.get("BlockType")
|
|
inst = ci.find(f"{NS_SL}Instance")
|
|
s = f'"{name}"'
|
|
if inst is not None:
|
|
s += " , " + fmt_symbol(inst)
|
|
params = []
|
|
for p in ci.findall(f"{NS_SL}Parameter"):
|
|
pa = p.find(f"{NS_SL}Access")
|
|
params.append(f"{p.get('Name')}:={fmt_access(pa) if pa is not None else '?'}")
|
|
if params:
|
|
s += " (" + ", ".join(params) + ")"
|
|
return f"CALLINFO {btype} {s}"
|
|
return f"?{scope}?"
|
|
|
|
|
|
def render_statement(st):
|
|
label = ""
|
|
ld = st.find(f"{NS_SL}LabelDeclaration/{NS_SL}Label")
|
|
if ld is not None:
|
|
label = ld.get("Name") + ": "
|
|
toks = st.findall(f"{NS_SL}StlToken")
|
|
accesses = st.findall(f"{NS_SL}Access")
|
|
comments = []
|
|
for lc in st.iter(f"{NS_SL}LineComment"):
|
|
t = lc.findtext(f"{NS_SL}Text", "")
|
|
if t:
|
|
comments.append(t)
|
|
for cm in st.findall(f"{NS_SL}Comment"):
|
|
t = cm.findtext(f"{NS_SL}Text", "")
|
|
if t:
|
|
comments.append(t)
|
|
if not toks:
|
|
if label:
|
|
return label.rstrip()
|
|
return "// " + " | ".join(comments) if comments else ""
|
|
tok = toks[0].get("Text")
|
|
mn = TOKEN_MAP.get(tok, tok)
|
|
if tok == "EMPTY_LINE":
|
|
return label.rstrip() if label else ""
|
|
if tok == "COMMENT":
|
|
return "// " + " | ".join(comments)
|
|
ops = []
|
|
for a in accesses:
|
|
f = fmt_access(a)
|
|
if f.startswith("CALLINFO"):
|
|
return f"{label}CALL {f[9:]}" + (" // " + " | ".join(comments) if comments else "")
|
|
ops.append(f)
|
|
line = f"{label:6s}{mn:6s} " + ", ".join(ops)
|
|
if comments:
|
|
line = f"{line:60s} // " + " | ".join(comments)
|
|
return line.rstrip()
|
|
|
|
|
|
def render_interface(root):
|
|
out = []
|
|
for sec in root.iter(f"{NS_IF}Section"):
|
|
members = sec.findall(f"{NS_IF}Member")
|
|
out.append(f" {sec.get('Name')}:")
|
|
for m in members:
|
|
out.extend(render_member(m, 4))
|
|
return out
|
|
|
|
|
|
def render_member(m, indent):
|
|
out = []
|
|
cm = m.find(f"{NS_IF}Comment/{NS_IF}MultiLanguageText")
|
|
sv = m.findtext(f"{NS_IF}StartValue")
|
|
line = " " * indent + f"{m.get('Name')} : {m.get('Datatype')}"
|
|
if sv:
|
|
line += f" := {sv}"
|
|
if cm is not None and cm.text:
|
|
line = f"{line:55s} // {cm.text}"
|
|
out.append(line)
|
|
for sub in m.findall(f"{NS_IF}Member"):
|
|
out.extend(render_member(sub, indent + 2))
|
|
return out
|
|
|
|
|
|
def main(path):
|
|
s = open(path, encoding="utf-8").read()
|
|
root = ET.fromstring(s)
|
|
blk = None
|
|
for tag in ("FB", "FC", "OB", "GlobalDB", "InstanceDB"):
|
|
blk = root.find(f".//SW.Blocks.{tag}")
|
|
if blk is not None:
|
|
btype = tag
|
|
break
|
|
attrs = blk.find("AttributeList")
|
|
name = attrs.findtext("Name")
|
|
num = attrs.findtext("Number")
|
|
lang = attrs.findtext("ProgrammingLanguage")
|
|
print(f"=== {btype} {num}: {name} [{lang}] ===")
|
|
iface = attrs.find("Interface")
|
|
if iface is not None:
|
|
secs = iface.find(f"{NS_IF}Sections")
|
|
if secs is not None:
|
|
print("INTERFACE:")
|
|
for line in render_interface(secs):
|
|
print(line)
|
|
nwno = 0
|
|
for cu in blk.iter("SW.Blocks.CompileUnit"):
|
|
nwno += 1
|
|
title = ""
|
|
for mt in cu.iter("MultilingualTextItem"):
|
|
al = mt.find("AttributeList")
|
|
if al is not None and al.findtext("Culture") == "de-DE" and al.findtext("Text"):
|
|
title = al.findtext("Text")
|
|
break
|
|
print(f"\n--- NW {nwno}: {title} ---")
|
|
src = cu.find(f".//{NS_SL}StatementList")
|
|
if src is None:
|
|
print(" (kein STL-Quelltext)")
|
|
continue
|
|
for st in src.findall(f"{NS_SL}StlStatement"):
|
|
print(render_statement(st))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
for p in sys.argv[1:]:
|
|
main(p)
|