Skip to main content

xml_disassembler/builders/
extract_root_attributes.rs

1//! Extract XML attributes from root element.
2
3use serde_json::{Map, Value};
4
5use crate::types::XmlElement;
6
7fn attr_value_to_string(v: &Value) -> String {
8    match v {
9        Value::String(s) => s.clone(),
10        Value::Number(n) => n.to_string(),
11        Value::Bool(b) => b.to_string(),
12        Value::Null => String::new(),
13        _ => serde_json::to_string(v).unwrap_or_default(),
14    }
15}
16
17/// Extracts XML attributes from a root element and returns them as a flat map.
18/// Handles @-prefixed keys (e.g. @xmlns, @version) and xmlns (default namespace).
19/// Converts values to strings for consistent XML output.
20pub fn extract_root_attributes(element: &XmlElement) -> XmlElement {
21    let mut attributes = Map::new();
22    if let Some(obj) = element.as_object() {
23        for (key, value) in obj {
24            let is_attr = key.starts_with('@') || key == "xmlns";
25            if is_attr {
26                let attr_key = if key == "xmlns" {
27                    "@xmlns".to_string()
28                } else {
29                    key.clone()
30                };
31                attributes.insert(attr_key, Value::String(attr_value_to_string(value)));
32            }
33        }
34    }
35    Value::Object(attributes)
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41    use serde_json::json;
42
43    #[test]
44    fn extracts_at_prefixed_attributes() {
45        let element = json!({
46            "@xmlns": "http://example.com",
47            "@version": "1.0",
48            "child": "ignored"
49        });
50        let attrs = extract_root_attributes(&element);
51        let obj = attrs.as_object().unwrap();
52        assert_eq!(
53            obj.get("@xmlns").and_then(|v| v.as_str()),
54            Some("http://example.com")
55        );
56        assert_eq!(obj.get("@version").and_then(|v| v.as_str()), Some("1.0"));
57        assert!(obj.get("child").is_none());
58    }
59
60    #[test]
61    fn returns_empty_for_non_object() {
62        let element = json!("string");
63        let attrs = extract_root_attributes(&element);
64        assert!(attrs.as_object().unwrap().is_empty());
65    }
66
67    #[test]
68    fn converts_xmlns_to_at_xmlns() {
69        let element = json!({ "xmlns": "http://ns.example.com", "child": {} });
70        let attrs = extract_root_attributes(&element);
71        assert_eq!(
72            attrs
73                .as_object()
74                .unwrap()
75                .get("@xmlns")
76                .and_then(|v| v.as_str()),
77            Some("http://ns.example.com")
78        );
79    }
80
81    #[test]
82    fn attr_value_number_and_bool_converted_to_string() {
83        let element = json!({ "@num": 42, "@flag": true, "child": {} });
84        let attrs = extract_root_attributes(&element);
85        let obj = attrs.as_object().unwrap();
86        assert_eq!(obj.get("@num").and_then(|v| v.as_str()), Some("42"));
87        assert_eq!(obj.get("@flag").and_then(|v| v.as_str()), Some("true"));
88    }
89
90    #[test]
91    fn attr_value_null_converted_to_empty_string() {
92        let element = json!({ "@empty": null, "child": {} });
93        let attrs = extract_root_attributes(&element);
94        assert_eq!(attrs.get("@empty").and_then(|v| v.as_str()), Some(""));
95    }
96}