Skip to main content

xml_disassembler/parsers/
parse_to_xml_object.rs

1//! Parse file to XmlElement - supports XML, YAML, JSON.
2
3use crate::parsers::{
4    extract_xml_declaration_from_raw, extract_xmlns_from_raw, parse_xml_from_str,
5};
6use crate::types::XmlElement;
7use serde_json::Value;
8use tokio::fs;
9
10pub async fn parse_to_xml_object(file_path: &str) -> Option<XmlElement> {
11    if file_path.to_lowercase().ends_with(".xml") {
12        let content = fs::read_to_string(file_path).await.ok()?;
13        let mut parsed = parse_xml_from_str(&content, file_path)?;
14        if let Some(obj) = parsed.as_object_mut() {
15            // quickxml_to_serde drops the declaration - extract from raw and add at top level
16            if let Some(decl) = extract_xml_declaration_from_raw(&content) {
17                obj.insert("?xml".to_string(), decl);
18            }
19            // quickxml_to_serde drops xmlns - extract from raw and add to root element
20            if let Some(xmlns) = extract_xmlns_from_raw(&content) {
21                let root_key = obj.keys().find(|k| *k != "?xml")?.clone();
22                if let Some(root_val) = obj.get_mut(&root_key) {
23                    if let Some(root_obj) = root_val.as_object_mut() {
24                        if !root_obj.contains_key("@xmlns") {
25                            root_obj.insert("@xmlns".to_string(), Value::String(xmlns));
26                        }
27                    }
28                }
29            }
30        }
31        return Some(parsed);
32    }
33
34    let content = fs::read_to_string(file_path).await.ok()?;
35
36    if file_path.to_lowercase().ends_with(".yaml") || file_path.to_lowercase().ends_with(".yml") {
37        return serde_yaml::from_str(&content).ok();
38    }
39
40    if file_path.to_lowercase().ends_with(".json5") {
41        return json5::from_str(&content).ok();
42    }
43
44    if file_path.to_lowercase().ends_with(".json") {
45        return serde_json::from_str(&content).ok();
46    }
47
48    None
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[tokio::test]
56    async fn parse_to_xml_object_json() {
57        let dir = tempfile::tempdir().unwrap();
58        let path = dir.path().join("test.json");
59        std::fs::write(&path, r#"{"root":{"a":1}}"#).unwrap();
60        let out = parse_to_xml_object(path.to_str().unwrap()).await;
61        assert!(out.is_some());
62        let obj = out.unwrap();
63        assert!(obj.get("root").is_some());
64    }
65
66    #[tokio::test]
67    async fn parse_to_xml_object_yaml() {
68        let dir = tempfile::tempdir().unwrap();
69        let path = dir.path().join("test.yaml");
70        std::fs::write(&path, "root:\n  a: 1\n").unwrap();
71        let out = parse_to_xml_object(path.to_str().unwrap()).await;
72        assert!(out.is_some());
73    }
74
75    #[tokio::test]
76    async fn parse_to_xml_object_yml_extension() {
77        let dir = tempfile::tempdir().unwrap();
78        let path = dir.path().join("test.yml");
79        std::fs::write(&path, "root: {}").unwrap();
80        let out = parse_to_xml_object(path.to_str().unwrap()).await;
81        assert!(out.is_some());
82    }
83
84    #[tokio::test]
85    async fn parse_to_xml_object_json5() {
86        let dir = tempfile::tempdir().unwrap();
87        let path = dir.path().join("test.json5");
88        std::fs::write(&path, "{ root: { a: 1 } }").unwrap();
89        let out = parse_to_xml_object(path.to_str().unwrap()).await;
90        assert!(out.is_some());
91    }
92
93    #[tokio::test]
94    async fn parse_to_xml_object_unsupported_returns_none() {
95        let dir = tempfile::tempdir().unwrap();
96        let path = dir.path().join("test.txt");
97        std::fs::write(&path, "not xml").unwrap();
98        let out = parse_to_xml_object(path.to_str().unwrap()).await;
99        assert!(out.is_none());
100    }
101
102    #[tokio::test]
103    async fn parse_to_xml_object_xml_with_declaration_and_xmlns() {
104        let dir = tempfile::tempdir().unwrap();
105        let path = dir.path().join("meta.xml");
106        let xml = r#"<?xml version="1.0" encoding="UTF-8"?><root xmlns="http://example.com"><a>1</a></root>"#;
107        std::fs::write(&path, xml).unwrap();
108        let out = parse_to_xml_object(path.to_str().unwrap()).await;
109        assert!(out.is_some());
110        let obj = out.unwrap();
111        assert!(obj.get("?xml").is_some());
112        let root = obj.get("root").and_then(|r| r.as_object()).unwrap();
113        assert_eq!(
114            root.get("@xmlns").and_then(|v| v.as_str()),
115            Some("http://example.com")
116        );
117    }
118}