diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index 1ba1328ea..a42d5cd84 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -399,7 +399,7 @@ fn parse_dupe(val: &str) -> ParseResult { let text = it.next().ok_or(ParseError {})?; Ok(SearchNode::Duplicates { note_type_id: mid, - text: unescape_quotes(text), + text: unescape_quotes_and_backslashes(text), }) } @@ -478,6 +478,15 @@ fn unescape_quotes(s: &str) -> Cow { } } +/// For non-globs like dupe text without any assumption about the content +fn unescape_quotes_and_backslashes(s: &str) -> Cow { + if s.contains('"') || s.contains('\\') { + s.replace(r#"\""#, "\"").replace(r"\\", r"\").into() + } else { + s.into() + } +} + /// Unescape chars with special meaning to the parser. fn unescape(txt: &str) -> ParseResult> { if is_invalid_escape(txt) { diff --git a/rslib/src/search/writer.rs b/rslib/src/search/writer.rs index 8c2199c8a..94215697e 100644 --- a/rslib/src/search/writer.rs +++ b/rslib/src/search/writer.rs @@ -119,7 +119,7 @@ fn write_search_node(node: &SearchNode) -> String { NoteType(s) => quote(&format!("note:{}", s)), Rated { days, ease } => write_rated(days, ease), Tag(s) => quote(&format!("tag:{}", s)), - Duplicates { note_type_id, text } => quote(&format!("dupe:{},{}", note_type_id, text)), + Duplicates { note_type_id, text } => write_dupe(note_type_id, text), State(k) => write_state(k), Flag(u) => format!("\"flag:{}\"", u), NoteIDs(s) => format!("\"nid:{}\"", s), @@ -163,6 +163,12 @@ fn write_rated(days: &u32, ease: &EaseKind) -> String { } } +/// Escape double quotes and backslashes: \" +fn write_dupe(note_type_id: &NoteTypeIDType, text: &str) -> String { + let esc = text.replace(r"\", r"\\").replace('"', r#"\""#); + format!("\"dupe:{},{}\"", note_type_id, esc) +} + fn write_state(kind: &StateKind) -> String { use StateKind::*; format!(