1#![forbid(unsafe_code)]
2
3mod decoders;
4mod errors;
5mod frames;
6
7use crate::frames::AttributesWriter;
8pub use errors::XmlToJsonError;
9use quick_xml::Reader;
10use quick_xml::events::Event;
11use std::io::{BufRead, BufReader, Read, Write};
12
13const MB: usize = 1024 * 1024;
14
15pub fn xml_to_json<R: Read, W: Write>(reader: R, out: W) -> Result<(), XmlToJsonError> {
56 xml_to_json_from_bufread(BufReader::new(reader), out)
57}
58
59macro_rules! convert_events {
65 ($xml:ident, $writer:ident, $next_event:expr) => {{
66 let mut stack: Vec<frames::Element> = Vec::with_capacity(16);
67 let mut spare_text_buf = String::new();
68
69 loop {
70 match ($next_event, stack.last_mut()) {
71 (Event::Start(e), None) => {
75 let text_buf = std::mem::take(&mut spare_text_buf);
76 let mut frame = frames::Element::new_and_open(&e, &mut $writer, text_buf)?;
77 frame.process_element_attributes(&e, &mut $writer)?;
78
79 stack.push(frame);
80 }
81
82 (Event::Empty(e), None) => {
84 let mut frame = frames::EmptyNode::new_and_open(&e, &mut $writer)?;
85 frame.process_element_attributes(&e, &mut $writer)?;
86 frame.close(&mut $writer)?;
87
88 $writer.flush()?;
89 return Ok(());
90 }
91
92 (Event::Start(e), Some(parent)) => {
96 parent.begin_child(&mut $writer)?;
97
98 let text_buf = std::mem::take(&mut spare_text_buf);
99 let mut frame = frames::Element::new_and_open(&e, &mut $writer, text_buf)?;
100 frame.process_element_attributes(&e, &mut $writer)?;
101
102 stack.push(frame);
103 }
104
105 (Event::Empty(e), Some(parent)) => {
107 parent.begin_child(&mut $writer)?;
108
109 let mut frame = frames::EmptyNode::new_and_open(&e, &mut $writer)?;
110 frame.process_element_attributes(&e, &mut $writer)?;
111 frame.close(&mut $writer)?;
112 }
113
114 (Event::Text(t), Some(frame)) => {
120 if frame.has_text()
121 || !t.iter().all(|b| matches!(b, b' ' | b'\t' | b'\r' | b'\n'))
122 {
123 let text = decoders::decode_bytes(&t)?;
124 frame.push_text(text);
125 }
126 }
127
128 (Event::GeneralRef(r), Some(frame)) => {
133 if let Some(ch) = r.resolve_char_ref()? {
134 frame.push_char(ch);
135 } else {
136 let name = decoders::decode_bytes(&r)?;
137 match quick_xml::escape::resolve_predefined_entity(name) {
138 Some(resolved) => frame.push_resolved(resolved),
139 None => frame.push_unresolved_ref(name),
140 }
141 }
142 }
143
144 (Event::End(_), _) => {
146 if let Some(mut frame) = stack.pop() {
147 frame.close(&mut $writer)?;
148 spare_text_buf = frame.take_text_buf();
149
150 if stack.is_empty() {
152 $writer.flush()?;
153 return Ok(());
154 }
155 }
156 }
157
158 (Event::Eof, _) => break,
159 _ => {}
160 }
161 }
162
163 Err(XmlToJsonError::InvalidXML)
164 }};
165}
166
167pub fn xml_to_json_from_bufread<R: BufRead, W: Write>(
182 reader: R,
183 out: W,
184) -> Result<(), XmlToJsonError> {
185 let mut writer = std::io::BufWriter::with_capacity(MB * 2, out);
186 let mut xml = Reader::from_reader(reader);
187 let mut buf = Vec::with_capacity(256);
188
189 convert_events!(xml, writer, {
190 buf.clear();
191 xml.read_event_into(&mut buf)?
192 })
193}
194
195pub fn xml_to_json_from_slice<W: Write>(input: &[u8], out: W) -> Result<(), XmlToJsonError> {
219 let mut writer = std::io::BufWriter::with_capacity(MB * 2, out);
220 let mut xml = Reader::from_reader(input);
221
222 convert_events!(xml, writer, xml.read_event()?)
223}
224
225#[cfg(test)]
226mod tests {
227
228 #[test]
229 fn test_nested_structure() {
230 let xml = r#"
231 <root>
232 <child1 attr1="value1">
233 <subchild>Text 1</subchild>
234 <subchild>Text 2</subchild>
235 <subchild>Text 3</subchild>
236 </child1>
237 <child2 attr2="value2" />
238 <child1 attr2="value2" attr3="value3" attr1="value1">
239 <subchild>Text 2</subchild>
240 <subchild>Text 1</subchild>
241 <subchild>Text 3</subchild>
242 </child1>
243 </root>
244 "#;
245
246 let expected_json = serde_json::json!({
247 "root": {
248 "#c": [
249 {
250 "child1": {
251 "@attr1": "value1",
252 "#c": [
253 {
254 "subchild": {
255 "#t": "Text 1"
256 }
257 },
258 {
259 "subchild": {
260 "#t": "Text 2"
261 }
262 },
263 {
264 "subchild": {
265 "#t": "Text 3"
266 }
267 },
268 ]
269 }
270 },
271 {
272 "child2": {
273 "@attr2": "value2"
274 }
275 },
276 {
277 "child1": {
278 "@attr3": "value3",
279 "@attr2": "value2",
280 "@attr1": "value1",
281 "#c": [
282 {
283 "subchild": {
284 "#t": "Text 2"
285 }
286 },
287 {
288 "subchild": {
289 "#t": "Text 1"
290 }
291 },
292 {
293 "subchild": {
294 "#t": "Text 3"
295 }
296 },
297 ]
298 }
299 },
300 ]
301 }
302 });
303
304 assert_eq!(expected_json, convert_xml_to_json(xml));
305 }
306
307 #[test]
308 fn test_basic_xml_to_json() {
309 let xml = r#"<users count="3">
310 <user age="40">Jane Doe</user>
311 <user age="42">John Doe</user>
312 <user age="12">Jim Doe</user>
313</users>"#;
314
315 let expected_json = serde_json::json!({
316 "users": {
317 "@count": "3",
318 "#c": [
319 {
320 "user": {
321 "@age": "40",
322 "#t": "Jane Doe"
323 }
324 },
325 {
326 "user": {
327 "@age": "42",
328 "#t": "John Doe"
329 }
330 },
331 {
332 "user": {
333 "@age": "12",
334 "#t": "Jim Doe"
335 }
336 }
337 ]
338 }
339 });
340
341 assert_eq!(expected_json, convert_xml_to_json(xml));
342 }
343
344 #[test]
345 fn test_single_element_with_text() {
346 let xml = "<name>John Doe</name>";
347 let expected_json = serde_json::json!({
348 "name": {
349 "#t": "John Doe"
350 }
351 });
352
353 assert_eq!(expected_json, convert_xml_to_json(xml));
354 }
355
356 #[test]
357 fn test_element_with_attributes_only() {
358 let xml = r#"<div class="container" id="main"></div>"#;
359 let expected_json = serde_json::json!({
360 "div": {
361 "@class": "container",
362 "@id": "main"
363 }
364 });
365
366 assert_eq!(expected_json, convert_xml_to_json(xml));
367 }
368
369 #[test]
370 fn test_nested_elements() {
371 let xml = r#"<root><parent id="1"><child>Value</child></parent></root>"#;
372 let expected_json = serde_json::json!({
373 "root": {
374 "#c": [
375 {
376 "parent": {
377 "@id": "1",
378 "#c": [
379 { "child": { "#t": "Value" } }
380 ]
381 }
382 }
383 ]
384 }
385 });
386
387 assert_eq!(expected_json, convert_xml_to_json(xml));
388 }
389
390 #[test]
391 fn test_mixed_content_trailing_text_is_kept_and_does_not_leak() {
392 let xml = "<r><a>pre <b/>post</a><c>text</c></r>";
393 let expected_json = serde_json::json!({
394 "r": {
395 "#c": [
396 {
397 "a": {
398 "#c": [ { "b": {} } ],
399 "#t": "pre post"
400 }
401 },
402 {
403 "c": { "#t": "text" }
404 }
405 ]
406 }
407 });
408
409 assert_eq!(expected_json, convert_xml_to_json(xml));
410 }
411
412 #[test]
413 fn test_text_after_children_only() {
414 let xml = "<a><b/>tail</a>";
415 let expected_json = serde_json::json!({
416 "a": {
417 "#c": [ { "b": {} } ],
418 "#t": "tail"
419 }
420 });
421
422 assert_eq!(expected_json, convert_xml_to_json(xml));
423 }
424
425 #[test]
426 fn test_predefined_entities_in_text() {
427 let xml = "<a>x & y <tag> "q" 's'</a>";
428 let expected_json = serde_json::json!({
429 "a": { "#t": r#"x & y <tag> "q" 's'"# }
430 });
431
432 assert_eq!(expected_json, convert_xml_to_json(xml));
433 }
434
435 #[test]
436 fn test_entities_do_not_introduce_spaces() {
437 let xml = "<a>a&b</a>";
438 let expected_json = serde_json::json!({
439 "a": { "#t": "a&b" }
440 });
441
442 assert_eq!(expected_json, convert_xml_to_json(xml));
443 }
444
445 #[test]
446 fn test_numeric_char_refs_in_text() {
447 let xml = "<a>Hey</a>";
448 let expected_json = serde_json::json!({
449 "a": { "#t": "Hey" }
450 });
451
452 assert_eq!(expected_json, convert_xml_to_json(xml));
453 }
454
455 #[test]
456 fn test_char_ref_producing_json_escapable_char() {
457 let xml = "<a>x y</a>";
458 let expected_json = serde_json::json!({
459 "a": { "#t": "x\ny" }
460 });
461
462 assert_eq!(expected_json, convert_xml_to_json(xml));
463 }
464
465 #[test]
466 fn test_unknown_named_entity_passes_through_in_text() {
467 let xml = "<a>x ü y</a>";
468 let expected_json = serde_json::json!({
469 "a": { "#t": "x ü y" }
470 });
471
472 assert_eq!(expected_json, convert_xml_to_json(xml));
473 }
474
475 #[test]
476 fn test_invalid_char_ref_in_text_errors() {
477 assert!(super::xml_to_json(b"<a>&#xZZ;</a>".as_slice(), Vec::new()).is_err());
478 assert!(super::xml_to_json(b"<a>&#+65;</a>".as_slice(), Vec::new()).is_err());
479 }
480
481 #[test]
482 fn test_entities_in_attribute_values() {
483 let xml = r#"<a m="5 < 6" q=""q"" n="ABC" u="x ü y"/>"#;
484 let expected_json = serde_json::json!({
485 "a": {
486 "@m": "5 < 6",
487 "@q": "\"q\"",
488 "@n": "ABC",
489 "@u": "x ü y"
490 }
491 });
492
493 assert_eq!(expected_json, convert_xml_to_json(xml));
494 }
495
496 #[test]
497 fn test_invalid_char_ref_in_attribute_errors() {
498 assert!(super::xml_to_json(br#"<a t="&#xZZ;"/>"#.as_slice(), Vec::new()).is_err());
499 assert!(super::xml_to_json(br#"<a t="&#+65;"/>"#.as_slice(), Vec::new()).is_err());
500 }
501
502 #[test]
503 fn test_bare_ampersand_in_attribute_passes_through() {
504 let xml = r#"<a href="q?x=1&y=2"/>"#;
505 let expected_json = serde_json::json!({
506 "a": { "@href": "q?x=1&y=2" }
507 });
508
509 assert_eq!(expected_json, convert_xml_to_json(xml));
510 }
511
512 #[test]
513 fn test_interior_whitespace_preserved_in_text() {
514 let xml = "<a>line1\nline2</a>";
515 let expected_json = serde_json::json!({
516 "a": { "#t": "line1\nline2" }
517 });
518
519 assert_eq!(expected_json, convert_xml_to_json(xml));
520 }
521
522 #[test]
523 fn test_edge_whitespace_trimmed_from_text() {
524 let xml = "<a>\n padded \n</a>";
525 let expected_json = serde_json::json!({
526 "a": { "#t": "padded" }
527 });
528
529 assert_eq!(expected_json, convert_xml_to_json(xml));
530 }
531
532 #[test]
533 fn test_whitespace_only_text_produces_no_text_node() {
534 let xml = "<a> </a>";
535 let expected_json = serde_json::json!({ "a": {} });
536
537 assert_eq!(expected_json, convert_xml_to_json(xml));
538 }
539
540 #[test]
541 fn test_empty_xml() {
542 assert!(super::xml_to_json(b"".as_slice(), Vec::new()).is_err());
543 }
544
545 #[test]
546 fn test_malformed_xml() {
547 assert!(super::xml_to_json(b"<root><unclosed>".as_slice(), Vec::new()).is_err());
548 assert!(super::xml_to_json_from_slice(b"<root><unclosed>", Vec::new()).is_err());
549 assert!(super::xml_to_json_from_slice(b"", Vec::new()).is_err());
550 }
551
552 #[test]
553 fn test_empty_nodes() {
554 let xml = "<main><br /></main>";
555 let expected_json = serde_json::json!({
556 "main": {
557 "#c": [
558 { "br": {}}
559 ]
560 }
561 });
562
563 assert_eq!(expected_json, convert_xml_to_json(xml));
564 }
565
566 #[test]
567 fn test_empty_node_at_root() {
568 let xml = "<br />";
569 let expected_json = serde_json::json!({ "br": {} });
570
571 assert_eq!(expected_json, convert_xml_to_json(xml));
572 }
573
574 #[test]
575 fn test_empty_node_at_root_with_attributes() {
576 let xml = r#"<br id="5" />"#;
577 let expected_json = serde_json::json!({ "br": { "@id": "5" } });
578
579 assert_eq!(expected_json, convert_xml_to_json(xml));
580 }
581
582 fn convert_xml_to_json(xml: &str) -> serde_json::Value {
583 let mut output = Vec::new();
584 super::xml_to_json(xml.as_bytes(), &mut output).unwrap();
585
586 let mut slice_output = Vec::new();
587 super::xml_to_json_from_slice(xml.as_bytes(), &mut slice_output).unwrap();
588 assert_eq!(
589 output, slice_output,
590 "slice and bufread APIs must produce identical output"
591 );
592
593 serde_json::from_slice(&output).unwrap()
594 }
595}