Coverage Report

Created: 2025-10-10 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/html5ever-0.27.0/src/serialize/mod.rs
Line
Count
Source
1
// Copyright 2014-2017 The html5ever Project Developers. See the
2
// COPYRIGHT file at the top-level directory of this distribution.
3
//
4
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7
// option. This file may not be copied, modified, or distributed
8
// except according to those terms.
9
10
use log::warn;
11
pub use markup5ever::serialize::{AttrRef, Serialize, Serializer, TraversalScope};
12
use markup5ever::{local_name, namespace_url, ns};
13
use std::io::{self, Write};
14
15
use crate::{LocalName, QualName};
16
17
0
pub fn serialize<Wr, T>(writer: Wr, node: &T, opts: SerializeOpts) -> io::Result<()>
18
0
where
19
0
    Wr: Write,
20
0
    T: Serialize,
21
{
22
0
    let mut ser = HtmlSerializer::new(writer, opts.clone());
23
0
    node.serialize(&mut ser, opts.traversal_scope)
24
0
}
Unexecuted instantiation: html5ever::serialize::serialize::<&mut alloc::vec::Vec<u8>, ammonia::rcdom::SerializableHandle>
Unexecuted instantiation: html5ever::serialize::serialize::<_, _>
25
26
#[derive(Clone)]
27
pub struct SerializeOpts {
28
    /// Is scripting enabled? Default: true
29
    pub scripting_enabled: bool,
30
31
    /// Serialize the root node? Default: ChildrenOnly
32
    pub traversal_scope: TraversalScope,
33
34
    /// If the serializer is asked to serialize an invalid tree, the default
35
    /// behavior is to panic in the event that an `end_elem` is created without a
36
    /// matching `start_elem`. Setting this to true will prevent those panics by
37
    /// creating a default parent on the element stack. No extra start elem will
38
    /// actually be written. Default: false
39
    pub create_missing_parent: bool,
40
}
41
42
impl Default for SerializeOpts {
43
0
    fn default() -> SerializeOpts {
44
0
        SerializeOpts {
45
0
            scripting_enabled: true,
46
0
            traversal_scope: TraversalScope::ChildrenOnly(None),
47
0
            create_missing_parent: false,
48
0
        }
49
0
    }
50
}
51
52
#[derive(Default)]
53
struct ElemInfo {
54
    html_name: Option<LocalName>,
55
    ignore_children: bool,
56
}
57
58
pub struct HtmlSerializer<Wr: Write> {
59
    pub writer: Wr,
60
    opts: SerializeOpts,
61
    stack: Vec<ElemInfo>,
62
}
63
64
0
fn tagname(name: &QualName) -> LocalName {
65
0
    match name.ns {
66
0
        ns!(html) | ns!(mathml) | ns!(svg) => (),
67
0
        ref ns => {
68
            // FIXME(#122)
69
0
            warn!("node with weird namespace {:?}", ns);
70
        },
71
    }
72
73
0
    name.local.clone()
74
0
}
75
76
impl<Wr: Write> HtmlSerializer<Wr> {
77
0
    pub fn new(writer: Wr, opts: SerializeOpts) -> Self {
78
0
        let html_name = match opts.traversal_scope {
79
0
            TraversalScope::IncludeNode | TraversalScope::ChildrenOnly(None) => None,
80
0
            TraversalScope::ChildrenOnly(Some(ref n)) => Some(tagname(n)),
81
        };
82
0
        HtmlSerializer {
83
0
            writer,
84
0
            opts,
85
0
            stack: vec![ElemInfo {
86
0
                html_name,
87
0
                ignore_children: false,
88
0
            }],
89
0
        }
90
0
    }
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<&mut alloc::vec::Vec<u8>>>::new
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<_>>::new
91
92
0
    fn parent(&mut self) -> &mut ElemInfo {
93
0
        if self.stack.is_empty() {
94
0
            if self.opts.create_missing_parent {
95
0
                warn!("ElemInfo stack empty, creating new parent");
96
0
                self.stack.push(Default::default());
97
            } else {
98
0
                panic!("no parent ElemInfo")
99
            }
100
0
        }
101
0
        self.stack.last_mut().unwrap()
102
0
    }
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<&mut alloc::vec::Vec<u8>>>::parent
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<_>>::parent
103
104
0
    fn write_escaped(&mut self, text: &str, attr_mode: bool) -> io::Result<()> {
105
0
        for c in text.chars() {
106
0
            match c {
107
0
                '&' => self.writer.write_all(b"&amp;"),
108
0
                '\u{00A0}' => self.writer.write_all(b"&nbsp;"),
109
0
                '"' if attr_mode => self.writer.write_all(b"&quot;"),
110
0
                '<' if !attr_mode => self.writer.write_all(b"&lt;"),
111
0
                '>' if !attr_mode => self.writer.write_all(b"&gt;"),
112
0
                c => self.writer.write_fmt(format_args!("{}", c)),
113
0
            }?;
114
        }
115
0
        Ok(())
116
0
    }
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<&mut alloc::vec::Vec<u8>>>::write_escaped
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<_>>::write_escaped
117
}
118
119
impl<Wr: Write> Serializer for HtmlSerializer<Wr> {
120
0
    fn start_elem<'a, AttrIter>(&mut self, name: QualName, attrs: AttrIter) -> io::Result<()>
121
0
    where
122
0
        AttrIter: Iterator<Item = AttrRef<'a>>,
123
    {
124
0
        let html_name = match name.ns {
125
0
            ns!(html) => Some(name.local.clone()),
126
0
            _ => None,
127
        };
128
129
0
        if self.parent().ignore_children {
130
0
            self.stack.push(ElemInfo {
131
0
                html_name,
132
0
                ignore_children: true,
133
0
            });
134
0
            return Ok(());
135
0
        }
136
137
0
        self.writer.write_all(b"<")?;
138
0
        self.writer.write_all(tagname(&name).as_bytes())?;
139
0
        for (name, value) in attrs {
140
0
            self.writer.write_all(b" ")?;
141
142
0
            match name.ns {
143
0
                ns!() => (),
144
0
                ns!(xml) => self.writer.write_all(b"xml:")?,
145
                ns!(xmlns) => {
146
0
                    if name.local != local_name!("xmlns") {
147
0
                        self.writer.write_all(b"xmlns:")?;
148
0
                    }
149
                },
150
0
                ns!(xlink) => self.writer.write_all(b"xlink:")?,
151
0
                ref ns => {
152
                    // FIXME(#122)
153
0
                    warn!("attr with weird namespace {:?}", ns);
154
0
                    self.writer.write_all(b"unknown_namespace:")?;
155
                },
156
            }
157
158
0
            self.writer.write_all(name.local.as_bytes())?;
159
0
            self.writer.write_all(b"=\"")?;
160
0
            self.write_escaped(value, true)?;
161
0
            self.writer.write_all(b"\"")?;
162
        }
163
0
        self.writer.write_all(b">")?;
164
165
0
        let ignore_children = name.ns == ns!(html)
166
0
            && matches!(
167
0
                name.local,
168
                local_name!("area")
169
                    | local_name!("base")
170
                    | local_name!("basefont")
171
                    | local_name!("bgsound")
172
                    | local_name!("br")
173
                    | local_name!("col")
174
                    | local_name!("embed")
175
                    | local_name!("frame")
176
                    | local_name!("hr")
177
                    | local_name!("img")
178
                    | local_name!("input")
179
                    | local_name!("keygen")
180
                    | local_name!("link")
181
                    | local_name!("meta")
182
                    | local_name!("param")
183
                    | local_name!("source")
184
                    | local_name!("track")
185
                    | local_name!("wbr")
186
            );
187
188
0
        self.stack.push(ElemInfo {
189
0
            html_name,
190
0
            ignore_children,
191
0
        });
192
193
0
        Ok(())
194
0
    }
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<&mut alloc::vec::Vec<u8>> as markup5ever::serialize::Serializer>::start_elem::<core::iter::adapters::map::Map<core::slice::iter::Iter<markup5ever::interface::Attribute>, <ammonia::rcdom::SerializableHandle as markup5ever::serialize::Serialize>::serialize<html5ever::serialize::HtmlSerializer<&mut alloc::vec::Vec<u8>>>::{closure#1}>>
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<_> as markup5ever::serialize::Serializer>::start_elem::<_>
195
196
0
    fn end_elem(&mut self, name: QualName) -> io::Result<()> {
197
0
        let info = match self.stack.pop() {
198
0
            Some(info) => info,
199
0
            None if self.opts.create_missing_parent => {
200
0
                warn!("missing ElemInfo, creating default.");
201
0
                Default::default()
202
            },
203
0
            _ => panic!("no ElemInfo"),
204
        };
205
0
        if info.ignore_children {
206
0
            return Ok(());
207
0
        }
208
209
0
        self.writer.write_all(b"</")?;
210
0
        self.writer.write_all(tagname(&name).as_bytes())?;
211
0
        self.writer.write_all(b">")
212
0
    }
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<&mut alloc::vec::Vec<u8>> as markup5ever::serialize::Serializer>::end_elem
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<_> as markup5ever::serialize::Serializer>::end_elem
213
214
0
    fn write_text(&mut self, text: &str) -> io::Result<()> {
215
0
        let escape = match self.parent().html_name {
216
            Some(local_name!("style"))
217
            | Some(local_name!("script"))
218
            | Some(local_name!("xmp"))
219
            | Some(local_name!("iframe"))
220
            | Some(local_name!("noembed"))
221
            | Some(local_name!("noframes"))
222
0
            | Some(local_name!("plaintext")) => false,
223
224
0
            Some(local_name!("noscript")) => !self.opts.scripting_enabled,
225
226
0
            _ => true,
227
        };
228
229
0
        if escape {
230
0
            self.write_escaped(text, false)
231
        } else {
232
0
            self.writer.write_all(text.as_bytes())
233
        }
234
0
    }
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<&mut alloc::vec::Vec<u8>> as markup5ever::serialize::Serializer>::write_text
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<_> as markup5ever::serialize::Serializer>::write_text
235
236
0
    fn write_comment(&mut self, text: &str) -> io::Result<()> {
237
0
        self.writer.write_all(b"<!--")?;
238
0
        self.writer.write_all(text.as_bytes())?;
239
0
        self.writer.write_all(b"-->")
240
0
    }
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<&mut alloc::vec::Vec<u8>> as markup5ever::serialize::Serializer>::write_comment
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<_> as markup5ever::serialize::Serializer>::write_comment
241
242
0
    fn write_doctype(&mut self, name: &str) -> io::Result<()> {
243
0
        self.writer.write_all(b"<!DOCTYPE ")?;
244
0
        self.writer.write_all(name.as_bytes())?;
245
0
        self.writer.write_all(b">")
246
0
    }
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<&mut alloc::vec::Vec<u8>> as markup5ever::serialize::Serializer>::write_doctype
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<_> as markup5ever::serialize::Serializer>::write_doctype
247
248
0
    fn write_processing_instruction(&mut self, target: &str, data: &str) -> io::Result<()> {
249
0
        self.writer.write_all(b"<?")?;
250
0
        self.writer.write_all(target.as_bytes())?;
251
0
        self.writer.write_all(b" ")?;
252
0
        self.writer.write_all(data.as_bytes())?;
253
0
        self.writer.write_all(b">")
254
0
    }
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<&mut alloc::vec::Vec<u8>> as markup5ever::serialize::Serializer>::write_processing_instruction
Unexecuted instantiation: <html5ever::serialize::HtmlSerializer<_> as markup5ever::serialize::Serializer>::write_processing_instruction
255
}