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::{extract_xml_declaration_from_raw, parse_xml_from_str};
4use crate::types::XmlElement;
5use tokio::fs;
6
7pub async fn parse_to_xml_object(file_path: &str) -> Option<XmlElement> {
8    if file_path.to_lowercase().ends_with(".xml") {
9        let content = fs::read_to_string(file_path).await.ok()?;
10        let mut parsed = parse_xml_from_str(&content, file_path)?;
11        // The declaration is not preserved by the parser - extract from raw XML.
12        // `parse_xml_from_str` always yields an object, so `as_object_mut` is safe.
13        if let (Some(obj), Some(decl)) = (
14            parsed.as_object_mut(),
15            extract_xml_declaration_from_raw(&content),
16        ) {
17            obj.insert("?xml".to_string(), decl);
18        }
19        return Some(parsed);
20    }
21
22    let content = fs::read_to_string(file_path).await.ok()?;
23
24    if file_path.to_lowercase().ends_with(".yaml") || file_path.to_lowercase().ends_with(".yml") {
25        return serde_yaml::from_str(&content).ok();
26    }
27
28    if file_path.to_lowercase().ends_with(".json5") {
29        return json5::from_str(&content).ok();
30    }
31
32    if file_path.to_lowercase().ends_with(".json") {
33        return serde_json::from_str(&content).ok();
34    }
35
36    None
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    #[tokio::test]
44    async fn parse_to_xml_object_json() {
45        let dir = tempfile::tempdir().unwrap();
46        let path = dir.path().join("test.json");
47        std::fs::write(&path, r#"{"root":{"a":1}}"#).unwrap();
48        let out = parse_to_xml_object(path.to_str().unwrap()).await;
49        assert!(out.is_some());
50        let obj = out.unwrap();
51        assert!(obj.get("root").is_some());
52    }
53
54    #[tokio::test]
55    async fn parse_to_xml_object_yaml() {
56        let dir = tempfile::tempdir().unwrap();
57        let path = dir.path().join("test.yaml");
58        std::fs::write(&path, "root:\n  a: 1\n").unwrap();
59        let out = parse_to_xml_object(path.to_str().unwrap()).await;
60        assert!(out.is_some());
61    }
62
63    #[tokio::test]
64    async fn parse_to_xml_object_yml_extension() {
65        let dir = tempfile::tempdir().unwrap();
66        let path = dir.path().join("test.yml");
67        std::fs::write(&path, "root: {}").unwrap();
68        let out = parse_to_xml_object(path.to_str().unwrap()).await;
69        assert!(out.is_some());
70    }
71
72    #[tokio::test]
73    async fn parse_to_xml_object_json5() {
74        let dir = tempfile::tempdir().unwrap();
75        let path = dir.path().join("test.json5");
76        std::fs::write(&path, "{ root: { a: 1 } }").unwrap();
77        let out = parse_to_xml_object(path.to_str().unwrap()).await;
78        assert!(out.is_some());
79    }
80
81    #[tokio::test]
82    async fn parse_to_xml_object_unsupported_returns_none() {
83        let dir = tempfile::tempdir().unwrap();
84        let path = dir.path().join("test.txt");
85        std::fs::write(&path, "not xml").unwrap();
86        let out = parse_to_xml_object(path.to_str().unwrap()).await;
87        assert!(out.is_none());
88    }
89
90    #[tokio::test]
91    async fn parse_to_xml_object_xml_with_declaration_and_xmlns() {
92        let dir = tempfile::tempdir().unwrap();
93        let path = dir.path().join("meta.xml");
94        let xml = r#"<?xml version="1.0" encoding="UTF-8"?><root xmlns="http://example.com"><a>1</a></root>"#;
95        std::fs::write(&path, xml).unwrap();
96        let out = parse_to_xml_object(path.to_str().unwrap()).await;
97        assert!(out.is_some());
98        let obj = out.unwrap();
99        assert!(obj.get("?xml").is_some());
100        let root = obj.get("root").and_then(|r| r.as_object()).unwrap();
101        assert_eq!(
102            root.get("@xmlns").and_then(|v| v.as_str()),
103            Some("http://example.com")
104        );
105    }
106}