use std::fmt::Write;
pub fn escape_xml(s: &str) -> String {
if !s
.bytes()
.any(|b| matches!(b, b'&' | b'<' | b'>' | b'"' | b'\''))
{
return s.to_string();
}
let mut out = String::with_capacity(s.len() + 8);
for c in s.chars() {
match c {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'"' => out.push_str("""),
'\'' => out.push_str("'"),
_ => out.push(c),
}
}
out
}
pub fn append_escape_xml(buf: &mut String, s: &str) {
if !s
.bytes()
.any(|b| matches!(b, b'&' | b'<' | b'>' | b'"' | b'\''))
{
buf.push_str(s);
return;
}
for c in s.chars() {
match c {
'&' => buf.push_str("&"),
'<' => buf.push_str("<"),
'>' => buf.push_str(">"),
'"' => buf.push_str("""),
'\'' => buf.push_str("'"),
_ => buf.push(c),
}
}
}
pub fn append_usize(buf: &mut String, value: usize) {
let _ = write!(buf, "{value}");
}
pub fn append_i32(buf: &mut String, value: i32) {
let _ = write!(buf, "{value}");
}
pub struct XmlWriter {
buffer: String,
}
impl XmlWriter {
pub fn new() -> Self {
Self {
buffer: String::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
buffer: String::with_capacity(capacity),
}
}
pub fn xml_declaration(&mut self) -> &mut Self {
self.buffer
.push_str(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#);
self.buffer.push('\n');
self
}
pub fn start_element(&mut self, name: &str, attrs: &[(&str, &str)]) -> &mut Self {
self.buffer.push('<');
self.buffer.push_str(name);
for (key, value) in attrs {
self.buffer.push(' ');
self.buffer.push_str(key);
self.buffer.push_str("=\"");
append_escape_xml(&mut self.buffer, value);
self.buffer.push('"');
}
self.buffer.push('>');
self
}
pub fn end_element(&mut self, name: &str) -> &mut Self {
self.buffer.push_str("</");
self.buffer.push_str(name);
self.buffer.push('>');
self
}
pub fn empty_element(&mut self, name: &str, attrs: &[(&str, &str)]) -> &mut Self {
self.buffer.push('<');
self.buffer.push_str(name);
for (key, value) in attrs {
self.buffer.push(' ');
self.buffer.push_str(key);
self.buffer.push_str("=\"");
append_escape_xml(&mut self.buffer, value);
self.buffer.push('"');
}
self.buffer.push_str("/>");
self
}
pub fn text(&mut self, content: &str) -> &mut Self {
append_escape_xml(&mut self.buffer, content);
self
}
pub fn raw(&mut self, xml: &str) -> &mut Self {
self.buffer.push_str(xml);
self
}
pub fn finish(self) -> String {
self.buffer
}
pub fn as_str(&self) -> &str {
&self.buffer
}
}
impl Default for XmlWriter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_escape_xml() {
assert_eq!(escape_xml("plain"), "plain");
assert_eq!(escape_xml("a & b"), "a & b");
assert_eq!(escape_xml("<tag>"), "<tag>");
assert_eq!(escape_xml("\"quoted\""), ""quoted"");
assert_eq!(escape_xml("it's"), "it's");
}
#[test]
fn test_append_escape_xml() {
let mut buf = String::from("pre:");
append_escape_xml(&mut buf, "a < b & \"c\"");
assert_eq!(buf, "pre:a < b & "c"");
}
#[test]
fn test_xml_writer() {
let mut writer = XmlWriter::new();
writer
.start_element("root", &[("attr", "value")])
.text("content")
.end_element("root");
assert_eq!(writer.finish(), r#"<root attr="value">content</root>"#);
}
#[test]
fn test_xml_writer_empty_element() {
let mut writer = XmlWriter::new();
writer.empty_element("br", &[]);
assert_eq!(writer.finish(), "<br/>");
}
#[test]
fn test_append_usize() {
let mut buf = String::new();
append_usize(&mut buf, 42);
assert_eq!(buf, "42");
}
}