html5ever/serialize/
mod.rs1use log::warn;
11pub use markup5ever::serialize::{AttrRef, Serialize, Serializer, TraversalScope};
12use markup5ever::{local_name, ns};
13use memchr::{memchr2, memchr3};
14use std::io::{self, Write};
15
16use crate::{LocalName, QualName};
17
18pub fn serialize<Wr, T>(writer: Wr, node: &T, opts: SerializeOpts) -> io::Result<()>
19where
20 Wr: Write,
21 T: Serialize,
22{
23 let mut ser = HtmlSerializer::new(writer, opts.clone());
24 node.serialize(&mut ser, opts.traversal_scope)
25}
26
27#[derive(Clone)]
28pub struct SerializeOpts {
29 pub scripting_enabled: bool,
31
32 pub traversal_scope: TraversalScope,
34
35 pub create_missing_parent: bool,
41}
42
43impl Default for SerializeOpts {
44 fn default() -> SerializeOpts {
45 SerializeOpts {
46 scripting_enabled: true,
47 traversal_scope: TraversalScope::ChildrenOnly(None),
48 create_missing_parent: false,
49 }
50 }
51}
52
53#[derive(Default)]
54struct ElemInfo {
55 html_name: Option<LocalName>,
56 ignore_children: bool,
57}
58
59pub struct HtmlSerializer<Wr: Write> {
60 pub writer: Wr,
61 opts: SerializeOpts,
62 stack: Vec<ElemInfo>,
63}
64
65fn tagname(name: &QualName) -> LocalName {
66 match name.ns {
67 ns!(html) | ns!(mathml) | ns!(svg) => (),
68 ref ns => {
69 warn!("node with weird namespace {ns:?}");
71 },
72 }
73
74 name.local.clone()
75}
76
77impl<Wr: Write> HtmlSerializer<Wr> {
78 pub fn new(writer: Wr, opts: SerializeOpts) -> Self {
79 let html_name = match opts.traversal_scope {
80 TraversalScope::IncludeNode | TraversalScope::ChildrenOnly(None) => None,
81 TraversalScope::ChildrenOnly(Some(ref n)) => Some(tagname(n)),
82 };
83 HtmlSerializer {
84 writer,
85 opts,
86 stack: vec![ElemInfo {
87 html_name,
88 ignore_children: false,
89 }],
90 }
91 }
92
93 fn parent(&mut self) -> &mut ElemInfo {
94 if self.stack.is_empty() {
95 if self.opts.create_missing_parent {
96 warn!("ElemInfo stack empty, creating new parent");
97 self.stack.push(Default::default());
98 } else {
99 panic!("no parent ElemInfo")
100 }
101 }
102 self.stack.last_mut().unwrap()
103 }
104
105 fn write_escaped(&mut self, text: &str, attr_mode: bool) -> io::Result<()> {
106 let maybe_quote = if attr_mode { b'"' } else { b'<' };
110 let find_next_escaped_character = |slice: &[u8]| {
111 let result = memchr3(maybe_quote, b'<', b'>', slice).unwrap_or(slice.len());
114 memchr2(b'&', 0xC2, &slice[..result]).unwrap_or(result)
115 };
116
117 let bytes = text.as_bytes();
118 let mut search_start = 0;
119 while search_start < text.len() {
120 let next_special = find_next_escaped_character(&bytes[search_start..]) + search_start;
121
122 self.writer.write_all(&bytes[search_start..next_special])?;
124
125 if next_special == bytes.len() {
127 break;
128 }
129
130 search_start = next_special + 1;
131 let replacement = match bytes[next_special] {
132 b'&' => "&",
133 b'"' => """,
134 b'<' => "<",
135 b'>' => ">",
136 0xC2 if bytes.get(next_special + 1) == Some(&0xA0) => {
137 search_start += 1;
138 " "
139 },
140 _ => {
141 continue;
143 },
144 };
145 self.writer.write_all(replacement.as_bytes())?;
146 }
147
148 Ok(())
149 }
150}
151
152impl<Wr: Write> Serializer for HtmlSerializer<Wr> {
153 fn start_elem<'a, AttrIter>(&mut self, name: QualName, attrs: AttrIter) -> io::Result<()>
154 where
155 AttrIter: Iterator<Item = AttrRef<'a>>,
156 {
157 let html_name = match name.ns {
158 ns!(html) => Some(name.local.clone()),
159 _ => None,
160 };
161
162 if self.parent().ignore_children {
163 self.stack.push(ElemInfo {
164 html_name,
165 ignore_children: true,
166 });
167 return Ok(());
168 }
169
170 self.writer.write_all(b"<")?;
171 self.writer.write_all(tagname(&name).as_bytes())?;
172 for (name, value) in attrs {
173 self.writer.write_all(b" ")?;
174
175 match name.ns {
176 ns!() => (),
177 ns!(xml) => self.writer.write_all(b"xml:")?,
178 ns!(xmlns) => {
179 if name.local != local_name!("xmlns") {
180 self.writer.write_all(b"xmlns:")?;
181 }
182 },
183 ns!(xlink) => self.writer.write_all(b"xlink:")?,
184 ref ns => {
185 warn!("attr with weird namespace {ns:?}");
187 self.writer.write_all(b"unknown_namespace:")?;
188 },
189 }
190
191 self.writer.write_all(name.local.as_bytes())?;
192 self.writer.write_all(b"=\"")?;
193 self.write_escaped(value, true)?;
194 self.writer.write_all(b"\"")?;
195 }
196 self.writer.write_all(b">")?;
197
198 let ignore_children = name.ns == ns!(html)
199 && matches!(
200 name.local,
201 local_name!("area")
202 | local_name!("base")
203 | local_name!("basefont")
204 | local_name!("bgsound")
205 | local_name!("br")
206 | local_name!("col")
207 | local_name!("embed")
208 | local_name!("frame")
209 | local_name!("hr")
210 | local_name!("img")
211 | local_name!("input")
212 | local_name!("keygen")
213 | local_name!("link")
214 | local_name!("meta")
215 | local_name!("param")
216 | local_name!("source")
217 | local_name!("track")
218 | local_name!("wbr")
219 );
220
221 self.stack.push(ElemInfo {
222 html_name,
223 ignore_children,
224 });
225
226 Ok(())
227 }
228
229 fn end_elem(&mut self, name: QualName) -> io::Result<()> {
230 let info = match self.stack.pop() {
231 Some(info) => info,
232 None if self.opts.create_missing_parent => {
233 warn!("missing ElemInfo, creating default.");
234 Default::default()
235 },
236 _ => panic!("no ElemInfo"),
237 };
238 if info.ignore_children {
239 return Ok(());
240 }
241
242 self.writer.write_all(b"</")?;
243 self.writer.write_all(tagname(&name).as_bytes())?;
244 self.writer.write_all(b">")
245 }
246
247 fn write_text(&mut self, text: &str) -> io::Result<()> {
248 let escape = match self.parent().html_name {
249 Some(local_name!("style"))
250 | Some(local_name!("script"))
251 | Some(local_name!("xmp"))
252 | Some(local_name!("iframe"))
253 | Some(local_name!("noembed"))
254 | Some(local_name!("noframes"))
255 | Some(local_name!("plaintext")) => false,
256
257 Some(local_name!("noscript")) => !self.opts.scripting_enabled,
258
259 _ => true,
260 };
261
262 if escape {
263 self.write_escaped(text, false)
264 } else {
265 self.writer.write_all(text.as_bytes())
266 }
267 }
268
269 fn write_comment(&mut self, text: &str) -> io::Result<()> {
270 self.writer.write_all(b"<!--")?;
271 self.writer.write_all(text.as_bytes())?;
272 self.writer.write_all(b"-->")
273 }
274
275 fn write_doctype(&mut self, name: &str) -> io::Result<()> {
276 self.writer.write_all(b"<!DOCTYPE ")?;
277 self.writer.write_all(name.as_bytes())?;
278 self.writer.write_all(b">")
279 }
280
281 fn write_processing_instruction(&mut self, target: &str, data: &str) -> io::Result<()> {
282 self.writer.write_all(b"<?")?;
283 self.writer.write_all(target.as_bytes())?;
284 self.writer.write_all(b" ")?;
285 self.writer.write_all(data.as_bytes())?;
286 self.writer.write_all(b">")
287 }
288}