use std::fmt;
use std::io;
#[cfg(feature = "pulldown-cmark")]
use pulldown_cmark::{html, Event, Parser};
pub trait Encoder {
type Error;
fn write_unescaped(&mut self, part: &str) -> Result<(), Self::Error>;
fn write_escaped(&mut self, part: &str) -> Result<(), Self::Error>;
#[cfg(feature = "pulldown-cmark")]
fn write_html<'a, I: Iterator<Item = Event<'a>>>(&mut self, iter: I)
-> Result<(), Self::Error>;
fn format_unescaped<D: fmt::Display>(&mut self, display: D) -> Result<(), Self::Error>;
fn format_escaped<D: fmt::Display>(&mut self, display: D) -> Result<(), Self::Error>;
}
struct EscapingStringEncoder<'a>(&'a mut String);
impl<'a> EscapingStringEncoder<'a> {
fn write_escaped(&mut self, part: &str) {
let mut start = 0;
for (idx, byte) in part.bytes().enumerate() {
let replace = match byte {
b'<' => "<",
b'>' => ">",
b'&' => "&",
b'"' => """,
_ => continue,
};
self.0.push_str(&part[start..idx]);
self.0.push_str(replace);
start = idx + 1;
}
self.0.push_str(&part[start..]);
}
}
impl<'a> fmt::Write for EscapingStringEncoder<'a> {
#[inline]
fn write_str(&mut self, part: &str) -> fmt::Result {
self.write_escaped(part);
Ok(())
}
}
pub(crate) struct EscapingIOEncoder<W: io::Write> {
inner: W,
}
impl<W: io::Write> EscapingIOEncoder<W> {
#[inline]
pub fn new(inner: W) -> Self {
Self { inner }
}
fn write_escaped_bytes(&mut self, part: &[u8]) -> io::Result<()> {
let mut start = 0;
for (idx, byte) in part.iter().enumerate() {
let replace: &[u8] = match *byte {
b'<' => b"<",
b'>' => b">",
b'&' => b"&",
b'"' => b""",
_ => continue,
};
self.inner.write_all(&part[start..idx])?;
self.inner.write_all(replace)?;
start = idx + 1;
}
self.inner.write_all(&part[start..])
}
}
impl<W: io::Write> io::Write for EscapingIOEncoder<W> {
#[inline]
fn write(&mut self, part: &[u8]) -> io::Result<usize> {
self.write_escaped_bytes(part).map(|()| part.len())
}
#[inline]
fn write_all(&mut self, part: &[u8]) -> io::Result<()> {
self.write_escaped_bytes(part)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl<W: io::Write> Encoder for EscapingIOEncoder<W> {
type Error = io::Error;
#[inline]
fn write_unescaped(&mut self, part: &str) -> io::Result<()> {
self.inner.write_all(part.as_bytes())
}
#[inline]
fn write_escaped(&mut self, part: &str) -> io::Result<()> {
self.write_escaped_bytes(part.as_bytes())
}
#[cfg(feature = "pulldown-cmark")]
#[inline]
fn write_html<'a, I: Iterator<Item = Event<'a>>>(&mut self, iter: I) -> io::Result<()> {
html::write_html_io(&mut self.inner, iter)
}
#[inline]
fn format_unescaped<D: fmt::Display>(&mut self, display: D) -> Result<(), Self::Error> {
write!(self.inner, "{}", display)
}
#[inline]
fn format_escaped<D: fmt::Display>(&mut self, display: D) -> Result<(), Self::Error> {
use io::Write;
write!(self, "{}", display)
}
}
pub enum NeverError {}
impl Encoder for String {
type Error = NeverError;
#[inline]
fn write_unescaped(&mut self, part: &str) -> Result<(), Self::Error> {
self.push_str(part);
Ok(())
}
#[inline]
fn write_escaped(&mut self, part: &str) -> Result<(), Self::Error> {
EscapingStringEncoder(self).write_escaped(part);
Ok(())
}
#[cfg(feature = "pulldown-cmark")]
#[inline]
fn write_html<'a, I: Iterator<Item = Event<'a>>>(
&mut self,
iter: I,
) -> Result<(), Self::Error> {
html::push_html(self, iter);
Ok(())
}
#[inline]
fn format_unescaped<D: fmt::Display>(&mut self, display: D) -> Result<(), Self::Error> {
use std::fmt::Write;
let _ = write!(self, "{}", display);
Ok(())
}
#[inline]
fn format_escaped<D: fmt::Display>(&mut self, display: D) -> Result<(), Self::Error> {
use std::fmt::Write;
let _ = write!(EscapingStringEncoder(self), "{}", display);
Ok(())
}
}
#[cfg(feature = "pulldown-cmark")]
pub fn encode_cmark<E: Encoder>(source: &str, encoder: &mut E) -> Result<(), E::Error> {
let parser = Parser::new(source);
encoder.write_html(parser)
}