From 96a3290021f3c2faa7daf85870da515b6697d360 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 23 Oct 2026 17:16:26 -0400 Subject: [PATCH] Tagged Text: decode only the escapes we write; other backslashes stay literal Co-Authored-By: Claude Opus 5.5 --- crates/textimport/src/tagged.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/textimport/src/tagged.rs b/crates/textimport/src/tagged.rs index ffb106ea..b5f7815b 100644 --- a/crates/textimport/src/tagged.rs +++ b/crates/textimport/src/tagged.rs @@ -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"\\\"), r"\"); + } + #[test] fn tagged_text_round_trips() { let src = "\r\n\r\nCaf<0x00E9> \\<1\\>\r\nA word and big text";