Skip to content

fix(gp2rs): write arrangement XML as UTF-8 so GP import survives non-ASCII metadata#984

Open
ChrisBeWithYou wants to merge 2 commits into
mainfrom
fix/gp2rs-utf8-xml
Open

fix(gp2rs): write arrangement XML as UTF-8 so GP import survives non-ASCII metadata#984
ChrisBeWithYou wants to merge 2 commits into
mainfrom
fix/gp2rs-utf8-xml

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

The bug

Importing a Guitar Pro file whose metadata contains a non-ASCII character fails on Windows with:

xml.etree.ElementTree.ParseError: not well-formed (invalid token): line N, column 22

and the whole import returns HTTP 500. Repro'd with a real file whose album name is Chrysalis©1982 — the generated <albumName> line is exactly where it dies.

Root cause

The GP→arrangement-XML writers persist their output with Path.write_text(xml_str) and no explicit encoding. Path.write_text uses the platform default text encoding — cp1252 on Windows — so © (U+00A9) is written as the lone byte 0xA9. parse_arrangement then reads the file back as UTF-8 (expat's default), where 0xA9 is an invalid start byte → parse error.

CI runs on Linux, where the default is UTF-8, so the bug is invisible there — which is why it shipped.

Fix

Pin encoding="utf-8" on all three arrangement-XML writes: lib/gp2rs.py (1) and lib/gp2rs_gpx.py (2).

Test

tests/test_gp2rs_xml_encoding.py — because a plain functional test passes on Linux CI even with the old code (UTF-8 default), the regression is pinned at the source-contract level (no bare write_text(xml_str); the UTF-8 form is present), plus a round-trip that a © album name written as UTF-8 parses back intact. Converter suites: 215 passed.

Context

Found while dogfooding the Arrangement Editor's GP import on Windows. Independent of the editor plugin — this is core lib/.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q

Summary by CodeRabbit

  • Bug Fixes
    • Fixed Guitar Pro imports on Windows failing when song metadata includes non-ASCII characters.
    • Arrangement XML generation now explicitly writes output in UTF-8 to avoid malformed XML during import.
  • Tests
    • Added regression tests to enforce UTF-8 encoding for generated arrangement XML and verify correct round-trip handling of non-ASCII album metadata.

…ASCII metadata

The GP→arrangement-XML writers persisted their output with
`Path.write_text(xml_str)` and no explicit encoding. On Windows that uses
the cp1252 default, so a non-ASCII metadata character — e.g. the © in an
album name like "Chrysalis©1982" — was written as the lone byte 0xA9.
The XML is read back as UTF-8 (expat's default), where 0xA9 is an invalid
start byte, so `parse_arrangement` died with:

    xml.etree.ElementTree.ParseError: not well-formed (invalid token): line 10, column 22

and the whole Guitar Pro import failed (HTTP 500). All three arrangement
XML writes (gp2rs.py, gp2rs_gpx.py ×2) now pin encoding="utf-8".

CI runs on Linux (UTF-8 default) so the bug was invisible there; the new
test pins the locale-independent contract at the source level plus a
round-trip of a © album name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
Signed-off-by: ChrisBeWithYou <christian.a.cowan@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f3bff7e5-ff5d-42bc-b2db-e51bd2ea99c3

📥 Commits

Reviewing files that changed from the base of the PR and between 80273ff and 1e8302d.

📒 Files selected for processing (1)
  • tests/test_gp2rs_xml_encoding.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_gp2rs_xml_encoding.py

📝 Walkthrough

Walkthrough

Guitar Pro arrangement XML writers now explicitly emit UTF-8 in all output paths. Regression tests verify the encoding contract and preserve non-ASCII album metadata during XML parsing. The changelog documents the Windows import fix.

Changes

UTF-8 arrangement XML output

Layer / File(s) Summary
Explicit UTF-8 XML writes
lib/gp2rs.py, lib/gp2rs_gpx.py, CHANGELOG.md
All arrangement XML writes specify UTF-8 encoding, and the Windows non-ASCII import fix is documented.
Encoding regression coverage
tests/test_gp2rs_xml_encoding.py
Tests detect bare platform-default writes and verify non-ASCII album metadata round-trips through UTF-8 XML.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the change: forcing UTF-8 for GP2RS arrangement XML to fix non-ASCII import failures.
Description check ✅ Passed The description explains the bug, root cause, fix, and tests, but it does not follow the repository template sections or checklist formatting.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/gp2rs-utf8-xml

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_gp2rs_xml_encoding.py (1)

24-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert every XML write uses UTF-8.

This check only rejects exact bare write_text(xml_str) calls and verifies that one UTF-8 call exists. If another writer changes to an explicit non-UTF-8 encoding, the test still passes. Enumerate every arrangement XML write and assert each call supplies encoding="utf-8".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_gp2rs_xml_encoding.py` around lines 24 - 37, Strengthen
test_arrangement_xml_writes_specify_utf8 so it enumerates every arrangement XML
write in gp2rs and gp2rs_gpx and validates each call explicitly supplies
encoding="utf-8". Replace the current bare-call and single-occurrence checks
with per-write assertions that also reject any explicit non-UTF-8 encoding.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_gp2rs_xml_encoding.py`:
- Around line 40-53: Update test_utf8_write_round_trips_non_ascii_album to use
minimal GP and GPX fixture files and generate arrangement XML through
gp2rs.convert_file and gp2rs_gpx.convert_file instead of writing XML directly.
Parse each converter’s output and assert the albumName remains “Chrysalis©1982”,
covering both production write paths and album propagation.

---

Nitpick comments:
In `@tests/test_gp2rs_xml_encoding.py`:
- Around line 24-37: Strengthen test_arrangement_xml_writes_specify_utf8 so it
enumerates every arrangement XML write in gp2rs and gp2rs_gpx and validates each
call explicitly supplies encoding="utf-8". Replace the current bare-call and
single-occurrence checks with per-write assertions that also reject any explicit
non-UTF-8 encoding.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e88b1bd-e05c-4d3d-9ffe-572e5091a1f4

📥 Commits

Reviewing files that changed from the base of the PR and between 0b4b174 and 80273ff.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • lib/gp2rs.py
  • lib/gp2rs_gpx.py
  • tests/test_gp2rs_xml_encoding.py

Comment thread tests/test_gp2rs_xml_encoding.py Outdated
Comment on lines +40 to +53
def test_utf8_write_round_trips_non_ascii_album():
# The behavioural end of the contract: a © album name written as UTF-8
# parses cleanly and reads back intact (the cp1252 write does not).
from pathlib import Path
import tempfile

xml_str = (
'<?xml version="1.0"?>\n<song>\n'
" <albumName>Chrysalis©1982</albumName>\n</song>\n"
)
path = Path(tempfile.mkdtemp()) / "arr.xml"
path.write_text(xml_str, encoding="utf-8")
root = ET.parse(path).getroot()
assert root.findtext("albumName") == "Chrysalis©1982"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Exercise the production converters in the round-trip test.

This test writes its own XML string and never calls gp2rs.convert_file or gp2rs_gpx.convert_file, so it cannot detect regressions in album propagation or the actual production write paths. Use a minimal GP/GPX fixture, generate the arrangement XML through the converter, then parse that output and assert the © album name survives.

🧰 Tools
🪛 Ruff (0.15.21)

[error] 52-52: Using xml to parse untrusted data is known to be vulnerable to XML attacks; use defusedxml equivalents

(S314)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_gp2rs_xml_encoding.py` around lines 40 - 53, Update
test_utf8_write_round_trips_non_ascii_album to use minimal GP and GPX fixture
files and generate arrangement XML through gp2rs.convert_file and
gp2rs_gpx.convert_file instead of writing XML directly. Parse each converter’s
output and assert the albumName remains “Chrysalis©1982”, covering both
production write paths and album propagation.

@ChrisBeWithYou

Copy link
Copy Markdown
Contributor Author

Review pass complete. Found and fixed one regression-test gap in 1e8302d: the original regex could miss incorrectly encoded or differently formatted write_text(xml_str, ...) calls, and the round-trip test leaked its temporary directory. The test now inspects every arrangement XML write via the Python AST, pins all three writer paths to UTF-8, and uses pytest's managed temp directory.

Verification: 234 converter/notation tests pass on Windows, git diff --check is clean, and all PR checks (including CodeRabbit) are green. A second review pass found no further issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant