]>
Commit | Line | Data |
---|---|---|
3dfed10e XL |
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 | pub use markup5ever::serialize::{AttrRef, Serialize, Serializer, TraversalScope}; | |
11 | use std::io::{self, Write}; | |
12 | use crate::tree_builder::NamespaceMap; | |
13 | use crate::QualName; | |
14 | ||
15 | #[derive(Clone)] | |
16 | /// Struct for setting serializer options. | |
17 | pub struct SerializeOpts { | |
18 | /// Serialize the root node? Default: ChildrenOnly | |
19 | pub traversal_scope: TraversalScope, | |
20 | } | |
21 | ||
22 | impl Default for SerializeOpts { | |
23 | fn default() -> SerializeOpts { | |
24 | SerializeOpts { | |
25 | traversal_scope: TraversalScope::ChildrenOnly(None), | |
26 | } | |
27 | } | |
28 | } | |
29 | ||
30 | /// Method for serializing generic node to a given writer. | |
31 | pub fn serialize<Wr, T>(writer: Wr, node: &T, opts: SerializeOpts) -> io::Result<()> | |
32 | where | |
33 | Wr: Write, | |
34 | T: Serialize, | |
35 | { | |
36 | let mut ser = XmlSerializer::new(writer); | |
37 | node.serialize(&mut ser, opts.traversal_scope) | |
38 | } | |
39 | ||
40 | /// Struct used for serializing nodes into a text that other XML | |
41 | /// parses can read. | |
42 | /// | |
43 | /// Serializer contains a set of functions (start_elem, end_elem...) | |
44 | /// that make parsing nodes easier. | |
45 | pub struct XmlSerializer<Wr> { | |
46 | writer: Wr, | |
47 | namespace_stack: NamespaceMapStack, | |
48 | } | |
49 | ||
50 | #[derive(Debug)] | |
51 | struct NamespaceMapStack(Vec<NamespaceMap>); | |
52 | ||
53 | impl NamespaceMapStack { | |
54 | fn new() -> NamespaceMapStack { | |
55 | NamespaceMapStack(vec![]) | |
56 | } | |
57 | ||
58 | fn push(&mut self, namespace: NamespaceMap) { | |
59 | self.0.push(namespace); | |
60 | } | |
61 | ||
62 | fn pop(&mut self) { | |
63 | self.0.pop(); | |
64 | } | |
65 | } | |
66 | ||
67 | /// Writes given text into the Serializer, escaping it, | |
68 | /// depending on where the text is written inside the tag or attribute value. | |
69 | /// | |
70 | /// For example | |
71 | ///```text | |
72 | /// <tag>'&-quotes'</tag> becomes <tag>'&-quotes'</tag> | |
73 | /// <tag = "'&-quotes'"> becomes <tag = "'&-quotes'" | |
74 | ///``` | |
75 | fn write_to_buf_escaped<W: Write>(writer: &mut W, text: &str, attr_mode: bool) -> io::Result<()> { | |
76 | for c in text.chars() { | |
77 | match c { | |
78 | '&' => writer.write_all(b"&"), | |
79 | '\'' if attr_mode => writer.write_all(b"'"), | |
80 | '"' if attr_mode => writer.write_all(b"""), | |
81 | '<' if !attr_mode => writer.write_all(b"<"), | |
82 | '>' if !attr_mode => writer.write_all(b">"), | |
83 | c => writer.write_fmt(format_args!("{}", c)), | |
84 | }?; | |
85 | } | |
86 | Ok(()) | |
87 | } | |
88 | ||
89 | #[inline] | |
90 | fn write_qual_name<W: Write>(writer: &mut W, name: &QualName) -> io::Result<()> { | |
91 | if let Some(ref prefix) = name.prefix { | |
92 | writer.write_all(&prefix.as_bytes())?; | |
93 | writer.write_all(b":")?; | |
94 | writer.write_all(&*name.local.as_bytes())?; | |
95 | } else { | |
96 | writer.write_all(&*name.local.as_bytes())?; | |
97 | } | |
98 | ||
99 | Ok(()) | |
100 | } | |
101 | ||
102 | impl<Wr: Write> XmlSerializer<Wr> { | |
103 | /// Creates a new Serializier from a writer and given serialization options. | |
104 | pub fn new(writer: Wr) -> Self { | |
105 | XmlSerializer { | |
106 | writer: writer, | |
107 | namespace_stack: NamespaceMapStack::new(), | |
108 | } | |
109 | } | |
110 | ||
111 | #[inline(always)] | |
112 | fn qual_name(&mut self, name: &QualName) -> io::Result<()> { | |
113 | self.find_or_insert_ns(name); | |
114 | write_qual_name(&mut self.writer, name) | |
115 | } | |
116 | ||
117 | #[inline(always)] | |
118 | fn qual_attr_name(&mut self, name: &QualName) -> io::Result<()> { | |
119 | self.find_or_insert_ns(name); | |
120 | write_qual_name(&mut self.writer, name) | |
121 | } | |
122 | ||
123 | fn find_uri(&self, name: &QualName) -> bool { | |
124 | let mut found = false; | |
125 | for stack in self.namespace_stack.0.iter().rev() { | |
126 | if let Some(&Some(ref el)) = stack.get(&name.prefix) { | |
127 | found = *el == name.ns; | |
128 | break; | |
129 | } | |
130 | } | |
131 | found | |
132 | } | |
133 | ||
134 | fn find_or_insert_ns(&mut self, name: &QualName) { | |
135 | if name.prefix.is_some() || &*name.ns != "" { | |
136 | if !self.find_uri(name) { | |
137 | if let Some(last_ns) = self.namespace_stack.0.last_mut() { | |
138 | last_ns.insert(name); | |
139 | } | |
140 | } | |
141 | } | |
142 | } | |
143 | } | |
144 | ||
145 | impl<Wr: Write> Serializer for XmlSerializer<Wr> { | |
146 | /// Serializes given start element into text. Start element contains | |
147 | /// qualified name and an attributes iterator. | |
148 | fn start_elem<'a, AttrIter>(&mut self, name: QualName, attrs: AttrIter) -> io::Result<()> | |
149 | where | |
150 | AttrIter: Iterator<Item = AttrRef<'a>>, | |
151 | { | |
152 | self.namespace_stack.push(NamespaceMap::empty()); | |
153 | ||
154 | self.writer.write_all(b"<")?; | |
155 | self.qual_name(&name)?; | |
156 | if let Some(current_namespace) = self.namespace_stack.0.last() { | |
157 | for (prefix, url_opt) in current_namespace.get_scope_iter() { | |
158 | self.writer.write_all(b" xmlns")?; | |
159 | if let &Some(ref p) = prefix { | |
160 | self.writer.write_all(b":")?; | |
161 | self.writer.write_all(&*p.as_bytes())?; | |
162 | } | |
163 | ||
164 | self.writer.write_all(b"=\"")?; | |
165 | let url = if let &Some(ref a) = url_opt { | |
166 | a.as_bytes() | |
167 | } else { | |
168 | b"" | |
169 | }; | |
170 | self.writer.write_all(url)?; | |
171 | self.writer.write_all(b"\"")?; | |
172 | } | |
173 | } | |
174 | for (name, value) in attrs { | |
175 | self.writer.write_all(b" ")?; | |
176 | self.qual_attr_name(&name)?; | |
177 | self.writer.write_all(b"=\"")?; | |
178 | write_to_buf_escaped(&mut self.writer, value, true)?; | |
179 | self.writer.write_all(b"\"")?; | |
180 | } | |
181 | self.writer.write_all(b">")?; | |
182 | Ok(()) | |
183 | } | |
184 | ||
185 | /// Serializes given end element into text. | |
186 | fn end_elem(&mut self, name: QualName) -> io::Result<()> { | |
187 | self.namespace_stack.pop(); | |
188 | self.writer.write_all(b"</")?; | |
189 | self.qual_name(&name)?; | |
190 | self.writer.write_all(b">") | |
191 | } | |
192 | ||
193 | /// Serializes comment into text. | |
194 | fn write_comment(&mut self, text: &str) -> io::Result<()> { | |
195 | self.writer.write_all(b"<!--")?; | |
196 | self.writer.write_all(text.as_bytes())?; | |
197 | self.writer.write_all(b"-->") | |
198 | } | |
199 | ||
200 | /// Serializes given doctype | |
201 | fn write_doctype(&mut self, name: &str) -> io::Result<()> { | |
202 | self.writer.write_all(b"<!DOCTYPE ")?; | |
203 | self.writer.write_all(name.as_bytes())?; | |
204 | self.writer.write_all(b">") | |
205 | } | |
206 | ||
207 | /// Serializes text for a node or an attributes. | |
208 | fn write_text(&mut self, text: &str) -> io::Result<()> { | |
209 | write_to_buf_escaped(&mut self.writer, text, false) | |
210 | } | |
211 | ||
212 | /// Serializes given processing instruction. | |
213 | fn write_processing_instruction(&mut self, target: &str, data: &str) -> io::Result<()> { | |
214 | self.writer.write_all(b"<?")?; | |
215 | self.writer.write_all(target.as_bytes())?; | |
216 | self.writer.write_all(b" ")?; | |
217 | self.writer.write_all(data.as_bytes())?; | |
218 | self.writer.write_all(b"?>") | |
219 | } | |
220 | } |