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– ConstructsNodeobjects from<g class="node">elements, handling shapes, fills, and text labels.EdgeFactory– ConstructsEdgeobjects 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
RectFactoryto generateRectobjects defining the node's bounding box. - Styling – Extracts
fill(includingurl(#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:
- 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. - Curve extraction – Parses the
<path>element'sdattribute usingCurveFactory.from_svgto create Bézier curve representations. - Styling – Captures stroke color, width, dash style, and any arrowhead markers.
- Labels – Extracts
<text>elements associated with the edge. - Duplicate merging – If an edge between the same vertices already exists (common in multi-graphs), the parser merges additional labels into the existing
Edgeobject 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– AnOrderedDictmapping titles toNodeobjects.edges– A list ofEdgeobjects with merged labels and parsed curves.clusters– AnOrderedDictmapping cluster titles to containerNodeobjects.
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_clustersfunction inSvgParser.py. - The parser uses a custom
CommentedTreeBuilderto preserve GraphViz identifiers stored in XML comments. - Global coordinate transforms are extracted via
CoordsTranslateand applied to all geometric calculations. - Factory pattern:
NodeFactoryhandles nodes/clusters whileEdgeFactoryhandles 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →