How drawio_extract.py Works: Converting draw.io Files to a Normalized Intermediate Representation
drawio_extract.py is a deterministic parser that decodes draw.io files (XML, PNG, SVG, or compressed payloads), flattens the graph model into absolute-positioned nodes and edges, and emits either a Markdown digest or a JSON intermediate representation for downstream processing.
The drawio_extract.py script in the cathrynlavery/diagram-design repository serves as the deterministic import engine for draw.io diagrams. Unlike heuristic-driven converters, it strictly extracts and normalizes content without making design decisions, producing a canonical data structure that the rest of the Diagram-Design skill consumes.
Overview of the Extraction Pipeline
The script operates as a pure transformation layer. It never applies design logic; instead, it decodes whatever the user-produced file contains and normalizes it into an intermediate representation (IR). According to the source code in skills/diagram-design/scripts/drawio_extract.py, the pipeline consists of nine distinct phases, from file detection to final rendering.
Detecting and Loading File Formats
The entry point load_mxfile() (lines 62‑87) determines the container format and extracts the raw <mxfile> or <mxGraphModel> XML. It handles four distinct input types:
- PNG with embedded XML: Extracts the
mxfilepayload using_png_embedded_xml - SVG with embedded XML: Parses the XML chunk via
_svg_embedded_xml - Plain XML: Directly loads uncompressed diagram files
- Deflated/Base64 payload: Decompresses compressed streams using
_inflate
This flexibility allows the script to process diagrams exported directly from draw.io as well as images containing embedded diagram data.
Security Validation
Before parsing, _reject_unsafe_xml() (lines 62‑67) inspects the raw content for DTD or ENTITY declarations to prevent XML external entity (XXE) attacks. If the scanner detects unsafe constructs, the script aborts with exit code 2 via the _fail() handler, ensuring the extraction pipeline never processes malicious XML.
Parsing the XML Graph Model
The parse_file() function (lines 23‑38) orchestrates the initial XML processing. It calls load_mxfile() to retrieve the raw content, then uses ElementTree to parse the structure. The function iterates over each <diagram> element (or a single wrapper for simpler files) and delegates page-level processing to parse_page().
Building Node and Edge Objects
Inside parse_page() (lines 58‑121), the script constructs the IR through three sequential passes:
- Pass 1: Collects raw cell data, including
<object>andUserObjectwrappers, preserving all metadata attributes - Pass 2: Creates
Nodeobjects for every vertex, resolves absolute geometry coordinates, and establishes parent-child relationships - Pass 3: Creates
Edgeobjects, attaches edge-label text viaclean_label(), and computes node degrees (in-degree and out-degree)
This multi-pass approach ensures that all geometric relationships are normalized to absolute coordinates before structural analysis begins.
Helper Utilities for Data Normalization
Several utility functions standardize the extracted data (lines 98‑132):
parse_style(): Converts semicolon-delimited style strings into Python dictionaries for programmatic accessclean_label(): Strips HTML fragments and formatting artifacts from node and edge labelsclassify_shape(): Maps style keys to canonical shape names (e.g.,ellipse,swimlane,icon:aws)
These helpers ensure the IR contains clean, structured data regardless of the complexity of the original draw.io styling.
Structural Analysis
Once the IR is built, analyze() (lines 87‑136) inspects the graph topology to produce diagnostic signals. It detects cycles, identifies hub nodes with high connectivity, and suggests type candidates (such as “sequence” or “architecture” diagrams). This analysis guides downstream rendering decisions but remains non-deterministic—drawio_extract.py never selects the final diagram type itself.
Rendering Output
The final stage produces human-readable or machine-readable output based on command-line flags:
digest()(lines 84‑115): Generates a Markdown table showing page bounds, node/edge statistics, and the first N rows of entities (controlled by--max-rows)to_json()(lines 118‑134): Emits the complete intermediate representation as JSON when the--jsonflag is provided
The main() function (lines 57‑71, 86‑92) handles argument parsing with argparse, supports the --out parameter for file writing, and coordinates the pipeline execution.
Command-Line and Programmatic Usage
You can run the script directly from the terminal:
# Generate a readable Markdown digest (default 40 rows per table)
python3 drawio_extract.py examples/my-diagram.drawio
# Limit tables to 20 rows and write to a file
python3 drawio_extract.py examples/my-diagram.drawio --max-rows 20 --out report.md
# Emit the full intermediate representation as JSON
python3 drawio_extract.py examples/my-diagram.drawio --json > report.json
For integration into Python applications, import the core functions directly:
from pathlib import Path
from drawio_extract import parse_file, digest, to_json
# Load a file and get a list of Page objects
pages = parse_file(Path("examples/my-diagram.drawio"))
# Produce a Markdown digest for the first page
md = digest(Path("examples/my-diagram.drawio"), pages, pages[:1], max_rows=30)
print(md)
# Or get the complete JSON IR
json_payload = to_json(Path("examples/my-diagram.drawio"), pages, pages[:1])
print(json_payload)
Summary
- drawio_extract.py acts as a deterministic decoder for draw.io files, supporting XML, PNG, SVG, and compressed Base64 formats.
- The script validates XML for security threats via
_reject_unsafe_xml()before parsing. - It constructs an intermediate representation through a three-pass process in
parse_page(), normalizing all coordinates to absolute positions. - Helper functions like
classify_shape()andparse_style()ensure consistent data formatting. - Output options include a concise Markdown digest (
digest()) or a complete JSON intermediate representation (to_json()). - The tool operates without heuristic design decisions, leaving diagram-type selection to downstream consumers in the Diagram-Design skill.
Frequently Asked Questions
What file formats does drawio_extract.py support?
The script supports plain XML files, PNG images with embedded mxfile data, SVG files with embedded XML, and compressed Base64 payloads. The load_mxfile() function automatically detects the container type and extracts the raw diagram XML accordingly.
How does the script handle security risks in XML parsing?
Before parsing, _reject_unsafe_xml() scans the input for DTD or ENTITY declarations to prevent XXE attacks. If unsafe XML constructs are detected, the script immediately aborts with exit code 2, ensuring no malicious content reaches the processing pipeline.
Can I use drawio_extract.py as a library in my Python project?
Yes. You can import parse_file(), digest(), and to_json() directly from the script to process draw.io files programmatically. The functions accept pathlib.Path objects and return structured Python objects or formatted strings suitable for further processing.
What is the difference between the Markdown digest and JSON output?
The Markdown digest provides a human-readable summary with page statistics and sample rows of nodes and edges, useful for quick inspection or documentation. The JSON output contains the complete intermediate representation with all geometric data, styles, and structural analysis signals, intended for machine consumption by downstream rendering engines.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →