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