use std::{borrow::Cow, io};
use v_htmlescape::escape;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct QName<'a>(Cow<'a, str>);
impl From<String> for QName<'static> {
fn from(src: String) -> Self {
QName(src.into())
}
}
impl<'a> From<&'a str> for QName<'a> {
fn from(src: &'a str) -> Self {
QName(src.into())
}
}
impl<'a> QName<'a> {
fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}
#[derive(Debug)]
pub struct AttrPair<'n, 'v> {
name: QName<'n>,
value: Cow<'v, str>,
}
pub trait RenderTarget {
fn start_element_attrs(&mut self, name: QName, attrs: &[&AttrPair]) -> Result<(), io::Error>;
fn text(&mut self, content: &str) -> Result<(), io::Error>;
fn end_element(&mut self, name: QName) -> Result<(), io::Error>;
}
pub trait WeftRenderable {
fn render_to(&self, target: &mut impl RenderTarget) -> Result<(), io::Error>;
}
impl<'a, T: RenderTarget> RenderTarget for &'a mut T {
fn start_element_attrs(&mut self, name: QName, attrs: &[&AttrPair]) -> Result<(), io::Error> {
(**self).start_element_attrs(name, attrs)
}
fn text(&mut self, content: &str) -> Result<(), io::Error> {
(**self).text(content)
}
fn end_element(&mut self, name: QName) -> Result<(), io::Error> {
(**self).end_element(name)
}
}
impl<'a, R: WeftRenderable> WeftRenderable for &'a R {
fn render_to(&self, target: &mut impl RenderTarget) -> Result<(), io::Error> {
(**self).render_to(target)
}
}
struct Html5Ser<T>(T);
impl<'a, T: 'a + io::Write> RenderTarget for Html5Ser<T> {
fn start_element_attrs(&mut self, name: QName, attrs: &[&AttrPair]) -> Result<(), io::Error> {
self.0.write_all(b"<")?;
self.0.write_all(name.0.as_bytes())?;
for attr in attrs {
self.0.write_all(b" ")?;
self.0.write_all(attr.name.as_bytes())?;
self.0.write_all(b"=")?;
write!(self.0, "\"{}\"", escape(&attr.value))?;
}
self.0.write_all(b">")?;
Ok(())
}
fn text(&mut self, content: &str) -> Result<(), io::Error> {
write!(self.0, "{}", escape(content))?;
Ok(())
}
fn end_element(&mut self, name: QName) -> Result<(), io::Error> {
self.0.write_all(b"</")?;
self.0.write_all(name.0.as_bytes())?;
self.0.write_all(b">")?;
Ok(())
}
}
impl<'n, 'v> AttrPair<'n, 'v> {
pub fn new(name: QName<'n>, value: Cow<'v, str>) -> Self {
AttrPair { name, value }
}
}
pub fn render_writer<R: WeftRenderable, W: io::Write>(widget: R, out: W) -> Result<(), io::Error> {
let mut ser = Html5Ser(out);
widget.render_to(&mut ser)?;
Ok(())
}
pub fn render_to_string<R: WeftRenderable>(widget: R) -> Result<String, io::Error> {
let mut out = Vec::new();
render_writer(widget, &mut out)?;
Ok(String::from_utf8_lossy(&out).into_owned())
}