Skip to main content

xml_disassembler/builders/
merge_xml_elements.rs

1//! Merge multiple XML elements into one.
2
3use serde_json::{Map, Value};
4
5use crate::types::XmlElement;
6
7fn is_mergeable_object(value: &Value) -> bool {
8    value.is_object() && !value.is_array()
9}
10
11fn merge_element_content(target: &mut Map<String, Value>, source: &Map<String, Value>) {
12    for (key, value) in source {
13        if value.is_array() {
14            merge_array_value(target, key, value.as_array().unwrap());
15        } else if is_mergeable_object(value) {
16            merge_object_value(target, key, value.as_object().unwrap());
17        } else {
18            merge_primitive_value(target, key, value);
19        }
20    }
21}
22
23fn merge_array_value(target: &mut Map<String, Value>, key: &str, value: &[Value]) {
24    if !target.contains_key(key) {
25        target.insert(key.to_string(), Value::Array(value.to_vec()));
26    } else if let Some(Value::Array(arr)) = target.get_mut(key) {
27        arr.extend(value.iter().cloned());
28    } else {
29        let existing = target.remove(key).unwrap();
30        target.insert(
31            key.to_string(),
32            Value::Array(
33                [vec![existing], value.to_vec()]
34                    .into_iter()
35                    .flatten()
36                    .collect(),
37            ),
38        );
39    }
40}
41
42fn merge_object_value(target: &mut Map<String, Value>, key: &str, value: &Map<String, Value>) {
43    if let Some(Value::Array(arr)) = target.get_mut(key) {
44        arr.push(Value::Object(value.clone()));
45    } else if let Some(existing) = target.get(key) {
46        let existing = existing.clone();
47        target.insert(
48            key.to_string(),
49            Value::Array(vec![existing, Value::Object(value.clone())]),
50        );
51    } else {
52        target.insert(key.to_string(), Value::Object(value.clone()));
53    }
54}
55
56fn merge_primitive_value(target: &mut Map<String, Value>, key: &str, value: &Value) {
57    if !target.contains_key(key) {
58        target.insert(key.to_string(), value.clone());
59    }
60}
61
62fn default_xml_declaration() -> Value {
63    let mut decl = Map::new();
64    decl.insert("@version".to_string(), Value::String("1.0".to_string()));
65    decl.insert("@encoding".to_string(), Value::String("UTF-8".to_string()));
66    Value::Object(decl)
67}
68
69fn build_final_xml_element(
70    declaration: Option<&Value>,
71    root_key: &str,
72    content: Map<String, Value>,
73) -> XmlElement {
74    let mut result = Map::new();
75    let decl = declaration.cloned().unwrap_or_else(default_xml_declaration);
76    result.insert("?xml".to_string(), decl);
77    result.insert(root_key.to_string(), Value::Object(content));
78    Value::Object(result)
79}
80
81/// Reorder the root element's child keys to match the given order.
82/// Keys not in `key_order` are appended at the end.
83pub fn reorder_root_keys(element: &XmlElement, key_order: &[String]) -> Option<XmlElement> {
84    let obj = element.as_object()?;
85    let root_key = obj.keys().find(|k| *k != "?xml")?.clone();
86    let root_content = obj.get(&root_key)?.as_object()?;
87    let mut reordered = Map::new();
88    for key in key_order {
89        if let Some(v) = root_content.get(key) {
90            reordered.insert(key.clone(), v.clone());
91        }
92    }
93    for (key, value) in root_content {
94        if !reordered.contains_key(key) {
95            reordered.insert(key.clone(), value.clone());
96        }
97    }
98    let mut result = Map::new();
99    if let Some(decl) = obj.get("?xml") {
100        result.insert("?xml".to_string(), decl.clone());
101    }
102    result.insert(root_key, Value::Object(reordered));
103    Some(Value::Object(result))
104}
105
106/// Merge multiple XML elements into one.
107pub fn merge_xml_elements(elements: &[XmlElement]) -> Option<XmlElement> {
108    if elements.is_empty() {
109        log::error!("No elements to merge.");
110        return None;
111    }
112
113    let first = &elements[0];
114    let root_key = first.as_object()?.keys().find(|k| *k != "?xml")?.clone();
115    let mut merged_content = Map::new();
116
117    for element in elements {
118        if let Some(obj) = element.as_object() {
119            if let Some(root_content) = obj.get(&root_key) {
120                if let Some(content_obj) = root_content.as_object() {
121                    merge_element_content(&mut merged_content, content_obj);
122                }
123            }
124        }
125    }
126
127    let declaration = first.as_object()?.get("?xml");
128    Some(build_final_xml_element(
129        declaration,
130        &root_key,
131        merged_content,
132    ))
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use serde_json::json;
139
140    #[test]
141    fn merge_empty_returns_none() {
142        assert!(merge_xml_elements(&[]).is_none());
143    }
144
145    #[test]
146    fn merge_single_element_preserves_structure() {
147        let el = json!({
148            "?xml": { "@version": "1.0", "@encoding": "UTF-8" },
149            "Root": { "@xmlns": "http://example.com", "child": "a" }
150        });
151        let merged = merge_xml_elements(std::slice::from_ref(&el)).unwrap();
152        assert!(merged.get("?xml").is_some());
153        let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
154        assert_eq!(root.get("child").and_then(|v| v.as_str()), Some("a"));
155        assert_eq!(
156            root.get("@xmlns").and_then(|v| v.as_str()),
157            Some("http://example.com")
158        );
159    }
160
161    #[test]
162    fn merge_two_elements_combines_nested_objects_into_array() {
163        let a = json!({ "Root": { "section": { "name": "first" } } });
164        let b = json!({ "Root": { "section": { "name": "second" } } });
165        let merged = merge_xml_elements(&[a, b]).unwrap();
166        let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
167        let sections = root.get("section").and_then(|v| v.as_array()).unwrap();
168        assert_eq!(sections.len(), 2);
169        assert_eq!(
170            sections[0].get("name").and_then(|v| v.as_str()),
171            Some("first")
172        );
173        assert_eq!(
174            sections[1].get("name").and_then(|v| v.as_str()),
175            Some("second")
176        );
177    }
178}