use std::fmt::Display;
use std::io::{self, Write};
pub trait ToHtml {
fn to_html(&self, out: &mut dyn Write) -> io::Result<()>;
fn to_buffer(&self) -> io::Result<HtmlBuffer> {
let mut buf = Vec::new();
self.to_html(&mut buf)?;
Ok(HtmlBuffer { buf })
}
}
pub struct HtmlBuffer {
#[doc(hidden)]
buf: Vec<u8>,
}
impl std::fmt::Debug for HtmlBuffer {
fn fmt(&self, out: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(out, "HtmlBuffer({:?})", String::from_utf8_lossy(&self.buf))
}
}
impl ToHtml for HtmlBuffer {
fn to_html(&self, out: &mut dyn Write) -> io::Result<()> {
out.write_all(&self.buf)
}
}
impl AsRef<[u8]> for HtmlBuffer {
fn as_ref(&self) -> &[u8] {
&self.buf
}
}
impl PartialEq<&[u8]> for HtmlBuffer {
fn eq(&self, other: &&[u8]) -> bool {
&self.buf == other
}
}
impl PartialEq<&str> for HtmlBuffer {
fn eq(&self, other: &&str) -> bool {
let other: &[u8] = other.as_ref();
self.buf == other
}
}
#[allow(dead_code)]
pub struct Html<T>(pub T);
impl<T: Display> ToHtml for Html<T> {
#[inline]
fn to_html(&self, out: &mut dyn Write) -> io::Result<()> {
write!(out, "{}", self.0)
}
}
impl<T: Display> ToHtml for T {
#[inline]
fn to_html(&self, out: &mut dyn Write) -> io::Result<()> {
write!(ToHtmlEscapingWriter(out), "{self}")
}
}
struct ToHtmlEscapingWriter<'a>(&'a mut dyn Write);
impl Write for ToHtmlEscapingWriter<'_> {
#[inline]
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
let n = data
.iter()
.take_while(|&&c| {
c != b'"' && c != b'&' && c != b'\'' && c != b'<' && c != b'>'
})
.count();
if n > 0 {
self.0.write(&data[0..n])
} else {
Self::write_one_byte_escaped(&mut self.0, data)
}
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
impl ToHtmlEscapingWriter<'_> {
#[inline(never)]
fn write_one_byte_escaped(
out: &mut impl Write,
data: &[u8],
) -> io::Result<usize> {
let next = data.first();
out.write_all(match next {
Some(b'"') => b""",
Some(b'&') => b"&",
Some(b'<') => b"<",
Some(b'>') => b">",
None => return Ok(0),
_ => b"'",
})?;
Ok(1)
}
}