///|
/// A minimal streaming XML writer.
pub struct Writer {
priv buf : StringBuilder
} derive(Debug)
///|
pub fn Writer::new() -> Writer {
{ buf: StringBuilder() }
}
///|
/// Return the accumulated document.
pub fn Writer::to_string(self : Writer) -> String {
self.buf.to_string()
}
///|
/// Write the XML declaration used by RSS/Atom output.
pub fn Writer::write_decl(self : Writer) -> Unit {
self.buf.write_string("")
}
///|
/// Write an opening tag ``.
pub fn Writer::open(self : Writer, name : String) -> Unit {
self.buf.write_string("<\{name}>")
}
///|
/// Write an opening tag with attributes, ``.
/// Attribute values are escaped.
pub fn Writer::open_with(
self : Writer,
name : String,
attrs~ : Array[(String, String)],
) -> Unit {
self.buf.write_string("<\{name}")
for pair in attrs {
let (k, v) = pair
self.buf.write_string(" \{k}=\"\{escape_attr(v)}\"")
}
self.buf.write_string(">")
}
///|
/// Write a closing tag ``.
pub fn Writer::close(self : Writer, name : String) -> Unit {
self.buf.write_string("\{name}>")
}
///|
/// Write a self-closing tag; attributes are escaped.
pub fn Writer::empty(
self : Writer,
name : String,
attrs? : Array[(String, String)] = [],
) -> Unit {
if attrs.length() == 0 {
self.buf.write_string("<\{name}/>")
} else {
self.buf.write_string("<\{name}")
for pair in attrs {
let (k, v) = pair
self.buf.write_string(" \{k}=\"\{escape_attr(v)}\"")
}
self.buf.write_string("/>")
}
}
///|
/// Write escaped character data.
pub fn Writer::text(self : Writer, content : String) -> Unit {
self.buf.write_string(escape_text(content))
}
///|
/// Write pre-escaped / raw markup. The caller guarantees well-formedness.
pub fn Writer::raw(self : Writer, content : String) -> Unit {
self.buf.write_string(content)
}
///|
/// Write a CDATA section, splitting any `]]>` occurrences so the
/// section stays well-formed.
pub fn Writer::cdata(self : Writer, content : String) -> Unit {
self.buf.write_string("", new="]]]]>"))
self.buf.write_string("]]>")
}
///|
/// Escape text-node content.
pub fn escape_text(s : String) -> String {
if !(s.contains("&") ||
s.contains("<") ||
s.contains(">") ||
s.contains("\"") ||
s.contains("'")) {
return s
}
let sb = StringBuilder()
for c in s {
match c {
'&' => sb.write_string("&")
'<' => sb.write_string("<")
'>' => sb.write_string(">")
'"' => sb.write_string(""")
'\'' => sb.write_string("'")
_ => sb.write_char(c)
}
}
sb.to_string()
}
///|
/// Escape attribute-value content.
pub fn escape_attr(s : String) -> String {
if !(s.contains("&") ||
s.contains("<") ||
s.contains(">") ||
s.contains("\"") ||
s.contains("'")) {
return s
}
let sb = StringBuilder()
for c in s {
match c {
'&' => sb.write_string("&")
'<' => sb.write_string("<")
'>' => sb.write_string(">")
'"' => sb.write_string(""")
'\'' => sb.write_string("'")
_ => sb.write_char(c)
}
}
sb.to_string()
}