use std::fmt;
use std::fmt::{Display, Formatter};
use std::io::{self, Write};
#[cfg(not(feature = "check_xml"))]
use std::marker::PhantomData;
#[derive(PartialEq)]
enum Open {
None,
Elem,
Empty,
}
impl Display for Open {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Open::None => f.write_str("None")?,
Open::Elem => f.write_str("Elem")?,
Open::Empty => f.write_str("Empty")?,
}
Ok(())
}
}
#[derive(Debug)]
struct Stack {
#[cfg(feature = "check_xml")]
stack: Vec<String>,
#[cfg(not(feature = "check_xml"))]
stack: PhantomData<String>,
}
#[cfg(feature = "check_xml")]
impl Stack {
fn new() -> Self {
Self { stack: Vec::new() }
}
fn push(&mut self, name: &str) {
self.stack.push(name.to_string());
}
fn pop(&mut self) -> Option<String> {
self.stack.pop()
}
fn is_empty(&self) -> bool {
self.stack.is_empty()
}
}
#[cfg(not(feature = "check_xml"))]
impl Stack {
fn new() -> Self {
Self {
stack: PhantomData {},
}
}
fn push(&mut self, _name: &str) {}
fn pop(&mut self) -> Option<String> {
None
}
fn is_empty(&self) -> bool {
true
}
}
pub(crate) struct XmlWriter<W: Write> {
writer: Box<W>,
buf: String,
stack: Stack,
open: Open,
}
impl<W: Write> fmt::Debug for XmlWriter<W> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"XmlWriter {{ stack: {:?}, opened: {} }}",
self.stack, self.open
)
}
}
impl<W: Write> XmlWriter<W> {
pub(crate) fn new(writer: W) -> XmlWriter<W> {
XmlWriter {
stack: Stack::new(),
buf: String::new(),
writer: Box::new(writer),
open: Open::None,
}
}
pub(crate) fn dtd(&mut self, encoding: &str) -> io::Result<()> {
self.buf.push_str("<?xml version=\"1.0\" encoding=\"");
self.buf.push_str(encoding);
self.buf.push_str("\" ?>\n");
Ok(())
}
pub(crate) fn elem_text<S: AsRef<str>>(&mut self, name: &str, text: S) -> io::Result<()> {
self.close_elem()?;
self.buf.push('<');
self.buf.push_str(name);
self.buf.push('>');
self.buf.push_str(text.as_ref());
self.buf.push('<');
self.buf.push('/');
self.buf.push_str(name);
self.buf.push('>');
Ok(())
}
#[allow(dead_code)]
pub(crate) fn elem_text_esc<S: AsRef<str>>(&mut self, name: &str, text: S) -> io::Result<()> {
self.close_elem()?;
self.buf.push('<');
self.buf.push_str(name);
self.buf.push('>');
self.escape(text.as_ref(), false);
self.buf.push('<');
self.buf.push('/');
self.buf.push_str(name);
self.buf.push('>');
Ok(())
}
pub(crate) fn elem(&mut self, name: &str) -> io::Result<()> {
self.close_elem()?;
self.stack.push(name);
self.buf.push('<');
self.open = Open::Elem;
self.buf.push_str(name);
Ok(())
}
pub(crate) fn empty(&mut self, name: &str) -> io::Result<()> {
self.close_elem()?;
self.buf.push('<');
self.open = Open::Empty;
self.buf.push_str(name);
Ok(())
}
fn close_elem(&mut self) -> io::Result<()> {
match self.open {
Open::None => {}
Open::Elem => {
self.buf.push('>');
}
Open::Empty => {
self.buf.push('/');
self.buf.push('>');
}
}
self.open = Open::None;
self.write_buf()?;
Ok(())
}
pub(crate) fn attr<S: AsRef<str>>(&mut self, name: &str, value: S) -> io::Result<()> {
if cfg!(feature = "check_xml") && self.open == Open::None {
panic!(
"Attempted to write attr to elem, when no elem was opened, stack {:?}",
self.stack
);
}
self.buf.push(' ');
self.buf.push_str(name);
self.buf.push('=');
self.buf.push('"');
self.buf.push_str(value.as_ref());
self.buf.push('"');
Ok(())
}
pub(crate) fn attr_esc<S: AsRef<str>>(&mut self, name: &str, value: S) -> io::Result<()> {
if cfg!(feature = "check_xml") && self.open == Open::None {
panic!(
"Attempted to write attr to elem, when no elem was opened, stack {:?}",
self.stack
);
}
self.buf.push(' ');
self.escape(name, true);
self.buf.push('=');
self.buf.push('"');
self.escape(value.as_ref(), false);
self.buf.push('"');
Ok(())
}
fn escape(&mut self, text: &str, ident: bool) {
for c in text.chars() {
match c {
'"' => self.buf.push_str("""),
'\'' => self.buf.push_str("'"),
'&' => self.buf.push_str("&"),
'<' => self.buf.push_str("<"),
'>' => self.buf.push_str(">"),
'\\' if ident => {
self.buf.push('\\');
self.buf.push('\\');
}
_ => {
self.buf.push(c);
}
};
}
}
pub(crate) fn text<S: AsRef<str>>(&mut self, text: S) -> io::Result<()> {
self.close_elem()?;
self.buf.push_str(text.as_ref());
Ok(())
}
pub(crate) fn text_esc<S: AsRef<str>>(&mut self, text: S) -> io::Result<()> {
self.close_elem()?;
self.escape(text.as_ref(), false);
Ok(())
}
pub(crate) fn end_elem(&mut self, name: &str) -> io::Result<()> {
self.close_elem()?;
if cfg!(feature = "check_xml") {
match self.stack.pop() {
Some(test) => {
if name != test {
panic!(
"Attempted to close elem {} but the open was {}, stack {:?}",
name, test, self.stack
)
}
}
None => panic!(
"Attempted to close an elem, when none was open, stack {:?}",
self.stack
),
}
}
self.buf.push('<');
self.buf.push('/');
self.buf.push_str(name);
self.buf.push('>');
Ok(())
}
fn write_buf(&mut self) -> io::Result<()> {
self.writer.write_all(self.buf.as_bytes())?;
self.buf.clear();
Ok(())
}
pub(crate) fn close(&mut self) -> io::Result<()> {
self.write_buf()?;
if cfg!(feature = "check_xml") && !self.stack.is_empty() {
panic!(
"Attempted to close the xml, but there are open elements on the stack {:?}",
self.stack
)
}
Ok(())
}
}