config_disassembler/xml/builders/
merge_xml_elements.rs1use serde_json::{Map, Value};
4
5use crate::xml::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 let decl = merged
211 .get("?xml")
212 .and_then(|v| v.as_object())
213 .expect("default ?xml declaration must be an object, not Null");
214 assert_eq!(decl.get("@version").and_then(|v| v.as_str()), Some("1.0"));
215 assert_eq!(
216 decl.get("@encoding").and_then(|v| v.as_str()),
217 Some("UTF-8")
218 );
219 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
220 assert_eq!(root.get("a").and_then(|v| v.as_str()), Some("1"));
221 assert_eq!(root.get("b").and_then(|v| v.as_str()), Some("2"));
222 }
223
224 #[test]
225 fn merge_array_value_coalesces_into_array() {
226 let a = json!({ "Root": { "item": { "x": "1" } } });
227 let b = json!({ "Root": { "item": { "x": "2" } } });
228 let merged = merge_xml_elements(&[a, b]).unwrap();
229 let items = merged
230 .get("Root")
231 .and_then(|r| r.get("item"))
232 .and_then(|i| i.as_array())
233 .unwrap();
234 assert_eq!(items.len(), 2);
235 }
236
237 #[test]
238 fn merge_elements_with_non_object_or_missing_root_skipped() {
239 let a = json!({ "Root": { "item": "a" } });
243 let b = json!("not-an-object");
244 let c = json!({ "Other": { "item": "c" } });
245 let d = json!({ "Root": "primitive" });
246 let merged = merge_xml_elements(&[a, b, c, d]).unwrap();
247 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
248 assert_eq!(root.get("item").and_then(|v| v.as_str()), Some("a"));
249 }
250
251 #[test]
252 fn merge_primitive_value_does_not_overwrite_existing() {
253 let a = json!({ "Root": { "item": "first" } });
255 let b = json!({ "Root": { "item": "second" } });
256 let merged = merge_xml_elements(&[a, b]).unwrap();
257 let item = merged
258 .get("Root")
259 .and_then(|r| r.get("item"))
260 .and_then(|v| v.as_str())
261 .unwrap();
262 assert_eq!(item, "first");
263 }
264
265 #[test]
266 fn merge_array_value_when_target_key_absent_sets_array() {
267 let a = json!({ "Root": { "other": "x" } });
269 let b = json!({ "Root": { "item": [ { "x": "1" } ] } });
270 let merged = merge_xml_elements(&[a, b]).unwrap();
271 let items = merged
272 .get("Root")
273 .and_then(|r| r.get("item"))
274 .and_then(|v| v.as_array())
275 .unwrap();
276 assert_eq!(items.len(), 1);
277 }
278
279 #[test]
280 fn merge_array_value_extends_existing_array() {
281 let a = json!({ "Root": { "item": [ { "x": "1" } ] } });
283 let b = json!({ "Root": { "item": [ { "x": "2" }, { "x": "3" } ] } });
284 let merged = merge_xml_elements(&[a, b]).unwrap();
285 let items = merged
286 .get("Root")
287 .and_then(|r| r.get("item"))
288 .and_then(|v| v.as_array())
289 .unwrap();
290 assert_eq!(items.len(), 3);
291 }
292
293 #[test]
294 fn reorder_root_keys_returns_none_for_invalid_inputs() {
295 assert!(reorder_root_keys(&json!("string"), &["a".into()]).is_none());
297 assert!(reorder_root_keys(&json!({ "?xml": {} }), &["a".into()]).is_none());
299 let el = json!({ "Root": "primitive" });
301 assert!(reorder_root_keys(&el, &["a".into()]).is_none());
302 }
303
304 #[test]
305 fn reorder_root_keys_without_declaration_omits_it() {
306 let el = json!({ "Root": { "b": "2", "a": "1" } });
307 let out = reorder_root_keys(&el, &["a".into(), "b".into()]).unwrap();
308 assert!(out.get("?xml").is_none());
309 let root = out.get("Root").and_then(|v| v.as_object()).unwrap();
310 let keys: Vec<_> = root.keys().cloned().collect();
311 assert_eq!(keys, ["a", "b"]);
312 }
313
314 #[test]
315 fn merge_skips_leading_declaration_only_element() {
316 let a = json!({ "?xml": { "@version": "1.0" } });
319 let b = json!({ "?xml": { "@version": "1.0" }, "Root": { "item": "x" } });
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("x"));
323 }
324
325 #[test]
326 fn merge_skips_leading_empty_object_element() {
327 let a = json!({});
330 let b = json!({ "Root": { "item": "ok" } });
331 let merged = merge_xml_elements(&[a, b]).unwrap();
332 let root = merged.get("Root").and_then(|v| v.as_object()).unwrap();
333 assert_eq!(root.get("item").and_then(|v| v.as_str()), Some("ok"));
334 }
335
336 #[test]
337 fn merge_returns_none_when_every_element_has_no_root() {
338 let a = json!({});
341 let b = json!({ "?xml": { "@version": "1.0" } });
342 assert!(merge_xml_elements(&[a, b]).is_none());
343 }
344
345 #[test]
346 fn merge_array_value_when_target_has_key_non_array_coalesces_into_array() {
347 let a = json!({ "Root": { "item": { "x": "1" } } });
349 let b = json!({ "Root": { "item": [ { "x": "2" } ] } });
350 let merged = merge_xml_elements(&[a, b]).unwrap();
351 let items = merged
352 .get("Root")
353 .and_then(|r| r.get("item"))
354 .and_then(|i| i.as_array())
355 .unwrap();
356 assert_eq!(items.len(), 2);
357 assert_eq!(items[0].get("x").and_then(|v| v.as_str()), Some("1"));
358 assert_eq!(items[1].get("x").and_then(|v| v.as_str()), Some("2"));
359 }
360}