From 7509b64c5cedb371775ca1cb74cf18c4a9dacd84 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Sun, 25 Oct 2026 06:39:26 +0100 Subject: [PATCH] Strip XML-forbidden characters from MusicXML/SVG score export Found while testing crates/engine/src/score.rs:216-218 (xml_escape), used by musicxml() and svg() Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/engine/src/score.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/score.rs b/crates/engine/src/score.rs index 847be67e..8f40fe5f 100644 --- a/crates/engine/src/score.rs +++ b/crates/engine/src/score.rs @@ -214,7 +214,13 @@ fn spell(pitch: u8) -> (char, i32, i32) { } fn xml_escape(s: &str) -> String { - s.replace('&', "&").replace('<', "<").replace('>', ">").replace('"', """) + s.chars() + .filter(|c| matches!(c, '\t' | '\n' | '\r' | '\u{20}'..='\u{D7FF}' | '\u{E000}'..='\u{FFFD}' | '\u{10000}'..='\u{10FFFF}')) + .collect::() + .replace('&', "&") + .replace('<', "<") + .replace('>', ">") + .replace('"', """) } fn note_type(units: u32) -> (&'static str, bool) { @@ -558,4 +564,19 @@ mod tests { let s = svg(&parts, &setup); assert!(s.starts_with(" = + out.chars().filter(|c| (*c < ' ' && !matches!(c, '\n' | '\t' | '\r')) || matches!(c, '\u{FFFE}' | '\u{FFFF}')).collect(); + assert!(bad.is_empty(), "forbidden characters: {bad:?}"); + } + } }