serval_scripted_dom/
serialize.rs1use std::io;
11
12use html5ever::serialize::{SerializeOpts, serialize as html5ever_serialize};
13use layout_dom_api::NodeKind;
14use markup5ever::serialize::{Serialize, Serializer, TraversalScope};
15
16use crate::{NodeId, ScriptedDom};
17
18struct SerializableNode<'a> {
20 dom: &'a ScriptedDom,
21 id: NodeId,
22}
23
24impl SerializableNode<'_> {
25 fn child(&self, id: NodeId) -> SerializableNode<'_> {
26 SerializableNode { dom: self.dom, id }
27 }
28
29 fn serialize_children<S: Serializer>(&self, serializer: &mut S) -> io::Result<()> {
32 for &child in &self.dom.node(self.id).children {
33 self.child(child)
34 .serialize(serializer, TraversalScope::IncludeNode)?;
35 }
36 Ok(())
37 }
38}
39
40impl Serialize for SerializableNode<'_> {
41 fn serialize<S: Serializer>(
42 &self,
43 serializer: &mut S,
44 traversal_scope: TraversalScope,
45 ) -> io::Result<()> {
46 let node = self.dom.node(self.id);
47 match traversal_scope {
48 TraversalScope::IncludeNode => match node.kind {
49 NodeKind::Element => match &node.name {
50 Some(name) => {
51 serializer.start_elem(
52 name.clone(),
53 node.attrs.iter().map(|(n, v)| (n, v.as_str())),
54 )?;
55 self.serialize_children(serializer)?;
56 serializer.end_elem(name.clone())?;
57 },
58 None => self.serialize_children(serializer)?,
61 },
62 NodeKind::Text => {
63 if let Some(text) = &node.text {
64 serializer.write_text(text)?;
65 }
66 },
67 NodeKind::Comment => {
68 if let Some(text) = &node.text {
69 serializer.write_comment(text)?;
70 }
71 },
72 NodeKind::Doctype => {
73 serializer.write_doctype(node.text.as_deref().unwrap_or("html"))?;
74 },
75 NodeKind::Document | NodeKind::DocumentFragment => {
76 self.serialize_children(serializer)?;
77 },
78 NodeKind::ProcessingInstruction => {},
82 },
83 TraversalScope::ChildrenOnly(_) => self.serialize_children(serializer)?,
84 }
85 Ok(())
86 }
87}
88
89impl ScriptedDom {
90 pub fn outer_html(&self, node: NodeId) -> String {
95 self.serialize_scope(node, TraversalScope::IncludeNode)
96 }
97
98 pub fn inner_html(&self, node: NodeId) -> String {
100 self.serialize_scope(node, TraversalScope::ChildrenOnly(None))
101 }
102
103 fn serialize_scope(&self, node: NodeId, traversal_scope: TraversalScope) -> String {
104 let mut buf = Vec::new();
105 let opts = SerializeOpts {
106 traversal_scope,
107 ..SerializeOpts::default()
108 };
109 html5ever_serialize(
111 &mut buf,
112 &SerializableNode {
113 dom: self,
114 id: node,
115 },
116 opts,
117 )
118 .expect("serializing the scripted DOM to a Vec is infallible");
119 String::from_utf8(buf).expect("html5ever emits valid UTF-8")
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use html5ever::{local_name, ns};
127 use layout_dom_api::{LayoutDomMut, QualName};
128
129 fn html(local: markup5ever::LocalName) -> QualName {
130 QualName::new(None, ns!(html), local)
131 }
132
133 #[test]
134 fn outer_html_emits_tag_attrs_and_escaped_text() {
135 let mut dom = ScriptedDom::new();
136 let div = dom.create_element(html(local_name!("div")));
137 dom.set_attribute(div, QualName::new(None, ns!(), local_name!("class")), "x");
138 let text = dom.create_text("hi & bye");
139 dom.append_child(div, text);
140 assert_eq!(dom.outer_html(div), r#"<div class="x">hi & bye</div>"#);
142 }
143
144 #[test]
145 fn inner_html_skips_the_node_itself() {
146 let mut dom = ScriptedDom::new();
147 let div = dom.create_element(html(local_name!("div")));
148 let span = dom.create_element(html(local_name!("span")));
149 dom.append_child(div, span);
150 assert_eq!(dom.inner_html(div), "<span></span>");
151 }
152
153 #[test]
154 fn void_elements_get_no_close_tag() {
155 let mut dom = ScriptedDom::new();
156 let br = dom.create_element(html(local_name!("br")));
157 assert_eq!(dom.outer_html(br), "<br>");
158 }
159}