pub fn unescape_string_literal(raw: &str) -> String {
if !raw.contains('\\') {
return raw.to_string();
}
let mut out = String::with_capacity(raw.len());
let mut chars = raw.chars();
while let Some(c) = chars.next() {
if c != '\\' {
out.push(c);
continue;
}
match chars.next() {
None => out.push('\\'), Some('\\') => out.push('\\'),
Some('\'') => out.push('\''),
Some('"') => out.push('"'),
Some('n') => out.push('\n'),
Some('r') => out.push('\r'),
Some('t') => out.push('\t'),
Some(other) => {
out.push('\\');
out.push(other);
}
}
}
out
}
pub fn escape_string_literal(value: &str, quote: char) -> String {
let mut out = String::with_capacity(value.len());
for c in value.chars() {
match c {
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if c == quote => {
out.push('\\');
out.push(c);
}
c => out.push(c),
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defined_escapes() {
assert_eq!(unescape_string_literal(r"\\"), "\\");
assert_eq!(unescape_string_literal(r"\'"), "'");
assert_eq!(unescape_string_literal(r#"\""#), "\"");
assert_eq!(unescape_string_literal(r"\n"), "\n");
assert_eq!(unescape_string_literal(r"\r"), "\r");
assert_eq!(unescape_string_literal(r"\t"), "\t");
}
#[test]
fn unknown_escape_keeps_its_backslash() {
assert_eq!(unescape_string_literal(r"\W"), r"\W");
assert_eq!(unescape_string_literal(r"C:\Windows"), r"C:\Windows");
assert_eq!(unescape_string_literal(r"\d{3}\s\w+"), r"\d{3}\s\w+");
}
#[test]
fn escaped_backslash_before_n_is_not_a_newline() {
assert_eq!(unescape_string_literal(r"\\n"), r"\n");
assert!(!unescape_string_literal(r"\\n").contains('\n'));
}
#[test]
fn escaped_backslash_before_quote_is_not_an_escaped_quote() {
assert_eq!(unescape_string_literal(r"\\'"), r"\'");
}
#[test]
fn trailing_lone_backslash_is_preserved() {
assert_eq!(unescape_string_literal(r"C:\Temp\"), r"C:\Temp\");
}
#[test]
fn windows_path_with_doubled_backslashes() {
assert_eq!(
unescape_string_literal(r"C:\\Windows\\ntdll.dll"),
r"C:\Windows\ntdll.dll"
);
assert!(!unescape_string_literal(r"C:\\Windows\\ntdll.dll").contains('\n'));
}
#[test]
fn no_backslash_is_a_fast_path_and_identity() {
assert_eq!(unescape_string_literal("plain value"), "plain value");
assert_eq!(unescape_string_literal(""), "");
}
#[test]
fn escape_then_unescape_round_trips() {
let nasty = [
r"C:\Windows\ntdll.dll",
r"C:\Temp\",
"\\",
"a\tb",
"line\nbreak",
"don't",
"say \"hi\"",
"both'and\"quotes",
"plain",
r"\W\q",
"Администратор",
];
for value in nasty {
assert_eq!(
unescape_string_literal(&escape_string_literal(value, '\'')),
value,
"single-quoted round trip failed for {value:?}"
);
assert_eq!(
unescape_string_literal(&escape_string_literal(value, '"')),
value,
"double-quoted round trip failed for {value:?}"
);
}
}
#[test]
fn escape_leaves_the_inactive_quote_alone() {
assert_eq!(escape_string_literal("a\"b", '\''), "a\"b");
assert_eq!(escape_string_literal("don't", '"'), "don't");
assert_eq!(escape_string_literal("don't", '\''), r"don\'t");
}
#[test]
fn multibyte_values_are_not_split() {
assert_eq!(unescape_string_literal("Администратор"), "Администратор");
assert_eq!(unescape_string_literal("\u{202e}"), "\u{202e}");
}
}