Skip to main content

quick_xml_to_json/
lib.rs

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
15/// Convert XML to JSON
16///
17/// # Example Usage
18///
19/// ```
20/// use quick_xml_to_json::xml_to_json;
21///
22/// let xml = r#"<root><parent id="1"><child>Value</child></parent></root>"#;
23/// let expected_json = serde_json::json!({
24///     "root": {
25///         "#c": [
26///             {
27///                 "parent": {
28///                     "@id": "1",
29///                     "#c": [
30///                         { "child": { "#t": "Value" } }
31///                     ]
32///                 }
33///             }
34///         ]
35///     }
36/// });
37///
38///
39/// let mut output = Vec::new();
40/// assert!(xml_to_json(xml.as_bytes(), &mut output).is_ok());
41///
42/// assert_eq!(
43///   expected_json,
44///   serde_json::from_slice::<serde_json::Value>(&output).unwrap()
45/// );
46/// ```
47///
48/// # Errors
49///
50/// This may error when:
51///
52/// * reading XML
53/// * encountering a malformed character reference (e.g. `&#xZZ;`)
54/// * writing to the output
55pub 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
59/// The shared event loop for both reader modes.
60///
61/// This is a macro rather than a function because the two modes have incompatible
62/// borrow shapes: buffered events borrow from a per-iteration scratch buffer, while
63/// slice events borrow from the input for its whole lifetime.
64macro_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                // # Process root element
72                //
73                // Open root element that has children
74                (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                // Open root that has no children
83                (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                // # Process child element
93                //
94                // Open child element that has children
95                (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                // Open child element that has no children
106                (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                // Process a text node of an element
115                //
116                // Whitespace-only events before any real content would be edge-trimmed away
117                // anyway, so skip them on the raw bytes without paying for UTF-8 decoding —
118                // pretty-printed documents are full of them.
119                (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                // Process a reference (`&amp;`, `&#65;`, ...) within an element's text.
129                //
130                // Character references and predefined entities resolve to their characters;
131                // named entities we cannot resolve (e.g. DTD-defined) pass through verbatim.
132                (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                // Close out the current node on the stack
145                (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 there's nothing else on the stack, we're done
151                        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
167/// Convert XML to JSON from a buffered reader.
168///
169/// Use this instead of [`xml_to_json`] when the input is already buffered (e.g. a
170/// `BufReader` you manage yourself) to avoid double-buffering. If the whole document
171/// is already in memory, prefer [`xml_to_json_from_slice`], which avoids copying each
172/// event into an intermediate buffer.
173///
174/// # Errors
175///
176/// This may error when:
177///
178/// * reading XML
179/// * encountering a malformed character reference (e.g. `&#xZZ;`)
180/// * writing to the output
181pub 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
195/// Convert XML to JSON from an in-memory byte slice.
196///
197/// Use this instead of [`xml_to_json`] when the whole document is already in memory:
198/// events borrow directly from the input, avoiding the copy of every event into an
199/// intermediate buffer that the reader-based APIs must perform.
200///
201/// # Example Usage
202///
203/// ```
204/// use quick_xml_to_json::xml_to_json_from_slice;
205///
206/// let xml = r#"<root><child>Value</child></root>"#;
207/// let mut output = Vec::new();
208/// assert!(xml_to_json_from_slice(xml.as_bytes(), &mut output).is_ok());
209/// ```
210///
211/// # Errors
212///
213/// This may error when:
214///
215/// * reading XML
216/// * encountering a malformed character reference (e.g. `&#xZZ;`)
217/// * writing to the output
218pub 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 &amp; y &lt;tag&gt; &quot;q&quot; &apos;s&apos;</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&amp;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>&#72;&#x65;y</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&#10;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 &uuml; y</a>";
468        let expected_json = serde_json::json!({
469            "a": { "#t": "x &uuml; 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 &lt; 6" q="&quot;q&quot;" n="A&#66;C" u="x &uuml; y"/>"#;
484        let expected_json = serde_json::json!({
485            "a": {
486                "@m": "5 < 6",
487                "@q": "\"q\"",
488                "@n": "ABC",
489                "@u": "x &uuml; 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}