[Bug]: `graphify export svg` writes a graph.svg that is not well-formed XML when a label contains a control character
Maintainers usually reply within 1 day
Nobody has claimed this yet.
- #4244 by @Yyunozor — closed without merging
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 72/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- python
- Domain
- data-visualization
Research direction
In graphify/export.py, inspect to_svg and the existing _strip_xml_illegal usage in to_graphml. Add or extend a regression test alongside the tests for #2897, covering control characters in node labels and the legend. Run the focused export tests; done means SVG output is well-formed XML and the control characters are dropped from the drawn text.
Written by the indexing model from the issue text.
Description
Pre-flight checks
- I have checked the Troubleshooting section in the README
What happened?
A node or community label that contains a C0 control character (an ANSI escape from a pasted terminal capture, a form feed used as a section separator, a NUL) ends up in graph.svg. XML 1.0 forbids those characters, so the file is not well-formed. graphify export svg still prints graph.svg written and exits 0.
The same graph exports a well-formed graph.graphml. #2897 made to_graphml strip these characters and left to_svg out, for a reason given in #2899: to_svg does not crash, matplotlib only warns about a missing glyph and draws a box. That holds for a label without a pair of $ (between two of them, a control character can make matplotlib read invalid mathtext and to_svg raise ValueError), but the file it writes is not valid XML, and the command reports success.
Expected: a well-formed graph.svg, with the control character dropped from the drawn text, as to_graphml does.
Cause, in graphify/export.py: to_svg hands the raw node labels to nx.draw_networkx_labels and the raw community label to mpatches.Patch. With the default svg.fonttype, matplotlib's SVG backend writes each drawn string into a comment (<!-- text -->). Its comment escaping handles &, <, > and --, not control characters. _strip_xml_illegal is only called from to_graphml.
Steps to reproduce
mkdir svg-repro && cd svg-repro
printf '# Notes\n\n## Build \033[31mlog capture\033[0m\n\nSome text.\n\n## Deploy\n\nSee [[Notes]].\n' > notes.md
printf 'def helper():\n return 1\n\ndef main():\n return helper()\n' > app.py
graphify update .
graphify export svg
xmllint --noout graphify-out/graph.svg
graphify export graphml
xmllint --noout graphify-out/graph.graphml
The legend takes the same path. On a project with no control character in any heading, graphify export svg --labels labels.json with {"0": "Core \u001b[0mmodule", "1": "Other\u000cgroup"} gives the same xmllint error (<!-- Core [0mmodule (4) -->).
Error output or graph output
.../graphify/export.py:1409: UserWarning: Glyph 27 () missing from font(s) DejaVu Sans.
plt.savefig(output_path, format="svg", bbox_inches="tight",
graph.svg written - embeds in Obsidian, Notion, GitHub READMEs
graphify-out/graph.svg:513: parser error : xmlParseComment: invalid xmlChar value 27
<!-- Build [31mlog capture[0m -->
The parentheses of the warning hold the ESC character. xmllint exits 1 on graph.svg and 0 on graph.graphml. The heading becomes the node Build \x1b[31mlog capture\x1b[0m in graph.json.
Graphify version
0.9.80 (v8 @ 6478eb7)
Operating System
macOS
Python Version
3.12
Installation Method
built from source (git clone)
Additional Environment Details
uv sync --all-extras --frozen, matplotlib 3.10.9 (the uv.lock version); the same file is malformed with matplotlib 3.11.2. AST-only path (graphify update), no API key involved. Reproduced on a clean checkout of v8 at 6478eb7 (no local change).
Additional context
I have a small fix and can open a PR: pass the node labels and the legend text through _strip_xml_illegal in to_svg, with a regression test next to the #2897 tests.
- Dominant language
- Python
- Stars
- 124k
- Forks
- 11.9k
- PR merge metrics
- No merged PRs in 30d
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from Graphify-Labs/graphify
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
Graphify-Labs/graphify#3763 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Graphify-Labs/graphify#3611 · 1 comment ·
Maintainers usually reply within 1 day
-
Nix supportOpen
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
Graphify-Labs/graphify#3193 · 2 reactions ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 92/100
Graphify-Labs/graphify#2871 · 2 comments ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Graphify-Labs/graphify#2865 ·
Maintainers usually reply within 1 day
All issues in Graphify-Labs/graphify
Similar issues
-
area: agents area: docs good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
openapi/gknowledge#23 ·
-
get() for StrEnumPossibly taken @FeJo5952 claimed this today. Opengood first issue
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
collective/icalendar#1921 · 1 comment ·
Maintainers usually reply within 1 day
-
Improvement Python Spans
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
getsentry/sentry-python#7994 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
alphagov/notifications-api#5004 ·
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
equinor/radix-flux#3922 ·
Maintainers usually reply within 1 day