Tagged Text: decode only the escapes we write; other backslashes stay literal

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas
2026-10-23 17:16:26 -04:00
co-authored by Claude Opus 5.5
parent e1e9923471
commit 96a3290021
+20 -5
View File
@@ -37,14 +37,20 @@ fn escape_value(v: &str) -> String {
out
}
/// A tag value with backslash escapes removed.
/// A tag value with the escapes `escape_value` writes (`\\`, `\<`, `\>`) decoded. Any other
/// backslash, including a trailing one, stays literal, as older files and InDesign's `\:` expect.
fn unescape_value(v: &str) -> String {
let mut out = String::with_capacity(v.len());
let mut it = v.chars();
let mut it = v.chars().peekable();
while let Some(c) = it.next() {
match c {
'\\' => out.extend(it.next()),
c => out.push(c),
if c == '\\'
&& let Some(&n) = it.peek()
&& matches!(n, '\\' | '<' | '>')
{
out.push(n);
it.next();
} else {
out.push(c);
}
}
out
@@ -312,6 +318,15 @@ mod tests {
}
}
/// A backslash that isn't one of our escapes (older files, InDesign's `\:`) stays literal.
#[test]
fn unknown_backslashes_in_style_names_stay_literal() {
assert_eq!(unescape_value(r"x\y"), r"x\y");
assert_eq!(unescape_value(r"a\:b"), r"a\:b");
assert_eq!(unescape_value("end\\"), "end\\");
assert_eq!(unescape_value(r"\<a\>\\"), r"<a>\");
}
#[test]
fn tagged_text_round_trips() {
let src = "<ASCII-WIN>\r\n<Version:7>\r\n<ParaStyle:Head>Caf<0x00E9> \\<1\\>\r\n<ParaStyle:Body>A <CharStyle:Em>word<CharStyle:> and <cSize:18.000000>big<cSize:> text";