"""Рендерит представления из .archimate в один самодостаточный HTML со встроенным SVG. Нужен, чтобы схемы можно было посмотреть без установки Archi.""" import sys, io, pathlib, html import xml.etree.ElementTree as ET sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") XSI = "{http://www.w3.org/2001/XMLSchema-instance}type" src, dst = pathlib.Path(sys.argv[1]), pathlib.Path(sys.argv[2]) root = ET.parse(src).getroot() LAYER = { # цвета Archi по слоям "ApplicationComponent": "#B5FFFF", "ApplicationService": "#B5FFFF", "Node": "#C9E7B7", "SystemSoftware": "#C9E7B7", "TechnologyService": "#C9E7B7", "Artifact": "#C9E7B7", } STROKE = {"Application": "#2a7f8f", "Technology": "#4a7f3a"} concepts, rels = {}, {} for folder in root.findall("folder"): for e in folder.findall("element"): et = (e.get(XSI) or "").split(":")[-1] if et.endswith("Relationship"): rels[e.get("id")] = (et[:-len("Relationship")], e.get("source"), e.get("target")) elif et != "ArchimateDiagramModel": concepts[e.get("id")] = (et, e.get("name") or "") def collect(node, ox, oy, nodes, conns): b = node.find("bounds") x, y = ox + int(b.get("x", 0)), oy + int(b.get("y", 0)) w, h = int(b.get("width", 120)), int(b.get("height", 55)) nodes[node.get("id")] = (node.get("archimateElement"), x, y, w, h) for c in node.findall("sourceConnection"): conns.append((c.get("relationship"), c.get("source"), c.get("target"))) for ch in node.findall("child"): collect(ch, x, y, nodes, conns) def wrap(s, width): out, line = [], "" for word in s.split(): if len(line) + len(word) + 1 > width and line: out.append(line); line = word else: line = f"{line} {word}".strip() if line: out.append(line) return out[:4] def edge_point(x, y, w, h, tx, ty): """Точка пересечения луча к (tx,ty) с границей прямоугольника.""" cx, cy = x + w / 2, y + h / 2 dx, dy = tx - cx, ty - cy if dx == 0 and dy == 0: return cx, cy sx = (w / 2) / abs(dx) if dx else float("inf") sy = (h / 2) / abs(dy) if dy else float("inf") s = min(sx, sy) return cx + dx * s, cy + dy * s MARKERS = """ """ STYLE = { "Serving": ('stroke-dasharray="none"', 'marker-end="url(#open)"', ""), "Realization": ('stroke-dasharray="6,4"', 'marker-end="url(#hollow)"', ""), "Composition": ('stroke-dasharray="none"', "", 'marker-start="url(#diamond)"'), "Assignment": ('stroke-dasharray="none"', 'marker-end="url(#filled)"', 'marker-start="url(#ball)"'), } parts = [] views = [e for f in root.findall("folder") for e in f.findall("element") if (e.get(XSI) or "").endswith("ArchimateDiagramModel")] for v in views: nodes, conns = {}, [] for ch in v.findall("child"): collect(ch, 0, 0, nodes, conns) if not nodes: continue maxx = max(x + w for _, x, _, w, _ in nodes.values()) + 40 maxy = max(y + h for _, _, y, _, h in nodes.values()) + 40 svg = [f'', MARKERS] # связи под блоками for rid, so, to in conns: if so not in nodes or to not in nodes or rid not in rels: continue rtype = rels[rid][0] _, sx, sy, sw, sh = nodes[so] _, tx, ty, tw, th = nodes[to] x1, y1 = edge_point(sx, sy, sw, sh, tx + tw / 2, ty + th / 2) x2, y2 = edge_point(tx, ty, tw, th, sx + sw / 2, sy + sh / 2) dash, mend, mstart = STYLE.get(rtype, ('stroke-dasharray="none"', 'marker-end="url(#open)"', "")) svg.append(f'') # блоки: контейнеры раньше содержимого — сортируем по площади убыв. for nid, (eref, x, y, w, h) in sorted(nodes.items(), key=lambda kv: -kv[1][3] * kv[1][4]): etype, name = concepts.get(eref, ("", eref or "?")) fill = LAYER.get(etype, "#EEEEEE") stroke = STROKE["Application"] if etype.startswith("Application") else STROKE["Technology"] big = w * h > 60000 svg.append(f'') lines = wrap(name, max(12, w // 7)) if big: # подпись контейнера — сверху слева svg.append(f'{html.escape(name)}') else: fs = 11 if len(lines) > 2 else 12 y0 = y + h / 2 - (len(lines) - 1) * fs * 0.62 for i, ln in enumerate(lines): svg.append(f'' f'{html.escape(ln)}') svg.append("") doc = v.find("documentation") parts.append(f'

{html.escape(v.get("name") or "")}

' + (f'

{html.escape(doc.text)}

' if doc is not None and doc.text else "") + "".join(svg) + "
") page = f""" {html.escape(root.get('name') or 'Модель')}

{html.escape(root.get('name') or '')}

Автогенерация из ugmk-technology.archimate. Зелёный — технологический слой, голубой — прикладной. Пунктир с полым треугольником — realization, стрелка — serving, ромб — composition, линия с шариком — assignment.

{''.join(parts)} """ dst.write_text(page, encoding="utf-8", newline="\n") print(f"записано: {dst} ({len(parts)} представлений)")