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