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

> Explore how graphviz2drawio parses GraphViz SVG files. Learn about its custom XML DOM parsing, gradient extraction, and factory delegation for nodes, edges, and clusters.

- Repository: [Harold Martin/graphviz2drawio](https://github.com/hbmartin/graphviz2drawio)
- Tags: deep-dive
- Published: 2026-03-03

---

**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`](https://github.com/hbmartin/graphviz2drawio/blob/main/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.

```python
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.

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

```

*Source:* [`graphviz2drawio/models/SvgParser.py#L23-L31`](https://github.com/hbmartin/graphviz2drawio/blob/master/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`](https://github.com/hbmartin/graphviz2drawio/blob/main/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`](https://github.com/hbmartin/graphviz2drawio/blob/master/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/main/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/main/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:

```python
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`](https://github.com/hbmartin/graphviz2drawio/blob/master/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`**:

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

```

Inside `NodeFactory` ([`graphviz2drawio/mx/NodeFactory.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/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`](https://github.com/hbmartin/graphviz2drawio/blob/master/graphviz2drawio/mx/NodeFactory.py#L25-L95)

## Constructing Edges and Handling Multi-Labels

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

```python
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`](https://github.com/hbmartin/graphviz2drawio/blob/master/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`](https://github.com/hbmartin/graphviz2drawio/blob/master/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:

```python
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:

```python

# 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`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/models/SvgParser.py) | Core orchestration, gradient extraction, and DOM traversal | [Source](https://github.com/hbmartin/graphviz2drawio/blob/master/graphviz2drawio/models/SvgParser.py) |
| [`graphviz2drawio/models/CoordsTranslate.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/models/CoordsTranslate.py) | Global SVG transform parsing and coordinate offset application | [Source](https://github.com/hbmartin/graphviz2drawio/blob/master/graphviz2drawio/models/CoordsTranslate.py) |
| [`graphviz2drawio/models/SVG.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/models/SVG.py) | Namespace utilities and helper methods for SVG element inspection | [Source](https://github.com/hbmartin/graphviz2drawio/blob/master/graphviz2drawio/models/SVG.py) |
| [`graphviz2drawio/mx/NodeFactory.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/mx/NodeFactory.py) | Node and cluster object construction from SVG groups | [Source](https://github.com/hbmartin/graphviz2drawio/blob/master/graphviz2drawio/mx/NodeFactory.py) |
| [`graphviz2drawio/mx/EdgeFactory.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/mx/EdgeFactory.py) | Edge object construction with curve parsing and label merging | [Source](https://github.com/hbmartin/graphviz2drawio/blob/master/graphviz2drawio/mx/EdgeFactory.py) |
| [`graphviz2drawio/mx/CurveFactory.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/mx/CurveFactory.py) | SVG path data (`d` attribute) parsing into curve objects | [Source](https://github.com/hbmartin/graphviz2drawio/blob/master/graphviz2drawio/mx/CurveFactory.py) |
| [`graphviz2drawio/mx/RectFactory.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/mx/RectFactory.py) | Geometry generation from SVG primitives (polygon, ellipse, path) | [Source](https://github.com/hbmartin/graphviz2drawio/blob/master/graphviz2drawio/mx/RectFactory.py) |

## Summary

- **graphviz2drawio** converts GraphViz SVG to Draw.io via the `parse_nodes_edges_clusters` function in [`SvgParser.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/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.