115 lines
5.3 KiB
Python
115 lines
5.3 KiB
Python
"""Конвертирует нативный .archimate (формат Archi) в The Open Group
|
||
ArchiMate Model Exchange File Format — его понимают сторонние инструменты.
|
||
|
||
Вложенность узлов на схемах разворачивается в плоский список с абсолютными
|
||
координатами: в обменном формате трактовка координат вложенных узлов
|
||
неоднозначна, а плоская раскладка даёт одинаковый результат везде.
|
||
Контейнер выводится раньше содержимого, поэтому визуально вложенность
|
||
сохраняется.
|
||
"""
|
||
import sys, io, pathlib
|
||
import xml.etree.ElementTree as ET
|
||
from xml.sax.saxutils import escape, quoteattr
|
||
|
||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
||
XSI = "{http://www.w3.org/2001/XMLSchema-instance}type"
|
||
NS = "http://www.opengroup.org/xsd/archimate/3.0/"
|
||
|
||
src, dst = pathlib.Path(sys.argv[1]), pathlib.Path(sys.argv[2])
|
||
root = ET.parse(src).getroot()
|
||
|
||
def t(e):
|
||
return (e.get(XSI) or "").split(":")[-1]
|
||
|
||
def text(e, tag):
|
||
c = e.find(tag)
|
||
return c.text if c is not None and c.text else ""
|
||
|
||
elements, relationships, views = [], [], []
|
||
|
||
for folder in root.findall("folder"):
|
||
ftype = folder.get("type")
|
||
for e in folder.findall("element"):
|
||
et = t(e)
|
||
if ftype == "diagrams":
|
||
views.append(e)
|
||
elif et.endswith("Relationship"):
|
||
relationships.append((e.get("id"), et[:-len("Relationship")],
|
||
e.get("source"), e.get("target")))
|
||
else:
|
||
elements.append((e.get("id"), et, e.get("name") or "", text(e, "documentation")))
|
||
|
||
def flatten(node, ox=0, oy=0, out=None, conns=None):
|
||
"""Возвращает плоский список узлов с абсолютными координатами и соединения."""
|
||
out = [] if out is None else out
|
||
conns = [] if conns is None else conns
|
||
b = node.find("bounds")
|
||
x = ox + int(b.get("x", 0)); y = oy + int(b.get("y", 0))
|
||
# у надписей (Note) нет archimateElement — в обменном формате это узел типа Label
|
||
out.append((node.get("id"), node.get("archimateElement"),
|
||
x, y, int(b.get("width", 120)), int(b.get("height", 55)),
|
||
text(node, "content")))
|
||
for c in node.findall("sourceConnection"):
|
||
conns.append((c.get("id"), c.get("relationship"),
|
||
c.get("source"), c.get("target")))
|
||
for ch in node.findall("child"):
|
||
flatten(ch, x, y, out, conns)
|
||
return out, conns
|
||
|
||
out = ['<?xml version="1.0" encoding="UTF-8"?>',
|
||
f'<model xmlns="{NS}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',
|
||
f' xsi:schemaLocation="{NS} https://www.opengroup.org/xsd/archimate/3.1/archimate3_Diagram.xsd"',
|
||
f' identifier="{root.get("id")}">',
|
||
f' <name xml:lang="ru">{escape(root.get("name") or "model")}</name>']
|
||
purpose = root.find("purpose")
|
||
if purpose is not None and purpose.text:
|
||
out.append(f' <documentation xml:lang="ru">{escape(purpose.text)}</documentation>')
|
||
|
||
out.append(" <elements>")
|
||
for eid, etype, name, doc in elements:
|
||
out.append(f' <element identifier="{eid}" xsi:type="{etype}">')
|
||
out.append(f' <name xml:lang="ru">{escape(name)}</name>')
|
||
if doc:
|
||
out.append(f' <documentation xml:lang="ru">{escape(doc)}</documentation>')
|
||
out.append(" </element>")
|
||
out.append(" </elements>")
|
||
|
||
out.append(" <relationships>")
|
||
for rid, rtype, s, tg in relationships:
|
||
out.append(f' <relationship identifier="{rid}" source="{s}" target="{tg}" xsi:type="{rtype}"/>')
|
||
out.append(" </relationships>")
|
||
|
||
out.append(" <views>")
|
||
out.append(" <diagrams>")
|
||
for v in views:
|
||
out.append(f' <view identifier="{v.get("id")}" xsi:type="Diagram">')
|
||
out.append(f' <name xml:lang="ru">{escape(v.get("name") or "")}</name>')
|
||
doc = text(v, "documentation")
|
||
if doc:
|
||
out.append(f' <documentation xml:lang="ru">{escape(doc)}</documentation>')
|
||
nodes, conns = [], []
|
||
for child in v.findall("child"):
|
||
flatten(child, 0, 0, nodes, conns)
|
||
for nid, eref, x, y, w, h, content in nodes:
|
||
if eref:
|
||
out.append(f' <node identifier="{nid}" elementRef="{eref}" xsi:type="Element" '
|
||
f'x="{x}" y="{y}" w="{w}" h="{h}"/>')
|
||
else:
|
||
out.append(f' <node identifier="{nid}" xsi:type="Label" '
|
||
f'x="{x}" y="{y}" w="{w}" h="{h}">')
|
||
out.append(f' <label xml:lang="ru">{escape(content)}</label>')
|
||
out.append(' </node>')
|
||
for cid, rref, s, tg in conns:
|
||
out.append(f' <connection identifier="{cid}" relationshipRef="{rref}" '
|
||
f'xsi:type="Relationship" source="{s}" target="{tg}"/>')
|
||
out.append(" </view>")
|
||
out.append(" </diagrams>")
|
||
out.append(" </views>")
|
||
out.append("</model>")
|
||
|
||
dst.write_text("\n".join(out) + "\n", encoding="utf-8", newline="\n")
|
||
print(f"записано: {dst}")
|
||
print(f" элементов: {len(elements)}")
|
||
print(f" отношений: {len(relationships)}")
|
||
print(f" представлений: {len(views)}")
|