use core::fmt::Write;
#[cfg(feature = "std")]
use std::io;
#[cfg(feature = "std")]
pub(super) struct IoSink<W> {
pub(super) inner: W,
pub(super) error: Option<io::Error>,
}
#[cfg(feature = "std")]
impl<W: io::Write> Write for IoSink<W> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.inner.write_all(s.as_bytes()).map_err(|e| {
self.error = Some(e);
core::fmt::Error
})
}
}
pub(super) fn write_text<W: Write>(
text: &str,
out: &mut W,
) -> core::fmt::Result {
write_escaped(text, out, escape_text)
}
const fn escape_text(c: char) -> Option<&'static str> {
match c {
'&' => Some("&"),
'<' => Some("<"),
'>' => Some(">"),
'\r' => Some(" "),
_ => None,
}
}
fn write_escaped<W: Write>(
text: &str,
out: &mut W,
escape: fn(char) -> Option<&'static str>,
) -> core::fmt::Result {
for c in text.chars() {
match escape(c) {
Some(markup) => out.write_str(markup)?,
None if needs_reference(c) => write_reference(c, out)?,
None => out.write_char(c)?,
}
}
Ok(())
}
pub(super) const fn is_c0_control(c: char) -> bool {
matches!(c, '\u{1}'..='\u{8}' | '\u{b}' | '\u{c}' | '\u{e}'..='\u{1f}')
}
pub(super) const fn needs_reference(c: char) -> bool {
is_c0_control(c) || changes_in_11(c)
}
pub(super) const fn changes_in_11(c: char) -> bool {
matches!(c, '\u{7f}'..='\u{9f}' | '\u{2028}')
}
pub(super) fn write_reference<W: Write>(
c: char,
out: &mut W,
) -> core::fmt::Result {
write!(out, "&#{};", c as u32)
}
pub(super) fn write_attribute_value<W: Write>(
value: &str,
out: &mut W,
) -> core::fmt::Result {
write_escaped(value, out, escape_attribute_value)
}
const fn escape_attribute_value(c: char) -> Option<&'static str> {
match c {
'"' => Some("""),
'\t' => Some("	"),
'\n' => Some(" "),
_ => escape_text(c),
}
}