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";