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 mxfile payload 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:

  1. Pass 1: Collects raw cell data, including <object> and UserObject wrappers, preserving all metadata attributes
  2. Pass 2: Creates Node objects for every vertex, resolves absolute geometry coordinates, and establishes parent-child relationships
  3. Pass 3: Creates Edge objects, attaches edge-label text via clean_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 access
  • clean_label(): Strips HTML fragments and formatting artifacts from node and edge labels
  • classify_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 --json flag 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() and parse_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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →