How graphviz2drawio Parses GraphViz SVG Files: A Deep Dive into the SVG Parsing Process

graphviz2drawio converts GraphViz SVG exports into diagram objects by parsing the XML DOM with a custom CommentedTreeBuilder, extracting gradients and coordinates, and delegating node, edge, and cluster construction to specialized factories.

The graphviz2drawio library (available at hbmartin/graphviz2drawio) transforms GraphViz DOT output into editable Draw.io diagrams by reverse-engineering the SVG structure. Its SVG parsing process walks the DOM, handles GraphViz-specific metadata stored in XML comments, and reconstructs the visual graph as a structured object model.

Entry Point: The parse_nodes_edges_clusters Orchestrator

The entire conversion pipeline begins in graphviz2drawio/models/SvgParser.py with the parse_nodes_edges_clusters function. This method accepts raw SVG bytes and a directedness flag, returning three ordered collections: nodes, edges, and clusters.

def parse_nodes_edges_clusters(
    svg_data: bytes,
    *,
    is_directed: bool,
) -> tuple[OrderedDict[str, Node], list[Edge], OrderedDict[str, Node]]:

The function serves as the central dispatcher, coordinating XML parsing, coordinate transformation, and factory-based object construction.

Reading the SVG and Extracting Global Coordinates

Before processing diagram elements, the parser must handle SVG-level metadata. The raw bytes are parsed using xml.etree.ElementTree.fromstring with a custom CommentedTreeBuilder to preserve XML comments—critical because GraphViz stores element identifiers in comments rather than attributes.

root = ElementTree.fromstring(
    svg_data,
    parser=ElementTree.XMLParser(target=CommentedTreeBuilder()),
)[0]

Source: graphviz2drawio/models/SvgParser.py#L23-L31

The outermost <svg> element typically carries a transform="translate(x y)" attribute. The parser delegates to CoordsTranslate.from_svg_transform in graphviz2drawio/models/CoordsTranslate.py to extract these offsets and create a translation helper that adjusts all subsequent coordinates.

Source: graphviz2drawio/models/CoordsTranslate.py#L12-L15

Factory Architecture for Diagram Elements

Rather than building objects directly, the parser uses two specialized factories initialized with the coordinate translator:

  • NodeFactory – Constructs Node objects from <g class="node"> elements, handling shapes, fills, and text labels.
  • EdgeFactory – Constructs Edge objects from <g class="edge"> elements, processing curves, strokes, and directedness.

