1use serde_json::{Map, Value};
16
17use crate::xml::types::XmlElement;
18
19fn merge_element_content(target: &mut Map<String, Value>, source: Map<String, Value>) {
20 for (key, value) in source {
21 match value {
22 Value::Array(items) => merge_array_value(target, key, items),
23 Value::Object(map) => merge_object_value(target, key, map),
24 primitive => merge_primitive_value(target, key, primitive),
25 }
26 }
27}
28
29fn merge_array_value(target: &mut Map<String, Value>, key: String, items: Vec<Value>) {
30 if !target.contains_key(&key) {
31 target.insert(key, Value::Array(items));
32 } else if let Some(Value::Array(arr)) = target.get_mut(&key) {
33 arr.extend(items);
34 } else {
35 let existing = target.remove(&key).unwrap();
36 let mut combined = vec![existing];
37 combined.extend(items);
38 target.insert(key, Value::Array(combined));
39 }
40}
41
42fn merge_object_value(target: &mut Map<String, Value>, key: String, map: Map<String, Value>) {
43 match target.get_mut(&key) {
44 Some(Value::Array(arr)) => arr.push(Value::Object(map)),
45 Some(existing) => {
48 let previous = std::mem::take(existing);
49 *existing = Value::Array(vec![previous, Value::Object(map)]);
50 }
51 None => {
52 target.insert(key, Value::Object(map));
53 }
54 }
55}
56
57fn merge_primitive_value(target: &mut Map<String, Value>, key: String, value: Value) {
58 if !target.contains_key(&key) {
59 target.insert(key, value);
60 }
61}
62
63fn default_xml_declaration() -> Value {
64 let mut decl = Map::new();
65 decl.insert("@version".to_string(), Value::String("1.0".to_string()));
66 decl.insert("@encoding".to_string(), Value::String("UTF-8".to_string()));
67 Value::Object(decl)
68}
69
70fn build_final_xml_element(
71 declaration: Option<Value>,
72 root_key: &str,
73 content: Map<String, Value>,
74) -> XmlElement {
75 let mut result = Map::new();
76 let decl = declaration.unwrap_or_else(default_xml_declaration);
77 result.insert("?xml".to_string(), decl);
78 result.insert(root_key.to_string(), Value::Object(content));
79 Value::Object(result)
80}
81
82pub fn reorder_root_keys(element: &XmlElement, key_order: &[String]) -> Option<XmlElement> {
88 let obj = element.as_object()?;
89 let root_key = obj.keys().find(|k| *k != "?xml")?;
90 obj.get(root_key)?.as_object()?;
91 Some(reorder_root_keys_owned(element.clone(), key_order))
92}
93
94pub fn reorder_root_keys_owned(element: XmlElement, key_order: &[String]) -> XmlElement {
98 let mut obj = match element {
99 Value::Object(obj) => obj,
100 other => return other,
101 };
102 let Some(root_key) = obj.keys().find(|k| *k != "?xml").cloned() else {
103 return Value::Object(obj);
104 };
105 let mut root_content = match obj.get_mut(&root_key) {
106 Some(Value::Object(content)) => std::mem::take(content),
107 _ => return Value::Object(obj),
108 };
109
110 let mut reordered = Map::new();
111 for key in key_order {
112 if !reordered.contains_key(key) {
115 if let Some(value) = root_content.get_mut(key) {
116 reordered.insert(key.clone(), std::mem::take(value));
117 }
118 }
119 }
120 for (key, value) in root_content {
121 if !reordered.contains_key(&key) {
122 reordered.insert(key, value);
123 }
124 }
125
126 let mut result = Map::new();
127 if let Some(decl) = obj.get_mut("?xml") {
128 result.insert("?xml".to_string(), std::mem::take(decl));
129 }
130 result.insert(root_key, Value::Object(reordered));
131 Value::Object(result)
132}
133
134pub fn merge_xml_elements(elements: &[XmlElement]) -> Option<XmlElement> {
141 merge_xml_elements_owned(elements.to_vec())
142}
143
144pub fn merge_xml_elements_owned(elements: Vec<XmlElement>) -> Option<XmlElement> {
147 if elements.is_empty() {
148 log::error!("No elements to merge.");
149 return None;
150 }
151
152 let root_key = elements
153 .iter()
154 .find_map(|el| el.as_object()?.keys().find(|k| *k != "?xml").cloned())?;
155
156 let mut declaration: Option<Value> = None;
157 let mut merged_content = Map::new();
158 for element in elements {
159 let Value::Object(mut obj) = element else {
160 continue;
161 };
162 if declaration.is_none() {
164 if let Some(decl) = obj.get_mut("?xml") {
165 declaration = Some(std::mem::take(decl));
166 }
167 }
168 if let Some(Value::Object(content)) = obj.get_mut(&root_key) {
169 merge_element_content(&mut merged_content, std::mem::take(content));
170 }
171 }
172
173 Some(build_final_xml_element(
174 declaration,
175 &root_key,
176 merged_content,
177 ))
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use serde_json::json;
184
185 mod reference {
189 use serde_json::{Map, Value};
190
191 fn merge_element_content(target: &mut Map<String, Value>, source: &Map<String, Value>) {
192 for (key, value) in source {
193 if value.is_array() {
194 merge_array_value(target, key, value.as_array().unwrap());
195 } else if value.is_object() {
196 merge_object_value(target, key, value.as_object().unwrap());
197 } else if !target.contains_key(key) {
198 target.insert(key.to_string(), value.clone());
199 }
200 }
201 }
202
203 fn merge_array_value(target: &mut Map<String, Value>, key: &str, value: &[Value]) {
204 if !target.contains_key(key) {
205 target.insert(key.to_string(), Value::Array(value.to_vec()));
206 } else if let Some(Value::Array(arr)) = target.get_mut(key) {
207 arr.extend(value.iter().cloned());
208 } else {
209 let existing = target.remove(key).unwrap();
210 target.insert(
211 key.to_string(),
212 Value::Array(
213 [vec![existing], value.to_vec()]
214 .into_iter()
215 .flatten()
216 .collect(),
217 ),
218 );
219 }
220 }
221
222 fn merge_object_value(
223 target: &mut Map<String, Value>,
224 key: &str,
225 value: &Map<String, Value>,
226 ) {
227 if let Some(Value::Array(arr)) = target.get_mut(key) {
228 arr.push(Value::Object(value.clone()));
229 } else if let Some(existing) = target.get(key) {
230 let existing = existing.clone();
231 target.insert(
232 key.to_string(),
233 Value::Array(vec![existing, Value::Object(value.clone())]),
234 );
235 } else {
236 target.insert(key.to_string(), Value::Object(value.clone()));
237 }
238 }
239
240 pub fn merge(elements: &[Value]) -> Option<Value> {
241 if elements.is_empty() {
242 return None;
243 }
244 let root_key = elements
245 .iter()
246 .find_map(|el| el.as_object()?.keys().find(|k| *k != "?xml").cloned())?;
247 let declaration = elements.iter().find_map(|el| el.as_object()?.get("?xml"));
248 let mut merged = Map::new();
249 for element in elements {
250 if let Some(content) = element
251 .as_object()
252 .and_then(|o| o.get(&root_key))
253 .and_then(Value::as_object)
254 {
255 merge_element_content(&mut merged, content);
256 }
257 }
258 let mut result = Map::new();
259 let decl = declaration
260 .cloned()
261 .unwrap_or_else(|| serde_json::json!({"@version": "1.0", "@encoding": "UTF-8"}));
262 result.insert("?xml".to_string(), decl);
263 result.insert(root_key, Value::Object(merged));
264 Some(Value::Object(result))
265 }
266
267 pub fn reorder(element: &Value, key_order: &[String]) -> Option<Value> {
268 let obj = element.as_object()?;
269 let root_key = obj.keys().find(|k| *k != "?xml")?.clone();
270 let root_content = obj.get(&root_key)?.as_object()?;
271 let mut reordered = Map::new();
272 for key in key_order {
273 if let Some(v) = root_content.get(key) {
274 reordered.insert(key.clone(), v.clone());
275 }
276 }
277 for (key, value) in root_content {
278 if !reordered.contains_key(key) {
279 reordered.insert(key.clone(), value.clone());
280 }
281 }
282 let mut result = Map::new();
283 if let Some(decl) = obj.get("?xml") {
284 result.insert("?xml".to_string(), decl.clone());
285 }
286 result.insert(root_key, Value::Object(reordered));
287 Some(Value::Object(result))
288 }
289 }
290
291 fn ordered(value: &Option<Value>) -> String {
293 serde_json::to_string(value).unwrap()
294 }
295
296 #[test]
297 fn owned_merge_matches_reference_including_key_order() {
298 let cases: Vec<Vec<Value>> = vec![
299 vec![
302 json!({"R": {"a": "1", "b": "2", "c": "3"}}),
303 json!({"R": {"a": ["x", "y"], "d": "4"}}),
304 ],
305 vec![
307 json!({"?xml": {"@version": "1.0"}, "R": {"o": {"k": 1}, "z": "z"}}),
308 json!({"R": {"o": {"k": 2}, "p": "p"}}),
309 json!({"R": {"o": {"k": 3}}}),
310 ],
311 vec![
313 json!({"R": {"items": [{"n": 1}], "label": "first"}}),
314 json!({"R": {"items": [{"n": 2}, {"n": 3}], "label": "second"}}),
315 json!({"R": {"items": {"n": 4}}}),
316 ],
317 vec![
319 json!({"?xml": {"@version": "1.0", "@encoding": "UTF-8"}}),
320 json!({}),
321 json!("not an object"),
322 json!({"Other": {"x": 1}}),
323 json!({"R": {"x": "kept"}}),
324 json!({"?xml": {"@version": "ignored"}, "R": {"y": "also"}}),
325 ],
326 vec![json!({"R": "text"}), json!({"R": {"a": 1}})],
328 vec![],
330 ];
331 for (i, elements) in cases.iter().enumerate() {
332 let expected = ordered(&reference::merge(elements));
333 assert_eq!(
334 ordered(&merge_xml_elements_owned(elements.clone())),
335 expected,
336 "case {i}"
337 );
338 assert_eq!(
339 ordered(&merge_xml_elements(elements)),
340 expected,
341 "borrowing wrapper, case {i}"
342 );
343 }
344 }
345
346 #[test]
347 fn owned_reorder_matches_reference_including_key_order() {
348 let order = |keys: &[&str]| keys.iter().map(|k| k.to_string()).collect::<Vec<_>>();
349 let doc = json!({
350 "?xml": {"@version": "1.0"},
351 "R": {"c": 3, "a": [1, 2], "b": {"x": 1}, "d": "4"}
352 });
353 let cases = [
354 order(&["a", "b", "c", "d"]),
355 order(&["d", "missing", "a"]),
356 order(&["b", "b", "a"]), order(&[]),
358 ];
359 for key_order in &cases {
360 let expected = ordered(&reference::reorder(&doc, key_order));
361 assert_eq!(
362 ordered(&reorder_root_keys(&doc, key_order)),
363 expected,
364 "{key_order:?}"
365 );
366 assert_eq!(
367 ordered(&Some(reorder_root_keys_owned(doc.clone(), key_order))),
368 expected,
369 "owned {key_order:?}"
370 );
371 }
372 }
373
374 #[test]
375 fn reorder_owned_returns_unexpected_shapes_unchanged() {
376 let key_order = vec!["a".to_string()];
377 for value in [
378 json!("text"),
379 json!({}),
380 json!({"?xml": {"@version": "1.0"}}),
381 json!({"R": "not an object"}),
382 ] {
383 assert!(reorder_root_keys(&value, &key_order).is_none(), "{value}");
384 assert_eq!(reorder_root_keys_owned(value.clone(), &key_order), value);
385 }
386 }
387
388 #[test]
389 fn merge_empty_returns_none() {
390 assert!(merge_xml_elements(&[]).is_none());
391 }
392
393 #[test]
394 fn merge_single_element_preserves_structure() {
395 let el = json!({
396 "?xml": { "@version": "1.0", "@encoding": "UTF-8" },
397 "Root": { "@xmlns": "http://example.com", "child": "a" }
398 });
399 let merged = merge_xml_elements(std::slice::from_ref(&el)).unwrap();
400 assert!(merged.get("?xml").is_some());
401 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
402 assert_eq!(root.get("child").and_then(|v| v.as_str()), Some("a"));
403 assert_eq!(
404 root.get("@xmlns").and_then(|v| v.as_str()),
405 Some("http://example.com")
406 );
407 }
408
409 #[test]
410 fn merge_two_elements_combines_nested_objects_into_array() {
411 let a = json!({ "Root": { "section": { "name": "first" } } });
412 let b = json!({ "Root": { "section": { "name": "second" } } });
413 let merged = merge_xml_elements(&[a, b]).unwrap();
414 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
415 let sections = root.get("section").and_then(|v| v.as_array()).unwrap();
416 assert_eq!(sections.len(), 2);
417 assert_eq!(
418 sections[0].get("name").and_then(|v| v.as_str()),
419 Some("first")
420 );
421 assert_eq!(
422 sections[1].get("name").and_then(|v| v.as_str()),
423 Some("second")
424 );
425 }
426
427 #[test]
428 fn reorder_root_keys_reorders_and_appends_extra() {
429 let el = json!({
430 "?xml": { "@version": "1.0" },
431 "Root": { "z": "last", "a": "first", "m": "mid" }
432 });
433 let reordered = reorder_root_keys(&el, &["a".into(), "m".into()]).unwrap();
434 let root = reordered.get("Root").and_then(|v| v.as_object()).unwrap();
435 let keys: Vec<_> = root
436 .keys()
437 .filter(|k| !k.starts_with('@'))
438 .cloned()
439 .collect();
440 assert_eq!(keys, ["a", "m", "z"]);
441 }
442
443 #[test]
444 fn merge_elements_without_declaration_uses_default() {
445 let a = json!({ "Root": { "a": "1" } });
446 let b = json!({ "Root": { "b": "2" } });
447 let merged = merge_xml_elements(&[a, b]).unwrap();
448 let decl = merged
452 .get("?xml")
453 .and_then(|v| v.as_object())
454 .expect("default ?xml declaration must be an object, not Null");
455 assert_eq!(decl.get("@version").and_then(|v| v.as_str()), Some("1.0"));
456 assert_eq!(
457 decl.get("@encoding").and_then(|v| v.as_str()),
458 Some("UTF-8")
459 );
460 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
461 assert_eq!(root.get("a").and_then(|v| v.as_str()), Some("1"));
462 assert_eq!(root.get("b").and_then(|v| v.as_str()), Some("2"));
463 }
464
465 #[test]
466 fn merge_array_value_coalesces_into_array() {
467 let a = json!({ "Root": { "item": { "x": "1" } } });
468 let b = json!({ "Root": { "item": { "x": "2" } } });
469 let merged = merge_xml_elements(&[a, b]).unwrap();
470 let items = merged
471 .get("Root")
472 .and_then(|r| r.get("item"))
473 .and_then(|i| i.as_array())
474 .unwrap();
475 assert_eq!(items.len(), 2);
476 }
477
478 #[test]
479 fn merge_elements_with_non_object_or_missing_root_skipped() {
480 let a = json!({ "Root": { "item": "a" } });
484 let b = json!("not-an-object");
485 let c = json!({ "Other": { "item": "c" } });
486 let d = json!({ "Root": "primitive" });
487 let merged = merge_xml_elements(&[a, b, c, d]).unwrap();
488 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
489 assert_eq!(root.get("item").and_then(|v| v.as_str()), Some("a"));
490 }
491
492 #[test]
493 fn merge_primitive_value_does_not_overwrite_existing() {
494 let a = json!({ "Root": { "item": "first" } });
496 let b = json!({ "Root": { "item": "second" } });
497 let merged = merge_xml_elements(&[a, b]).unwrap();
498 let item = merged
499 .get("Root")
500 .and_then(|r| r.get("item"))
501 .and_then(|v| v.as_str())
502 .unwrap();
503 assert_eq!(item, "first");
504 }
505
506 #[test]
507 fn merge_array_value_when_target_key_absent_sets_array() {
508 let a = json!({ "Root": { "other": "x" } });
510 let b = json!({ "Root": { "item": [ { "x": "1" } ] } });
511 let merged = merge_xml_elements(&[a, b]).unwrap();
512 let items = merged
513 .get("Root")
514 .and_then(|r| r.get("item"))
515 .and_then(|v| v.as_array())
516 .unwrap();
517 assert_eq!(items.len(), 1);
518 }
519
520 #[test]
521 fn merge_array_value_extends_existing_array() {
522 let a = json!({ "Root": { "item": [ { "x": "1" } ] } });
524 let b = json!({ "Root": { "item": [ { "x": "2" }, { "x": "3" } ] } });
525 let merged = merge_xml_elements(&[a, b]).unwrap();
526 let items = merged
527 .get("Root")
528 .and_then(|r| r.get("item"))
529 .and_then(|v| v.as_array())
530 .unwrap();
531 assert_eq!(items.len(), 3);
532 }
533
534 #[test]
535 fn reorder_root_keys_returns_none_for_invalid_inputs() {
536 assert!(reorder_root_keys(&json!("string"), &["a".into()]).is_none());
538 assert!(reorder_root_keys(&json!({ "?xml": {} }), &["a".into()]).is_none());
540 let el = json!({ "Root": "primitive" });
542 assert!(reorder_root_keys(&el, &["a".into()]).is_none());
543 }
544
545 #[test]
546 fn reorder_root_keys_without_declaration_omits_it() {
547 let el = json!({ "Root": { "b": "2", "a": "1" } });
548 let out = reorder_root_keys(&el, &["a".into(), "b".into()]).unwrap();
549 assert!(out.get("?xml").is_none());
550 let root = out.get("Root").and_then(|v| v.as_object()).unwrap();
551 let keys: Vec<_> = root.keys().cloned().collect();
552 assert_eq!(keys, ["a", "b"]);
553 }
554
555 #[test]
556 fn merge_skips_leading_declaration_only_element() {
557 let a = json!({ "?xml": { "@version": "1.0" } });
560 let b = json!({ "?xml": { "@version": "1.0" }, "Root": { "item": "x" } });
561 let merged = merge_xml_elements(&[a, b]).unwrap();
562 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
563 assert_eq!(root.get("item").and_then(|v| v.as_str()), Some("x"));
564 }
565
566 #[test]
567 fn merge_skips_leading_empty_object_element() {
568 let a = json!({});
571 let b = json!({ "Root": { "item": "ok" } });
572 let merged = merge_xml_elements(&[a, b]).unwrap();
573 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
574 assert_eq!(root.get("item").and_then(|v| v.as_str()), Some("ok"));
575 }
576
577 #[test]
578 fn merge_returns_none_when_every_element_has_no_root() {
579 let a = json!({});
582 let b = json!({ "?xml": { "@version": "1.0" } });
583 assert!(merge_xml_elements(&[a, b]).is_none());
584 }
585
586 #[test]
587 fn merge_array_value_when_target_has_key_non_array_coalesces_into_array() {
588 let a = json!({ "Root": { "item": { "x": "1" } } });
590 let b = json!({ "Root": { "item": [ { "x": "2" } ] } });
591 let merged = merge_xml_elements(&[a, b]).unwrap();
592 let items = merged
593 .get("Root")
594 .and_then(|r| r.get("item"))
595 .and_then(|i| i.as_array())
596 .unwrap();
597 assert_eq!(items.len(), 2);
598 assert_eq!(items[0].get("x").and_then(|v| v.as_str()), Some("1"));
599 assert_eq!(items[1].get("x").and_then(|v| v.as_str()), Some("2"));
600 }
601}