1use std::rc::Rc;
4use rusty_xml_tree::{NodeId, NodeKind, XmlDoc};
5use std::cmp::Ordering;
6
7pub const XML_C14N_1_0: i32 = 0;
9pub const XML_C14N_EXCLUSIVE_1_0: i32 = 1;
10pub const XML_C14N_1_1: i32 = 2;
11
12const XML_NS: &str = "http://www.w3.org/XML/1998/namespace";
13
14#[doc(alias = "xmlC14NDocDumpMemory")]
16pub fn xml_c14n_doc_dump_memory(
17 doc: &XmlDoc,
18 exclusive: bool,
19 with_comments: bool,
20) -> Result<Vec<u8>, String> {
21 let mut out = String::new();
22 emit_node(doc, NodeId::DOCUMENT, exclusive, with_comments, &[], &[], &mut out)?;
23 Ok(out.into_bytes())
24}
25
26pub fn xml_c14n_1_0(doc: &XmlDoc) -> Result<Vec<u8>, String> {
28 xml_c14n_doc_dump_memory(doc, false, false)
29}
30
31pub fn xml_exc_c14n_1_0(doc: &XmlDoc) -> Result<Vec<u8>, String> {
33 xml_c14n_doc_dump_memory(doc, true, false)
34}
35
36enum Step {
46 Open(NodeId, Rc<Vec<(String, String)>>),
48 Inline(NodeId),
50 Close(String),
52}
53
54fn emit_node(
55 doc: &XmlDoc,
56 id: NodeId,
57 exclusive: bool,
58 with_comments: bool,
59 vis_prefixes: &[&str],
60 rendered: &[(String, String)],
61 out: &mut String,
62) -> Result<(), String> {
63 match doc.kind(id) {
64 NodeKind::Document | NodeKind::HtmlDocument => {
65 let mut kids = Vec::new();
66 let mut c = doc.first_child(id);
67 while let Some(x) = c {
68 kids.push(x);
69 c = doc.next_sibling(x);
70 }
71 let mut after_element = false;
72 let mut before_element = true;
73 let mut seen_pi_or_comment = false;
74 for kid in &kids {
75 match doc.kind(*kid) {
76 NodeKind::Element => {
77 before_element = false;
78 emit_node(doc, *kid, exclusive, with_comments, vis_prefixes, rendered, out)?;
79 after_element = true;
80 }
81 NodeKind::Pi => {
82 if after_element || seen_pi_or_comment {
83 out.push('\n');
84 }
85 emit_pi(doc, *kid, out);
86 if before_element {
87 out.push('\n');
88 }
89 seen_pi_or_comment = true;
90 }
91 NodeKind::Comment if with_comments => {
92 if after_element || seen_pi_or_comment {
93 out.push('\n');
94 }
95 emit_comment(doc, *kid, out);
96 if before_element {
97 out.push('\n');
98 }
99 seen_pi_or_comment = true;
100 }
101 _ => {}
102 }
103 }
104 }
105 NodeKind::Element => {
106 emit_element(doc, id, exclusive, with_comments, vis_prefixes, rendered, out)?
107 }
108 NodeKind::Text => out.push_str(&escape_text(doc.content(id))),
109 NodeKind::CData => out.push_str(&escape_text(doc.content(id))),
110 NodeKind::Pi => emit_pi(doc, id, out),
111 NodeKind::Comment if with_comments => emit_comment(doc, id, out),
112 _ => {}
113 }
114 Ok(())
115}
116
117fn emit_pi(doc: &XmlDoc, id: NodeId, out: &mut String) {
118 out.push_str("<?");
119 out.push_str(doc.name(id));
120 let data = doc.content(id);
121 if !data.is_empty() {
122 out.push(' ');
123 out.push_str(data);
124 }
125 out.push_str("?>");
126}
127
128fn emit_comment(doc: &XmlDoc, id: NodeId, out: &mut String) {
129 out.push_str("<!--");
130 out.push_str(doc.content(id));
131 out.push_str("-->");
132}
133
134fn emit_element(
135 doc: &XmlDoc,
136 id: NodeId,
137 exclusive: bool,
138 with_comments: bool,
139 vis_prefixes: &[&str],
140 rendered: &[(String, String)],
141 out: &mut String,
142) -> Result<(), String> {
143 let mut stack: Vec<Step> = vec![Step::Open(id, Rc::new(rendered.to_vec()))];
144 while let Some(step) = stack.pop() {
145 let (id, rendered) = match step {
146 Step::Close(qn) => {
147 out.push_str("</");
148 out.push_str(&qn);
149 out.push('>');
150 continue;
151 }
152 Step::Inline(id) => {
155 match doc.kind(id) {
156 NodeKind::Text | NodeKind::CData => {
157 out.push_str(&escape_text(doc.content(id)))
158 }
159 NodeKind::Pi => emit_pi(doc, id, out),
160 NodeKind::Comment if with_comments => emit_comment(doc, id, out),
161 _ => {}
162 }
163 continue;
164 }
165 Step::Open(id, rendered) => (id, rendered),
166 };
167
168 let qn = qname(doc.prefix(id), doc.name(id));
169 out.push('<');
170 out.push_str(&qn);
171
172 let mut ns_attrs = namespaces_to_emit(doc, id, exclusive, vis_prefixes, &rendered);
173 ns_attrs.sort_by(|a, b| a.0.cmp(&b.0));
174 for (pre, href) in &ns_attrs {
175 out.push(' ');
176 if pre.is_empty() {
177 out.push_str("xmlns");
178 } else {
179 out.push_str("xmlns:");
180 out.push_str(pre);
181 }
182 out.push_str("=\"");
183 out.push_str(&escape_attr(href));
184 out.push('"');
185 }
186
187 let child_rendered = if ns_attrs.is_empty() {
190 Rc::clone(&rendered)
191 } else {
192 let mut v: Vec<(String, String)> = rendered.as_ref().clone();
193 for (pre, href) in &ns_attrs {
194 v.retain(|(p, _)| p != pre);
195 v.push((pre.clone(), href.clone()));
196 }
197 Rc::new(v)
198 };
199
200 let mut attrs: Vec<(String, String, String)> = Vec::new();
201 let mut a = doc.first_attr(id);
202 while let Some(x) = a {
203 let ns = doc.ns_uri(x).unwrap_or("").to_string();
204 attrs.push((ns, doc.qname(x), doc.content(x).to_string()));
205 a = doc.next_sibling(x);
206 }
207 attrs.sort_by(|a, b| cmp_attr(&a.0, &a.1, &b.0, &b.1));
208 for (_ns, name, val) in attrs {
209 out.push(' ');
210 out.push_str(&name);
211 out.push_str("=\"");
212 out.push_str(&escape_attr(&val));
213 out.push('"');
214 }
215 out.push('>');
216
217 stack.push(Step::Close(qn));
219 let mut kids: Vec<NodeId> = Vec::new();
220 let mut c = doc.first_child(id);
221 while let Some(x) = c {
222 kids.push(x);
223 c = doc.next_sibling(x);
224 }
225 for x in kids.into_iter().rev() {
226 if doc.kind(x) == NodeKind::Element {
227 stack.push(Step::Open(x, Rc::clone(&child_rendered)));
228 } else {
229 stack.push(Step::Inline(x));
230 }
231 }
232 }
233 Ok(())
234}
235
236fn qname(prefix: Option<&str>, local: &str) -> String {
237 match prefix {
238 Some(p) if !p.is_empty() => format!("{p}:{local}"),
239 _ => local.to_string(),
240 }
241}
242
243fn in_scope_ns(doc: &XmlDoc, id: NodeId) -> Vec<(String, String)> {
244 let mut map: Vec<(String, String)> = Vec::new();
245 let mut cur = Some(id);
246 while let Some(n) = cur {
247 for (pre, href) in doc.ns_defs(n).iter().rev() {
248 let key = pre.clone().unwrap_or_default();
249 if !map.iter().any(|(k, _)| k == &key) {
250 map.push((key, href.clone()));
251 }
252 }
253 cur = doc.parent(n);
254 if cur == Some(NodeId::DOCUMENT) {
255 break;
256 }
257 }
258 map
259}
260
261fn namespaces_to_emit(
262 doc: &XmlDoc,
263 id: NodeId,
264 exclusive: bool,
265 vis_prefixes: &[&str],
266 rendered: &[(String, String)],
267) -> Vec<(String, String)> {
268 let mut map = in_scope_ns(doc, id);
269 map.retain(|(pre, href)| {
270 if pre == "xml" && href == XML_NS {
271 return false;
272 }
273 if exclusive {
274 if !visibly_used(doc, id, pre, vis_prefixes) {
275 return false;
276 }
277 if pre.is_empty() && href.is_empty() {
278 return rendered.iter().any(|(p, h)| p.is_empty() && !h.is_empty());
279 }
280 return !rendered.iter().any(|(p, h)| p == pre && h == href);
281 }
282 if rendered.iter().any(|(p, h)| p == pre && h == href) {
283 return false;
284 }
285 if pre.is_empty() && href.is_empty() {
286 return rendered.iter().any(|(p, h)| p.is_empty() && !h.is_empty());
287 }
288 true
289 });
290 map
291}
292
293fn visibly_used(doc: &XmlDoc, id: NodeId, pre: &str, extra: &[&str]) -> bool {
294 if extra.contains(&pre) {
295 return true;
296 }
297 if pre.is_empty() {
298 return doc.prefix(id).is_none();
299 }
300 if doc.prefix(id) == Some(pre) {
301 return true;
302 }
303 let mut a = doc.first_attr(id);
304 while let Some(x) = a {
305 if doc.prefix(x) == Some(pre) {
306 return true;
307 }
308 a = doc.next_sibling(x);
309 }
310 false
311}
312
313fn cmp_attr(ans: &str, an: &str, bns: &str, bn: &str) -> Ordering {
314 let au = ans;
315 let bu = bns;
316 match (au.is_empty(), bu.is_empty()) {
317 (true, false) => Ordering::Less,
318 (false, true) => Ordering::Greater,
319 _ => au.cmp(bu).then_with(|| {
320 let al = an.rsplit(':').next().unwrap_or(an);
321 let bl = bn.rsplit(':').next().unwrap_or(bn);
322 al.cmp(bl)
323 }),
324 }
325}
326
327fn escape_text(s: &str) -> String {
328 let mut out = String::new();
329 for c in s.chars() {
330 match c {
331 '&' => out.push_str("&"),
332 '<' => out.push_str("<"),
333 '>' => out.push_str(">"),
334 '\r' => out.push_str("
"),
335 c => out.push(c),
336 }
337 }
338 out
339}
340
341fn escape_attr(s: &str) -> String {
342 let mut out = String::new();
343 for c in s.chars() {
344 match c {
345 '&' => out.push_str("&"),
346 '<' => out.push_str("<"),
347 '"' => out.push_str("""),
348 '\t' => out.push_str("	"),
349 '\n' => out.push_str("
"),
350 '\r' => out.push_str("
"),
351 c => out.push(c),
352 }
353 }
354 out
355}