use std::{fmt, io};
use std::io::Write as _;
use std::fmt::Write as _;
use crate::util::base64;
use super::decode::Name;
#[derive(Debug)]
pub struct Writer<W> {
wrapped: W,
error: Option<io::Error>,
indent: &'static str,
indent_level: usize,
}
impl<W: io::Write> Writer<W> {
pub fn new(wrapped: W) -> Self {
Writer {
wrapped,
error: None,
indent: " ",
indent_level: 0,
}
}
pub fn set_indent(&mut self, s: &'static str) {
self.indent = s
}
pub fn element<'s>(
&'s mut self, tag: Name<'static, 'static>,
) -> Result<Element<'s, W>, io::Error> {
Element::start(self, tag)
}
pub fn into_wrapped(mut self) -> Result<W, io::Error> {
if let Some(err) = self.error.take() {
Err(err)
}
else {
Ok(self.wrapped)
}
}
pub fn done(mut self) -> Result<(), io::Error> {
if let Some(err) = self.error.take() {
Err(err)
}
else {
Ok(())
}
}
}
impl<W: io::Write> Writer<W> {
fn store_error(&mut self, error: io::Error) {
self.error = Some(error)
}
fn indent(&mut self) {
self.indent_level = self.indent_level.saturating_add(1);
}
fn dedent(&mut self) {
self.indent_level = self.indent_level.saturating_sub(1);
}
fn write_indent(&mut self) -> Result<(), io::Error> {
if self.indent_level == 0 || self.indent.is_empty() {
return Ok(())
}
for _ in 0..self.indent_level {
self.write_all(self.indent.as_bytes())?
}
Ok(())
}
}
impl<W: io::Write> io::Write for Writer<W> {
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
if let Some(err) = self.error.take() {
return Err(err)
}
self.wrapped.write(buf)
}
fn flush(&mut self) -> Result<(), io::Error> {
if let Some(err) = self.error.take() {
return Err(err)
}
self.wrapped.flush()
}
}
#[derive(Debug)]
pub struct Element<'a, W: io::Write> {
writer: &'a mut Writer<W>,
tag: Name<'static, 'static>,
empty: bool,
}
impl<'a, W: io::Write> Element<'a, W> {
fn start(
writer: &'a mut Writer<W>, tag: Name<'static, 'static>,
) -> Result<Self, io::Error> {
writer.write_all(b"<")?;
if let Some(ns) = tag.namespace() {
writer.write_all(ns)?;
writer.write_all(b":")?;
}
writer.write_all(tag.local())?;
Ok(Element { writer, tag, empty: true })
}
pub fn attr(
mut self, name: &str, value: &(impl Text + ?Sized),
) -> Result<Self, io::Error> {
self.writer.write_all(b" ")?;
self.writer.write_all(name.as_bytes())?;
self.writer.write_all(b"=\"")?;
value.write_escaped(TextEscape::Attr, &mut self.writer)?;
self.writer.write_all(b"\"")?;
Ok(self)
}
pub fn attr_opt(
self, name: &str, value: Option<&(impl Text + ?Sized)>,
) -> Result<Self, io::Error> {
match value {
None => Ok(self),
Some(value) => self.attr(name, value)
}
}
pub fn content(
mut self, op: impl FnOnce(&mut Content<W>) -> Result<(), io::Error>
) -> Result<Self, io::Error> {
self.empty = false;
self.writer.write_all(b">")?;
self.writer.indent();
op(&mut Content { writer: self.writer})?;
self.writer.dedent();
Ok(self)
}
fn end(&mut self) -> Result<(), io::Error> {
if self.empty {
self.writer.write_all(b"/>")?;
}
else {
self.writer.write_all(b"\n")?;
self.writer.write_indent()?;
self.writer.write_all(b"</")?;
if let Some(ns) = self.tag.namespace() {
self.writer.write_all(ns)?;
self.writer.write_all(b":")?;
}
self.writer.write_all(self.tag.local())?;
self.writer.write_all(b">")?;
}
Ok(())
}
}
impl<W: io::Write> Drop for Element<'_, W> {
fn drop(&mut self) {
if let Err(err) = self.end() {
self.writer.store_error(err)
}
}
}
#[derive(Debug)]
pub struct Content<'a, W> {
writer: &'a mut Writer<W>,
}
impl<W: io::Write> Content<'_, W> {
pub fn element<'s>(
&'s mut self, tag: Name<'static, 'static>
) -> Result<Element<'s, W>, io::Error> {
self.writer.write_all(b"\n")?;
self.writer.write_indent()?;
Element::start(self.writer, tag)
}
pub fn element_opt<'s, T>(
&'s mut self,
option: Option<&T>,
tag: Name<'static, 'static>,
op: impl FnOnce(&T, Element<'s, W>) -> Result<(), io::Error>
) -> Result<(), io::Error> {
if let Some(opt) = option {
let element = self.element(tag)?;
op(opt, element)
} else {
Ok(())
}
}
pub fn pcdata(
&mut self, text: &(impl Text + ?Sized)
) -> Result<(), io::Error> {
self.writer.write_all(b"\n")?;
self.writer.write_indent()?;
text.write_escaped(TextEscape::Pcdata, &mut self.writer)
}
pub fn raw(
&mut self, text: &(impl Text + ?Sized)
) -> Result<(), io::Error> {
self.writer.write_all(b"\n")?;
self.writer.write_indent()?;
text.write_raw(&mut self.writer)
}
pub fn base64(
&mut self, data: &(impl Text + ?Sized)
) -> Result<(), io::Error> {
self.writer.write_all(b"\n")?;
self.writer.write_indent()?;
data.write_base64(&mut self.writer)
}
}
pub trait Text {
fn write_escaped(
&self, mode: TextEscape, target: &mut impl io::Write
) -> Result<(), io::Error>;
fn write_raw(
&self, target: &mut impl io::Write
) -> Result<(), io::Error>;
fn write_base64(
&self, target: &mut impl io::Write
) -> Result<(), io::Error> {
self.write_raw(&mut base64::Xml.encode_writer(target))
}
}
impl Text for [u8] {
fn write_escaped(
&self, mode: TextEscape, target: &mut impl io::Write
) -> Result<(), io::Error> {
mode.write_escaped(self, target)
}
fn write_raw(
&self, target: &mut impl io::Write
) -> Result<(), io::Error> {
target.write_all(self)
}
}
impl Text for str {
fn write_escaped(
&self, mode: TextEscape, target: &mut impl io::Write
) -> Result<(), io::Error> {
mode.write_escaped(self.as_bytes(), target)
}
fn write_raw(
&self, target: &mut impl io::Write
) -> Result<(), io::Error> {
target.write_all(self.as_bytes())
}
}
impl<T: fmt::Display> Text for T {
fn write_escaped(
&self, mode: TextEscape, target: &mut impl io::Write
) -> Result<(), io::Error> {
let mut adaptor = DisplayText::new(target, mode);
match write!(adaptor, "{self}") {
Ok(()) => Ok(()),
Err(_) => match adaptor.into_result() {
Ok(()) => Err(io::Error::other("formatter error")),
Err(err) => Err(err)
}
}
}
fn write_raw(
&self, target: &mut impl io::Write
) -> Result<(), io::Error> {
write!(target, "{self}")
}
}
struct DisplayText<'a, W> {
inner: &'a mut W,
escape: TextEscape,
error: Result<(), io::Error>,
}
impl<'a, W: io::Write> DisplayText<'a, W> {
fn new(inner: &'a mut W, escape: TextEscape) -> Self {
DisplayText {
inner, escape,
error: Ok(()),
}
}
fn into_result(self) -> Result<(), io::Error> {
self.error
}
}
impl<W: io::Write> fmt::Write for DisplayText<'_, W> {
fn write_str(&mut self, s: &str) -> fmt::Result {
match self.escape.write_escaped(s.as_bytes(), self.inner) {
Ok(()) => Ok(()),
Err(err) => {
self.error = Err(err);
Err(fmt::Error)
}
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum TextEscape {
Attr,
Pcdata,
}
impl TextEscape {
fn replace_char(self, ch: u8) -> Option<&'static str> {
match self {
TextEscape::Attr => {
match ch {
b'<' => Some("<"),
b'>' => Some(">"),
b'"' => Some("""),
b'\'' => Some("'"),
b'&' => Some("&"),
_ => None
}
}
TextEscape::Pcdata => {
match ch {
b'<' => Some("<"),
b'&' => Some("&"),
_ => None
}
}
}
}
fn write_escaped(
self, mut s: &[u8], target: &mut impl io::Write
) -> Result<(), io::Error> {
while !s.is_empty() {
let mut iter = s.iter().enumerate().map(|(idx, ch)| {
(idx, self.replace_char(*ch))
});
let end = loop {
match iter.next() {
Some((idx, Some(repl))) => {
target.write_all(&s[0..idx])?;
target.write_all(repl.as_bytes())?;
break idx;
}
Some((_, None)) => { }
None => {
return target.write_all(s);
}
}
};
s = &s[end + 1..];
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
const ROOT_TAG: Name = Name::unqualified(b"root");
const OUTER_TAG: Name = Name::unqualified(b"outer");
const INNER_TAG: Name = Name::unqualified(b"inner");
#[test]
fn xml_doc_should_not_be_wrapped_with_whitespace() -> io::Result<()> {
let mut buf = Vec::<u8>::new();
let mut writer = Writer::new(&mut buf);
writer.element(ROOT_TAG)?;
writer.done()?;
assert_eq!("<root/>", std::str::from_utf8(&buf).unwrap());
Ok(())
}
#[test]
fn test_indent() -> io::Result<()> {
let mut buf = Vec::<u8>::new();
let mut writer = Writer::new(&mut buf);
writer
.element(ROOT_TAG)?
.content(|content| {
content.element(OUTER_TAG)?.content(|content| {
content.element(INNER_TAG)?;
Ok(())
})?;
Ok(())
})?;
writer.done()?;
assert_eq!(
"<root>\
\n <outer>\
\n <inner/>\
\n </outer>\
\n</root>",
std::str::from_utf8(&buf).unwrap()
);
Ok(())
}
}