Source: [graphviz2drawio/mx/NodeFactory.py](https://github.com/hbmartin/graphviz2drawio/blob/master/graphviz2drawio/mx/NodeFactory.py) & [graphviz2drawio/mx/EdgeFactory.py](https://github.com/hbmartin/graphviz2drawio/blob/master/graphviz2drawio/mx/EdgeFactory.py)

Traversing the SVG Tree: Comments, Definitions, and Groups

The parser iterates over direct children of the root SVG group, handling three distinct element types:

for g in root:
    if g.tag == COMMENT:                     # XML comment containing the title

        prev_comment = g.text
    elif SVG.is_tag(g, "defs"):              # Gradient definitions block

        for gradient in _extract_gradients(g):
            gradients[gradient[0]] = gradient[1:]
    elif SVG.is_tag(g, "g"):                 # Actual diagram element (node/edge/cluster)

        title = prev_comment or SVG.get_title(g)
        # ...

XML Comments as Identifiers: GraphViz embeds node and edge IDs in XML comments (e.g., <!-- a -->). The parser caches the most recent comment; if an element lacks a comment, it falls back to the <title> sub-element. If neither exists, the parser raises MissingTitleError.

Gradient Extraction: When encountering <defs>, the parser calls _extract_gradients to walk <radialGradient> and <linearGradient> elements. It reads the first and last <stop> colors (including opacity) and stores tuples of (id, start_color, end_color, direction) in a dictionary for later lookup.

Source: graphviz2drawio/models/SvgParser.py#L84-L135

Building Nodes from SVG Primitives

When the parser encounters a group with class="node", it delegates to NodeFactory.from_svg:

nodes[title] = node_factory.from_svg(
    g,
    labelloc="c",
    gradients=gradients,
)

Inside NodeFactory (graphviz2drawio/mx/NodeFactory.py), the method inspects child elements to determine geometry:

  • Shape detection – Checks for <polygon>, <ellipse>, <path>, or <image> elements.
  • Geometry – Uses RectFactory to generate Rect objects defining the node's bounding box.
  • Styling – Extracts fill (including url(#grad-id) references resolved via the gradients dictionary), stroke, stroke-width, and dash patterns.
  • Text labels – Locates <text> elements, calculating offsets to position labels relative to the node center (labelloc="c").

Source: graphviz2drawio/mx/NodeFactory.py#L25-L95

Constructing Edges and Handling Multi-Labels

For groups with class="edge", the parser invokes EdgeFactory.from_svg:

edge = edge_factory.from_svg(g, title)

The factory performs several operations:

  1. Title parsing – Splits the title string (e.g., "a--b" for undirected or "a->b" for directed) into source (fr) and target (to) node identifiers.
  2. Curve extraction – Parses the <path> element's d attribute using CurveFactory.from_svg to create Bézier curve representations.
  3. Styling – Captures stroke color, width, dash style, and any arrowhead markers.
  4. Labels – Extracts <text> elements associated with the edge.
  5. Duplicate merging – If an edge between the same vertices already exists (common in multi-graphs), the parser merges additional labels into the existing Edge object rather than creating duplicates.

Source: graphviz2drawio/mx/EdgeFactory.py#L18-L55

Processing Clusters as Container Nodes

GraphViz clusters (subgraphs) appear as <g class="cluster"> elements. The parser treats these similarly to nodes but with labelloc="t" (title at top) to position the cluster label above the bounding rectangle. The same NodeFactory builds the visual container that groups child nodes in the final Draw.io output.

Source: graphviz2drawio/models/SvgParser.py#L74-L80

Return Value and Model Completion

Upon completing the DOM traversal, parse_nodes_edges_clusters returns three collections:

  • nodes – An OrderedDict mapping titles to Node objects.
  • edges – A list of Edge objects with merged labels and parsed curves.
  • clusters – An OrderedDict mapping cluster titles to container Node objects.

These structured objects are subsequently passed to the Draw.io XML builder, completing the SVG parsing phase of the conversion pipeline.

Practical Code Examples

Parsing a GraphViz SVG File Directly

You can invoke the parser programmatically to inspect the extracted model before Draw.io conversion:

from pathlib import Path
from graphviz2drawio.models.SvgParser import parse_nodes_edges_clusters

# Load SVG output from `dot -Tsvg example.dot`

svg_bytes = Path("diagram.svg").read_bytes()

# Parse with directed=True for digraphs, False for graphs

nodes, edges, clusters = parse_nodes_edges_clusters(
    svg_bytes,
    is_directed=True,
)

print(f"Found {len(nodes)} nodes, {len(edges)} edges, {len(clusters)} clusters")
for nid, node in nodes.items():
    print(f"Node {nid!r}: shape={node.shape}, fill={node.fill}")

Integration with the Command-Line Interface

The library's CLI entry point (python -m graphviz2drawio) uses the same parsing function internally:


# Simplified pipeline from __main__.py

svg = dot_to_svg(input_path)               # Executes `dot -Tsvg`

nodes, edges, clusters = parse_nodes_edges_clusters(
    svg, 
    is_directed=directed
)
drawio_xml = mxgraph_builder.build(nodes, edges, clusters)

This demonstrates how parse_nodes_edges_clusters serves as the critical bridge between GraphViz SVG output and the Draw.io format.

Key Source Files

The SVG parsing implementation spans seven core files:

File Responsibility Link
graphviz2drawio/models/SvgParser.py Core orchestration, gradient extraction, and DOM traversal Source
graphviz2drawio/models/CoordsTranslate.py Global SVG transform parsing and coordinate offset application Source
graphviz2drawio/models/SVG.py Namespace utilities and helper methods for SVG element inspection Source
graphviz2drawio/mx/NodeFactory.py Node and cluster object construction from SVG groups Source
graphviz2drawio/mx/EdgeFactory.py Edge object construction with curve parsing and label merging Source
graphviz2drawio/mx/CurveFactory.py SVG path data (d attribute) parsing into curve objects Source
graphviz2drawio/mx/RectFactory.py Geometry generation from SVG primitives (polygon, ellipse, path) Source

Summary

  • graphviz2drawio converts GraphViz SVG to Draw.io via the parse_nodes_edges_clusters function in SvgParser.py.
  • The parser uses a custom CommentedTreeBuilder to preserve GraphViz identifiers stored in XML comments.
  • Global coordinate transforms are extracted via CoordsTranslate and applied to all geometric calculations.
  • Factory pattern: NodeFactory handles nodes/clusters while EdgeFactory handles edges with curve parsing.
  • Gradient support: SVG gradients are extracted from <defs> and resolved when processing node fills.
  • Edge merging: Duplicate edges between the same nodes are automatically merged to preserve multi-label semantics.
  • The final output consists of three ordered collections (nodes, edges, clusters) ready for Draw.io XML serialization.

Frequently Asked Questions

How does graphviz2drawio handle GraphViz node identifiers?

The parser looks for XML comments (e.g., <!-- node_id -->) preceding each SVG group element, as GraphViz stores identifiers in comments rather than attributes. If no comment exists, it falls back to the <title> element inside the group. If neither is found, it raises a MissingTitleError.

Why does the parser need a custom CommentedTreeBuilder?

Standard Python XML parsers strip comments during tree construction. Since GraphViz embeds critical node and edge identifiers in XML comments, the CommentedTreeBuilder subclass preserves these comments in the DOM, allowing the parser to extract titles and match them to the correct SVG elements.

Can the SVG parsing handle gradient fills and complex styles?

Yes. The _extract_gradients function parses <radialGradient> and <linearGradient> definitions from the SVG <defs> section. When NodeFactory encounters a fill="url(#grad-id)" attribute, it resolves the reference to the extracted gradient object, preserving the original visual styling in the converted diagram.

What happens when multiple edges exist between the same two nodes?

The EdgeFactory detects duplicate edges by comparing source and target identifiers. Rather than creating separate edge objects, it merges additional labels into the existing Edge instance. This preserves GraphViz's multi-label edge semantics while maintaining a clean diagram structure in Draw.io.

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 →