1use std::fmt;
9use std::io;
10
11#[cfg(feature = "pulldown-cmark")]
12use pulldown_cmark::{html, Event, Parser};
13
14pub trait Encoder {
17 type Error;
19
20 fn write_unescaped(&mut self, part: &str) -> Result<(), Self::Error>;
22
23 fn write_escaped(&mut self, part: &str) -> Result<(), Self::Error>;
25
26 #[cfg(feature = "pulldown-cmark")]
27 fn write_html<'a, I: Iterator<Item = Event<'a>>>(&mut self, iter: I)
29 -> Result<(), Self::Error>;
30
31 fn format_unescaped<D: fmt::Display>(&mut self, display: D) -> Result<(), Self::Error>;
33
34 fn format_escaped<D: fmt::Display>(&mut self, display: D) -> Result<(), Self::Error>;
36}
37
38struct EscapingStringEncoder<'a>(&'a mut String);
40
41impl<'a> EscapingStringEncoder<'a> {
42 fn write_escaped(&mut self, part: &str) {
45 let mut start = 0;
46
47 for (idx, byte) in part.bytes().enumerate() {
48 let replace = match byte {
49 b'<' => "<",
50 b'>' => ">",
51 b'&' => "&",
52 b'"' => """,
53 _ => continue,
54 };
55
56 self.0.push_str(&part[start..idx]);
57 self.0.push_str(replace);
58
59 start = idx + 1;
60 }
61
62 self.0.push_str(&part[start..]);
63 }
64}
65
66impl<'a> fmt::Write for EscapingStringEncoder<'a> {
68 #[inline]
69 fn write_str(&mut self, part: &str) -> fmt::Result {
70 self.write_escaped(part);
71
72 Ok(())
73 }
74}
75
76pub(crate) struct EscapingIOEncoder<W: io::Write> {
79 inner: W,
80}
81
82impl<W: io::Write> EscapingIOEncoder<W> {
83 #[inline]
84 pub fn new(inner: W) -> Self {
85 Self { inner }
86 }
87
88 fn write_escaped_bytes(&mut self, part: &[u8]) -> io::Result<()> {
91 let mut start = 0;
92
93 for (idx, byte) in part.iter().enumerate() {
94 let replace: &[u8] = match *byte {
95 b'<' => b"<",
96 b'>' => b">",
97 b'&' => b"&",
98 b'"' => b""",
99 _ => continue,
100 };
101
102 self.inner.write_all(&part[start..idx])?;
103 self.inner.write_all(replace)?;
104
105 start = idx + 1;
106 }
107
108 self.inner.write_all(&part[start..])
109 }
110}
111
112impl<W: io::Write> io::Write for EscapingIOEncoder<W> {
115 #[inline]
116 fn write(&mut self, part: &[u8]) -> io::Result<usize> {
117 self.write_escaped_bytes(part).map(|()| part.len())
118 }
119
120 #[inline]
121 fn write_all(&mut self, part: &[u8]) -> io::Result<()> {
122 self.write_escaped_bytes(part)
123 }
124
125 #[inline]
126 fn flush(&mut self) -> io::Result<()> {
127 Ok(())
128 }
129}
130
131impl<W: io::Write> Encoder for EscapingIOEncoder<W> {
132 type Error = io::Error;
133
134 #[inline]
135 fn write_unescaped(&mut self, part: &str) -> io::Result<()> {
136 self.inner.write_all(part.as_bytes())
137 }
138
139 #[inline]
140 fn write_escaped(&mut self, part: &str) -> io::Result<()> {
141 self.write_escaped_bytes(part.as_bytes())
142 }
143
144 #[cfg(feature = "pulldown-cmark")]
145 #[inline]
146 fn write_html<'a, I: Iterator<Item = Event<'a>>>(&mut self, iter: I) -> io::Result<()> {
147 html::write_html_io(&mut self.inner, iter)
148 }
149
150 #[inline]
151 fn format_unescaped<D: fmt::Display>(&mut self, display: D) -> Result<(), Self::Error> {
152 write!(self.inner, "{}", display)
153 }
154
155 #[inline]
156 fn format_escaped<D: fmt::Display>(&mut self, display: D) -> Result<(), Self::Error> {
157 use io::Write;
158
159 write!(self, "{}", display)
160 }
161}
162
163pub enum NeverError {}
166
167impl Encoder for String {
168 type Error = NeverError;
170
171 #[inline]
172 fn write_unescaped(&mut self, part: &str) -> Result<(), Self::Error> {
173 self.push_str(part);
174
175 Ok(())
176 }
177
178 #[inline]
179 fn write_escaped(&mut self, part: &str) -> Result<(), Self::Error> {
180 EscapingStringEncoder(self).write_escaped(part);
181
182 Ok(())
183 }
184
185 #[cfg(feature = "pulldown-cmark")]
186 #[inline]
187 fn write_html<'a, I: Iterator<Item = Event<'a>>>(
188 &mut self,
189 iter: I,
190 ) -> Result<(), Self::Error> {
191 html::push_html(self, iter);
192
193 Ok(())
194 }
195
196 #[inline]
197 fn format_unescaped<D: fmt::Display>(&mut self, display: D) -> Result<(), Self::Error> {
198 use std::fmt::Write;
199
200 let _ = write!(self, "{}", display);
202
203 Ok(())
204 }
205
206 #[inline]
207 fn format_escaped<D: fmt::Display>(&mut self, display: D) -> Result<(), Self::Error> {
208 use std::fmt::Write;
209
210 let _ = write!(EscapingStringEncoder(self), "{}", display);
212
213 Ok(())
214 }
215}
216
217#[cfg(feature = "pulldown-cmark")]
218pub fn encode_cmark<E: Encoder>(source: &str, encoder: &mut E) -> Result<(), E::Error> {
220 let parser = Parser::new(source);
221
222 encoder.write_html(parser)
223}