xml_disassembler/builders/
merge_xml_elements.rs1use 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
81pub 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
106pub fn merge_xml_elements(elements: &[XmlElement]) -> Option<XmlElement> {
113 if elements.is_empty() {
114 log::error!("No elements to merge.");
115 return None;
116 }
117
118 let root_key = elements
119 .iter()
120 .find_map(|el| el.as_object()?.keys().find(|k| *k != "?xml").cloned())?;
121
122 let declaration = elements.iter().find_map(|el| el.as_object()?.get("?xml"));
123
124 let mut merged_content = Map::new();
125 for element in elements {
126 if let Some(obj) = element.as_object() {
127 if let Some(root_content) = obj.get(&root_key) {
128 if let Some(content_obj) = root_content.as_object() {
129 merge_element_content(&mut merged_content, content_obj);
130 }
131 }
132 }
133 }
134
135 Some(build_final_xml_element(
136 declaration,
137 &root_key,
138 merged_content,
139 ))
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use serde_json::json;
146
147 #[test]
148 fn merge_empty_returns_none() {
149 assert!(merge_xml_elements(&[]).is_none());
150 }
151
152 #[test]
153 fn merge_single_element_preserves_structure() {
154 let el = json!({
155 "?xml": { "@version": "1.0", "@encoding": "UTF-8" },
156 "Root": { "@xmlns": "http://example.com", "child": "a" }
157 });
158 let merged = merge_xml_elements(std::slice::from_ref(&el)).unwrap();
159 assert!(merged.get("?xml").is_some());
160 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
161 assert_eq!(root.get("child").and_then(|v| v.as_str()), Some("a"));
162 assert_eq!(
163 root.get("@xmlns").and_then(|v| v.as_str()),
164 Some("http://example.com")
165 );
166 }
167
168 #[test]
169 fn merge_two_elements_combines_nested_objects_into_array() {
170 let a = json!({ "Root": { "section": { "name": "first" } } });
171 let b = json!({ "Root": { "section": { "name": "second" } } });
172 let merged = merge_xml_elements(&[a, b]).unwrap();
173 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
174 let sections = root.get("section").and_then(|v| v.as_array()).unwrap();
175 assert_eq!(sections.len(), 2);
176 assert_eq!(
177 sections[0].get("name").and_then(|v| v.as_str()),
178 Some("first")
179 );
180 assert_eq!(
181 sections[1].get("name").and_then(|v| v.as_str()),
182 Some("second")
183 );
184 }
185
186 #[test]
187 fn reorder_root_keys_reorders_and_appends_extra() {
188 let el = json!({
189 "?xml": { "@version": "1.0" },
190 "Root": { "z": "last", "a": "first", "m": "mid" }
191 });
192 let reordered = reorder_root_keys(&el, &["a".into(), "m".into()]).unwrap();
193 let root = reordered.get("Root").and_then(|v| v.as_object()).unwrap();
194 let keys: Vec<_> = root
195 .keys()
196 .filter(|k| !k.starts_with('@'))
197 .cloned()
198 .collect();
199 assert_eq!(keys, ["a", "m", "z"]);
200 }
201
202 #[test]
203 fn merge_elements_without_declaration_uses_default() {
204 let a = json!({ "Root": { "a": "1" } });
205 let b = json!({ "Root": { "b": "2" } });
206 let merged = merge_xml_elements(&[a, b]).unwrap();
207 assert!(merged.get("?xml").is_some());
208 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
209 assert_eq!(root.get("a").and_then(|v| v.as_str()), Some("1"));
210 assert_eq!(root.get("b").and_then(|v| v.as_str()), Some("2"));
211 }
212
213 #[test]
214 fn merge_array_value_coalesces_into_array() {
215 let a = json!({ "Root": { "item": { "x": "1" } } });
216 let b = json!({ "Root": { "item": { "x": "2" } } });
217 let merged = merge_xml_elements(&[a, b]).unwrap();
218 let items = merged
219 .get("Root")
220 .and_then(|r| r.get("item"))
221 .and_then(|i| i.as_array())
222 .unwrap();
223 assert_eq!(items.len(), 2);
224 }
225
226 #[test]
227 fn merge_elements_with_non_object_or_missing_root_skipped() {
228 let a = json!({ "Root": { "item": "a" } });
232 let b = json!("not-an-object");
233 let c = json!({ "Other": { "item": "c" } });
234 let d = json!({ "Root": "primitive" });
235 let merged = merge_xml_elements(&[a, b, c, d]).unwrap();
236 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
237 assert_eq!(root.get("item").and_then(|v| v.as_str()), Some("a"));
238 }
239
240 #[test]
241 fn merge_primitive_value_does_not_overwrite_existing() {
242 let a = json!({ "Root": { "item": "first" } });
244 let b = json!({ "Root": { "item": "second" } });
245 let merged = merge_xml_elements(&[a, b]).unwrap();
246 let item = merged
247 .get("Root")
248 .and_then(|r| r.get("item"))
249 .and_then(|v| v.as_str())
250 .unwrap();
251 assert_eq!(item, "first");
252 }
253
254 #[test]
255 fn merge_array_value_when_target_key_absent_sets_array() {
256 let a = json!({ "Root": { "other": "x" } });
258 let b = json!({ "Root": { "item": [ { "x": "1" } ] } });
259 let merged = merge_xml_elements(&[a, b]).unwrap();
260 let items = merged
261 .get("Root")
262 .and_then(|r| r.get("item"))
263 .and_then(|v| v.as_array())
264 .unwrap();
265 assert_eq!(items.len(), 1);
266 }
267
268 #[test]
269 fn merge_array_value_extends_existing_array() {
270 let a = json!({ "Root": { "item": [ { "x": "1" } ] } });
272 let b = json!({ "Root": { "item": [ { "x": "2" }, { "x": "3" } ] } });
273 let merged = merge_xml_elements(&[a, b]).unwrap();
274 let items = merged
275 .get("Root")
276 .and_then(|r| r.get("item"))
277 .and_then(|v| v.as_array())
278 .unwrap();
279 assert_eq!(items.len(), 3);
280 }
281
282 #[test]
283 fn reorder_root_keys_returns_none_for_invalid_inputs() {
284 assert!(reorder_root_keys(&json!("string"), &["a".into()]).is_none());
286 assert!(reorder_root_keys(&json!({ "?xml": {} }), &["a".into()]).is_none());
288 let el = json!({ "Root": "primitive" });
290 assert!(reorder_root_keys(&el, &["a".into()]).is_none());
291 }
292
293 #[test]
294 fn reorder_root_keys_without_declaration_omits_it() {
295 let el = json!({ "Root": { "b": "2", "a": "1" } });
296 let out = reorder_root_keys(&el, &["a".into(), "b".into()]).unwrap();
297 assert!(out.get("?xml").is_none());
298 let root = out.get("Root").and_then(|v| v.as_object()).unwrap();
299 let keys: Vec<_> = root.keys().cloned().collect();
300 assert_eq!(keys, ["a", "b"]);
301 }
302
303 #[test]
304 fn merge_skips_leading_declaration_only_element() {
305 let a = json!({ "?xml": { "@version": "1.0" } });
308 let b = json!({ "?xml": { "@version": "1.0" }, "Root": { "item": "x" } });
309 let merged = merge_xml_elements(&[a, b]).unwrap();
310 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
311 assert_eq!(root.get("item").and_then(|v| v.as_str()), Some("x"));
312 }
313
314 #[test]
315 fn merge_skips_leading_empty_object_element() {
316 let a = json!({});
319 let b = json!({ "Root": { "item": "ok" } });
320 let merged = merge_xml_elements(&[a, b]).unwrap();
321 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
322 assert_eq!(root.get("item").and_then(|v| v.as_str()), Some("ok"));
323 }
324
325 #[test]
326 fn merge_returns_none_when_every_element_has_no_root() {
327 let a = json!({});
330 let b = json!({ "?xml": { "@version": "1.0" } });
331 assert!(merge_xml_elements(&[a, b]).is_none());
332 }
333
334 #[test]
335 fn merge_array_value_when_target_has_key_non_array_coalesces_into_array() {
336 let a = json!({ "Root": { "item": { "x": "1" } } });
338 let b = json!({ "Root": { "item": [ { "x": "2" } ] } });
339 let merged = merge_xml_elements(&[a, b]).unwrap();
340 let items = merged
341 .get("Root")
342 .and_then(|r| r.get("item"))
343 .and_then(|i| i.as_array())
344 .unwrap();
345 assert_eq!(items.len(), 2);
346 assert_eq!(items[0].get("x").and_then(|v| v.as_str()), Some("1"));
347 assert_eq!(items[1].get("x").and_then(|v| v.as_str()), Some("2"));
348 }
349}