1#![warn(clippy::pedantic)]
2
3mod decoders;
4mod errors;
5mod frames;
6
7use crate::frames::AttributesWriter;
8use decoders::decode_text;
9pub use errors::XmlToJsonError;
10use quick_xml::Reader;
11use quick_xml::events::Event;
12use std::io::{BufReader, Read, Write};
13
14static CHILDREN_KEY: &str = "#c";
15static TEXT_NODE_KEY: &str = "#t";
16static MB: usize = 1024 * 1024;
17
18pub fn xml_to_json<R: Read, W: Write>(reader: R, out: W) -> Result<(), XmlToJsonError> {
60 let mut writer = std::io::BufWriter::with_capacity(MB * 2, out);
61 let mut xml = Reader::from_reader(BufReader::new(reader));
62 let mut buf = Vec::new();
63 let mut stack: Vec<frames::Element> = Vec::new();
64
65 loop {
66 match (xml.read_event_into(&mut buf)?, stack.last_mut()) {
67 (Event::Start(e), None) => {
71 let mut frame = frames::Element::from_element(&e, &xml)?;
72
73 frame.open(&mut writer)?;
74 frame.process_element_attributes(&e, &xml, &mut writer)?;
75
76 stack.push(frame);
77 }
78
79 (Event::Empty(e), None) => {
81 let mut frame = frames::EmptyNode::from_element(&e, &xml)?;
82
83 frame.open(&mut writer)?;
84 frame.process_element_attributes(&e, &xml, &mut writer)?;
85 frame.close(&mut writer)?;
86
87 writer.flush()?;
88 return Ok(());
89 }
90
91 (Event::Start(e), Some(parent)) => {
95 parent.begin_child(&mut writer)?;
96
97 let mut frame = frames::Element::from_element(&e, &xml)?;
98
99 frame.open(&mut writer)?;
100 frame.process_element_attributes(&e, &xml, &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::from_element(&e, &xml)?;
110
111 frame.open(&mut writer)?;
112 frame.process_element_attributes(&e, &xml, &mut writer)?;
113 frame.close(&mut writer)?;
114 }
115
116 (Event::Text(t), Some(frame)) => {
118 let text = decode_text(&xml, &t)?;
119 frame.push_text(&text);
120 }
121
122 (Event::End(_), _) => {
124 if let Some(mut frame) = stack.pop() {
125 frame.close(&mut writer)?;
126
127 if stack.is_empty() {
129 writer.flush()?;
130 return Ok(());
131 }
132 }
133 }
134
135 (Event::Eof, _) => break,
136 _ => {}
137 }
138
139 buf.clear();
140 }
141
142 Err(XmlToJsonError::InvalidXML)
143}
144
145#[cfg(test)]
146mod tests {
147
148 #[test]
149 fn test_nested_structure() {
150 let xml = r#"
151 <root>
152 <child1 attr1="value1">
153 <subchild>Text 1</subchild>
154 <subchild>Text 2</subchild>
155 <subchild>Text 3</subchild>
156 </child1>
157 <child2 attr2="value2" />
158 <child1 attr2="value2" attr3="value3" attr1="value1">
159 <subchild>Text 2</subchild>
160 <subchild>Text 1</subchild>
161 <subchild>Text 3</subchild>
162 </child1>
163 </root>
164 "#;
165
166 let expected_json = serde_json::json!({
167 "root": {
168 "#c": [
169 {
170 "child1": {
171 "@attr1": "value1",
172 "#c": [
173 {
174 "subchild": {
175 "#t": "Text 1"
176 }
177 },
178 {
179 "subchild": {
180 "#t": "Text 2"
181 }
182 },
183 {
184 "subchild": {
185 "#t": "Text 3"
186 }
187 },
188 ]
189 }
190 },
191 {
192 "child2": {
193 "@attr2": "value2"
194 }
195 },
196 {
197 "child1": {
198 "@attr3": "value3",
199 "@attr2": "value2",
200 "@attr1": "value1",
201 "#c": [
202 {
203 "subchild": {
204 "#t": "Text 2"
205 }
206 },
207 {
208 "subchild": {
209 "#t": "Text 1"
210 }
211 },
212 {
213 "subchild": {
214 "#t": "Text 3"
215 }
216 },
217 ]
218 }
219 },
220 ]
221 }
222 });
223
224 assert_eq!(expected_json, convert_xml_to_json(xml));
225 }
226
227 #[test]
228 fn test_basic_xml_to_json() {
229 let xml = r#"<users count="3">
230 <user age="40">Jane Doe</user>
231 <user age="42">John Doe</user>
232 <user age="12">Jim Doe</user>
233</users>"#;
234
235 let expected_json = serde_json::json!({
236 "users": {
237 "@count": "3",
238 "#c": [
239 {
240 "user": {
241 "@age": "40",
242 "#t": "Jane Doe"
243 }
244 },
245 {
246 "user": {
247 "@age": "42",
248 "#t": "John Doe"
249 }
250 },
251 {
252 "user": {
253 "@age": "12",
254 "#t": "Jim Doe"
255 }
256 }
257 ]
258 }
259 });
260
261 assert_eq!(expected_json, convert_xml_to_json(xml));
262 }
263
264 #[test]
265 fn test_single_element_with_text() {
266 let xml = "<name>John Doe</name>";
267 let expected_json = serde_json::json!({
268 "name": {
269 "#t": "John Doe"
270 }
271 });
272
273 assert_eq!(expected_json, convert_xml_to_json(xml));
274 }
275
276 #[test]
277 fn test_element_with_attributes_only() {
278 let xml = r#"<div class="container" id="main"></div>"#;
279 let expected_json = serde_json::json!({
280 "div": {
281 "@class": "container",
282 "@id": "main"
283 }
284 });
285
286 assert_eq!(expected_json, convert_xml_to_json(xml));
287 }
288
289 #[test]
290 fn test_nested_elements() {
291 let xml = r#"<root><parent id="1"><child>Value</child></parent></root>"#;
292 let expected_json = serde_json::json!({
293 "root": {
294 "#c": [
295 {
296 "parent": {
297 "@id": "1",
298 "#c": [
299 { "child": { "#t": "Value" } }
300 ]
301 }
302 }
303 ]
304 }
305 });
306
307 assert_eq!(expected_json, convert_xml_to_json(xml));
308 }
309
310 #[test]
311 fn test_empty_xml() {
312 assert!(super::xml_to_json("".as_bytes(), Vec::new()).is_err());
313 }
314
315 #[test]
316 fn test_malformed_xml() {
317 assert!(super::xml_to_json("<root><unclosed>".as_bytes(), Vec::new()).is_err());
318 }
319
320 #[test]
321 fn test_empty_nodes() {
322 let xml = "<main><br /></main>";
323 let expected_json = serde_json::json!({
324 "main": {
325 "#c": [
326 { "br": {}}
327 ]
328 }
329 });
330
331 assert_eq!(expected_json, convert_xml_to_json(xml));
332 }
333
334 #[test]
335 fn test_empty_node_at_root() {
336 let xml = "<br />";
337 let expected_json = serde_json::json!({ "br": {} });
338
339 assert_eq!(expected_json, convert_xml_to_json(xml));
340 }
341
342 #[test]
343 fn test_empty_node_at_root_with_attributes() {
344 let xml = r#"<br id="5" />"#;
345 let expected_json = serde_json::json!({ "br": { "@id": "5" } });
346
347 assert_eq!(expected_json, convert_xml_to_json(xml));
348 }
349
350 fn convert_xml_to_json(xml: &str) -> serde_json::Value {
351 let mut output = Vec::new();
352 super::xml_to_json(xml.as_bytes(), &mut output).unwrap();
353
354 serde_json::from_slice(&output).unwrap()
355 }
356}