use std::fmt::{self, Write};
#[derive(Debug, Clone, Copy, Default)]
pub struct Xml;
static REPLACEMENTS: [&str; 256] = {
let mut table = [""; 256];
table[b'&' as usize] = "&";
table[b'<' as usize] = "<";
table[b'>' as usize] = ">";
table[b'"' as usize] = """;
table[b'\'' as usize] = "'";
table
};
impl askama::filters::Escaper for Xml {
fn write_escaped_str<W: Write>(&self, mut dest: W, string: &str) -> fmt::Result {
let mut last = 0;
for (i, &byte) in string.as_bytes().iter().enumerate() {
let replacement = REPLACEMENTS[byte as usize];
if replacement.is_empty() {
continue;
}
dest.write_str(&string[last..i])?;
dest.write_str(replacement)?;
last = i + 1;
}
dest.write_str(&string[last..])
}
}
#[cfg(test)]
mod tests {
use super::*;
use askama::filters::Escaper;
fn esc(s: &str) -> String {
let mut out = String::new();
Xml.write_escaped_str(&mut out, s).unwrap();
out
}
#[test]
fn escapes_the_five_xml_characters() {
assert_eq!(esc("&<>\"'"), "&<>"'");
}
#[test]
fn leaves_ordinary_text_untouched() {
for s in ["", "passing", "1.2.3", "a b/c-d_e", "构建通过", "✓ ok"] {
assert_eq!(esc(s), s);
}
}
#[test]
fn preserves_text_around_escapes() {
assert_eq!(esc("AT&T"), "AT&T");
assert_eq!(esc("&lead"), "&lead");
assert_eq!(esc("trail&"), "trail&");
assert_eq!(esc("a&&b"), "a&&b");
}
#[test]
fn splits_on_char_boundaries_around_multibyte_text() {
assert_eq!(esc("构建&通过"), "构建&通过");
}
}