use std::borrow::Cow;
use std::fmt;
use std::io::Write;
use std::marker::PhantomData;
use quick_xml::events::{BytesDecl, BytesText, Event};
use quick_xml::Writer as QuickWriter;
use super::common::XmlVersion;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
Xml(quick_xml::Error),
UnbalancedEndTag,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Io(err) => write!(f, "io error: {}", err),
Error::Xml(err) => write!(f, "xml error: {}", err),
Error::UnbalancedEndTag => write!(f, "attempted to close more elements than opened"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Io(err) => Some(err),
Error::Xml(err) => Some(err),
Error::UnbalancedEndTag => None,
}
}
}
impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Error::Io(value)
}
}
impl From<quick_xml::Error> for Error {
fn from(value: quick_xml::Error) -> Self {
Error::Xml(value)
}
}
#[derive(Clone, Debug)]
pub struct EmitterConfig {
pub write_document_declaration: bool,
pub perform_escaping: bool,
pub perform_indent: bool,
pub line_separator: Cow<'static, str>,
}
impl Default for EmitterConfig {
fn default() -> Self {
Self {
write_document_declaration: true,
perform_escaping: true,
perform_indent: false,
line_separator: Cow::Borrowed("\n"),
}
}
}
impl EmitterConfig {
pub fn create_writer<W: Write>(&self, writer: W) -> EventWriter<W> {
EventWriter {
writer: QuickWriter::new(writer),
perform_escaping: self.perform_escaping,
element_stack: Vec::new(),
}
}
}
#[derive(Debug)]
struct ElementState {
name: String,
attributes: Vec<Attribute>,
pending: bool,
}
pub struct EventWriter<W: Write> {
writer: QuickWriter<W>,
perform_escaping: bool,
element_stack: Vec<ElementState>,
}
impl<W: Write> EventWriter<W> {
pub fn write<'a, E>(&mut self, event: E) -> Result<()>
where
E: Into<XmlEvent<'a>>,
{
match event.into() {
XmlEvent::StartDocument {
version,
encoding,
standalone,
} => {
let standalone_text = standalone.map(|flag| if flag { "yes" } else { "no" });
let decl = BytesDecl::new(version.as_str(), encoding, standalone_text);
self.writer.write_event(Event::Decl(decl))?;
}
XmlEvent::StartElement(element) => {
self.flush_pending()?;
let StartElement {
name, attributes, ..
} = element;
self.element_stack.push(ElementState {
name,
attributes,
pending: true,
});
}
XmlEvent::EndElement => {
let state = self.element_stack.pop().ok_or(Error::UnbalancedEndTag)?;
if state.pending {
self.write_empty(state)?;
} else {
self.write_closing(&state.name)?;
}
}
XmlEvent::Characters(text) => {
self.flush_pending()?;
let text_event = if self.perform_escaping {
BytesText::new(text.as_ref())
} else {
BytesText::from_escaped(text.as_ref())
};
self.writer.write_event(Event::Text(text_event))?;
}
}
Ok(())
}
pub fn into_inner(self) -> Result<W> {
Ok(self.writer.into_inner())
}
pub fn inner_mut(&mut self) -> Result<&mut W> {
Ok(self.writer.get_mut())
}
fn flush_pending(&mut self) -> Result<()> {
if let Some(state) = self.element_stack.last_mut() {
if state.pending {
state.pending = false;
let writer = self.writer.get_mut();
writer.write_all(b"<").map_err(Error::from)?;
writer
.write_all(state.name.as_bytes())
.map_err(Error::from)?;
for attr in &state.attributes {
writer.write_all(b" ").map_err(Error::from)?;
writer
.write_all(attr.name.as_bytes())
.map_err(Error::from)?;
writer.write_all(b"=\"").map_err(Error::from)?;
writer
.write_all(attr.value.as_bytes())
.map_err(Error::from)?;
writer.write_all(b"\"").map_err(Error::from)?;
}
writer.write_all(b">").map_err(Error::from)?;
}
}
Ok(())
}
fn write_empty(&mut self, state: ElementState) -> Result<()> {
self.write_tag(state.name.as_str(), &state.attributes, b" />")
}
fn write_closing(&mut self, name: &str) -> Result<()> {
let writer = self.writer.get_mut();
writer.write_all(b"</").map_err(Error::from)?;
writer.write_all(name.as_bytes()).map_err(Error::from)?;
writer.write_all(b">").map_err(Error::from)
}
fn write_tag(&mut self, name: &str, attributes: &[Attribute], suffix: &[u8]) -> Result<()> {
let writer = self.writer.get_mut();
writer.write_all(b"<").map_err(Error::from)?;
writer.write_all(name.as_bytes()).map_err(Error::from)?;
for attr in attributes {
writer.write_all(b" ").map_err(Error::from)?;
writer
.write_all(attr.name.as_bytes())
.map_err(Error::from)?;
writer.write_all(b"=\"").map_err(Error::from)?;
writer
.write_all(attr.value.as_bytes())
.map_err(Error::from)?;
writer.write_all(b"\"").map_err(Error::from)?;
}
writer.write_all(suffix).map_err(Error::from)
}
}
#[derive(Clone, Debug)]
pub struct StartElement<'a> {
name: String,
attributes: Vec<Attribute>,
_marker: PhantomData<&'a ()>,
}
#[derive(Clone, Debug)]
struct Attribute {
name: String,
value: String,
}
#[derive(Clone, Debug)]
pub enum XmlEvent<'a> {
StartDocument {
version: XmlVersion,
encoding: Option<&'a str>,
standalone: Option<bool>,
},
StartElement(StartElement<'a>),
EndElement,
Characters(Cow<'a, str>),
}
impl<'a> XmlEvent<'a> {
pub fn start_element(name: &'a str) -> StartElement<'a> {
StartElement {
name: name.to_string(),
attributes: Vec::new(),
_marker: std::marker::PhantomData,
}
}
pub fn end_element() -> XmlEvent<'static> {
XmlEvent::EndElement
}
}
impl<'a> From<StartElement<'a>> for XmlEvent<'a> {
fn from(value: StartElement<'a>) -> Self {
XmlEvent::StartElement(value)
}
}
impl<'a> From<&'a str> for XmlEvent<'a> {
fn from(value: &'a str) -> Self {
XmlEvent::Characters(Cow::Borrowed(value))
}
}
impl From<String> for XmlEvent<'static> {
fn from(value: String) -> Self {
XmlEvent::Characters(Cow::Owned(value))
}
}
impl<'a> StartElement<'a> {
pub fn attr(mut self, name: &str, value: &str) -> Self {
self.attributes.push(Attribute {
name: name.to_string(),
value: value.to_string(),
});
self
}
}