mirror of
https://github.com/github/awesome-copilot.git
synced 2026-04-12 19:25:55 +00:00
* Add draw-io diagram generator skill for awesome github copilot * Add comprehensive shape libraries and style reference documentation for draw.io - Introduced a new markdown file for draw.io shape libraries detailing various built-in shapes, their style keys, and usage. - Added a complete style reference for `<mxCell>` elements, including universal style keys, shape-specific keys, edge styles, and color palettes. - Included examples for common styles and shapes to aid users in creating diagrams effectively. * Add draw-io diagram validation and shape addition scripts * Add new diagram templates for flowchart, sequence, and UML class diagrams - Created a flowchart template with a structured layout including start, steps, decision points, and end. - Added a sequence diagram template illustrating interactions between a client, API server, and database with activation boxes and message arrows. - Introduced a UML class diagram template featuring an interface, classes, attributes, methods, and relationships, including composition and realization. * Add draw-io diagram generator skill to README with detailed usage instructions and bundled assets * Add draw.io instructions with workflow, XML structure rules, style conventions, and validation checklist * Add draw.io diagram standards to README instructions for enhanced diagram creation and editing * Moving diagram templates to assets/ to follow agentskills structure - Moved flowchart template with start, steps, decision points, and end nodes. - Moved sequence diagram template illustrating interactions between a client, API server, and database. - Moved UML class diagram template featuring an interface, classes, attributes, methods, and relationships. * Clarify installation instructions for draw.io VS Code extension in SKILL.md
215 lines
7.5 KiB
Python
215 lines
7.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
validate-drawio.py — Validate the XML structure of a .drawio diagram file.
|
|
|
|
Usage:
|
|
python scripts/validate-drawio.py <path-to-file.drawio>
|
|
|
|
Exit codes:
|
|
0 All checks passed
|
|
1 One or more validation errors found
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import xml.etree.ElementTree as ET
|
|
from pathlib import Path
|
|
|
|
|
|
def _error(msg: str, errors: list) -> None:
|
|
errors.append(msg)
|
|
print(f" ERROR: {msg}")
|
|
|
|
|
|
def validate_file(path: Path) -> list[str]:
|
|
"""Parse and validate a single .drawio file. Returns list of error strings."""
|
|
errors: list[str] = []
|
|
|
|
# --- XML well-formedness ---
|
|
try:
|
|
tree = ET.parse(path)
|
|
except ET.ParseError as exc:
|
|
return [f"XML parse error: {exc}"]
|
|
|
|
root = tree.getroot()
|
|
if root.tag != "mxfile":
|
|
_error(f"Root element must be <mxfile>, got <{root.tag}>", errors)
|
|
return errors
|
|
|
|
diagrams = root.findall("diagram")
|
|
if not diagrams:
|
|
_error("No <diagram> elements found inside <mxfile>", errors)
|
|
return errors
|
|
|
|
for d_idx, diagram in enumerate(diagrams):
|
|
d_name = diagram.get("name", f"page-{d_idx}")
|
|
prefix = f"[diagram '{d_name}']"
|
|
|
|
# Find mxGraphModel (may be direct child or base64-encoded; we handle direct only)
|
|
graph_model = diagram.find("mxGraphModel")
|
|
if graph_model is None:
|
|
print(f" SKIP {prefix}: mxGraphModel not found as direct child (may be compressed)")
|
|
continue
|
|
|
|
root_elem = graph_model.find("root")
|
|
if root_elem is None:
|
|
_error(f"{prefix} Missing <root> element inside <mxGraphModel>", errors)
|
|
continue
|
|
|
|
cells = root_elem.findall("mxCell")
|
|
cell_ids: dict[str, ET.Element] = {}
|
|
has_id0 = False
|
|
has_id1 = False
|
|
|
|
# --- Collect all IDs, check for root cells ---
|
|
for cell in cells:
|
|
cid = cell.get("id")
|
|
if cid is None:
|
|
_error(f"{prefix} Found <mxCell> without an 'id' attribute", errors)
|
|
continue
|
|
if cid in cell_ids:
|
|
_error(f"{prefix} Duplicate cell id='{cid}'", errors)
|
|
cell_ids[cid] = cell
|
|
if cid == "0":
|
|
has_id0 = True
|
|
if cid == "1":
|
|
has_id1 = True
|
|
|
|
if not has_id0:
|
|
_error(f"{prefix} Missing required root cell id='0'", errors)
|
|
if not has_id1:
|
|
_error(f"{prefix} Missing required default-layer cell id='1'", errors)
|
|
|
|
# L2: id="0" must be the first cell, id="1" must be the second cell
|
|
if len(cells) >= 1 and cells[0].get("id") != "0":
|
|
_error(
|
|
f"{prefix} First <mxCell> must have id='0', "
|
|
f"got id='{cells[0].get('id')}'",
|
|
errors,
|
|
)
|
|
if len(cells) >= 2 and cells[1].get("id") != "1":
|
|
_error(
|
|
f"{prefix} Second <mxCell> must have id='1', "
|
|
f"got id='{cells[1].get('id')}'",
|
|
errors,
|
|
)
|
|
# L3: id="1" must have parent="0"
|
|
for cell in cells:
|
|
if cell.get("id") == "1" and cell.get("parent") != "0":
|
|
_error(
|
|
f"{prefix} Cell id='1' must have parent='0', "
|
|
f"got parent='{cell.get('parent')}'",
|
|
errors,
|
|
)
|
|
# H2: Every diagram page must contain a title cell
|
|
# (a vertex with style containing 'text;' and 'fontSize=18')
|
|
def _is_title_style(style: str) -> bool:
|
|
"""Return True if the style string identifies a draw.io title text cell."""
|
|
return (
|
|
(style.startswith("text;") or ";text;" in style)
|
|
and "fontSize=18" in style
|
|
)
|
|
|
|
has_title_cell = any(
|
|
c.get("vertex") == "1" and _is_title_style(c.get("style") or "")
|
|
for c in cells
|
|
)
|
|
if not has_title_cell:
|
|
_error(
|
|
f"{prefix} No title cell found — add a vertex with style "
|
|
"containing 'text;' and 'fontSize=18' at the top of the page",
|
|
errors,
|
|
)
|
|
|
|
# --- Check each cell for structural validity ---
|
|
for cell in cells:
|
|
cid = cell.get("id", "<unknown>")
|
|
is_vertex = cell.get("vertex") == "1"
|
|
is_edge = cell.get("edge") == "1"
|
|
|
|
# Parent must exist (skip the root cell id=0 which has no parent)
|
|
parent = cell.get("parent")
|
|
if cid != "0":
|
|
if parent is None:
|
|
_error(f"{prefix} Cell id='{cid}' is missing a 'parent' attribute", errors)
|
|
elif parent not in cell_ids:
|
|
_error(
|
|
f"{prefix} Cell id='{cid}' references unknown parent='{parent}'",
|
|
errors,
|
|
)
|
|
|
|
# Vertex cells must have mxGeometry
|
|
if is_vertex:
|
|
geom = cell.find("mxGeometry")
|
|
if geom is None:
|
|
_error(
|
|
f"{prefix} Vertex cell id='{cid}' is missing <mxGeometry>",
|
|
errors,
|
|
)
|
|
|
|
# Edge cells must have source and target, both must exist.
|
|
# Exception: floating edges (e.g. sequence diagram lifelines) use
|
|
# sourcePoint/targetPoint in mxGeometry instead of source/target attributes.
|
|
if is_edge:
|
|
source = cell.get("source")
|
|
target = cell.get("target")
|
|
geom = cell.find("mxGeometry")
|
|
has_source_point = geom is not None and any(
|
|
p.get("as") == "sourcePoint" for p in geom.findall("mxPoint")
|
|
)
|
|
has_target_point = geom is not None and any(
|
|
p.get("as") == "targetPoint" for p in geom.findall("mxPoint")
|
|
)
|
|
if source is None and not has_source_point:
|
|
_error(
|
|
f"{prefix} Edge cell id='{cid}' is missing 'source' attribute "
|
|
f"(and no sourcePoint in mxGeometry)",
|
|
errors,
|
|
)
|
|
elif source is not None and source not in cell_ids:
|
|
_error(
|
|
f"{prefix} Edge id='{cid}' references unknown source='{source}'",
|
|
errors,
|
|
)
|
|
if target is None and not has_target_point:
|
|
_error(
|
|
f"{prefix} Edge cell id='{cid}' is missing 'target' attribute "
|
|
f"(and no targetPoint in mxGeometry)",
|
|
errors,
|
|
)
|
|
elif target is not None and target not in cell_ids:
|
|
_error(
|
|
f"{prefix} Edge id='{cid}' references unknown target='{target}'",
|
|
errors,
|
|
)
|
|
|
|
return errors
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) < 2:
|
|
print("Usage: python validate-drawio.py <diagram.drawio>")
|
|
return 1
|
|
|
|
path = Path(sys.argv[1])
|
|
if not path.exists():
|
|
print(f"File not found: {path}")
|
|
return 1
|
|
if not path.is_file():
|
|
print(f"Not a file: {path}")
|
|
return 1
|
|
|
|
print(f"Validating: {path}")
|
|
errors = validate_file(path)
|
|
|
|
if errors:
|
|
print(f"\nFAIL — {len(errors)} error(s) found.")
|
|
return 1
|
|
|
|
print("PASS — No errors found.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|