config_disassembler/xml/parsers/
parse_xml.rs1use serde_json::Value;
4use tokio::fs;
5
6use crate::xml::parsers::parse_xml_cdata;
7use crate::xml::parsers::{mark_compact_elements, strip_whitespace_text_nodes};
8use crate::xml::types::XmlElement;
9
10pub async fn parse_xml(file_path: &str) -> Option<XmlElement> {
12 let content = match fs::read_to_string(file_path).await {
13 Ok(c) => c,
14 Err(e) => {
15 log::error!(
16 "{} was unable to be parsed and will not be processed. Confirm formatting and try again.",
17 file_path
18 );
19 log::debug!("Parse error: {}", e);
20 return None;
21 }
22 };
23 parse_xml_from_str(&content, file_path)
24}
25
26pub fn parse_xml_from_str(content: &str, file_path: &str) -> Option<XmlElement> {
29 let mut parsed: Value = match parse_xml_cdata::parse_xml_with_cdata(content) {
30 Ok(v) => v,
31 Err(e) => {
32 log::error!(
33 "{} was unable to be parsed and will not be processed. Confirm formatting and try again.",
34 file_path
35 );
36 log::debug!("Parse error: {}", e);
37 return None;
38 }
39 };
40
41 let root_children = parsed
51 .as_object_mut()
52 .expect("parse_xml_with_cdata always returns an object");
53 for value in root_children.values_mut() {
54 mark_compact_elements(value);
55 }
56
57 let cleaned = strip_whitespace_text_nodes(&parsed);
58 Some(cleaned)
59}
60
61pub fn extract_xmlns_from_raw(xml_content: &str) -> Option<String> {
64 let re = regex::Regex::new(r#"xmlns="([^"]*)""#).ok()?;
65 re.captures(xml_content).map(|c| c[1].to_string())
66}
67
68pub fn extract_xml_declaration_from_raw(xml_content: &str) -> Option<XmlElement> {
72 let decl_re = regex::Regex::new(r#"<\?xml\s+([^?]+)\?>"#).ok()?;
73 let decl_content = decl_re.captures(xml_content)?.get(1)?.as_str();
74 let mut decl = serde_json::Map::new();
75 let version_re = regex::Regex::new(r#"version="([^"]*)""#).ok()?;
76 {
77 let cap = version_re.captures(decl_content)?;
78 decl.insert("@version".to_string(), Value::String(cap[1].to_string()));
79 }
80 let encoding_re = regex::Regex::new(r#"encoding="([^"]*)""#).ok()?;
81 if let Some(cap) = encoding_re.captures(decl_content) {
82 decl.insert("@encoding".to_string(), Value::String(cap[1].to_string()));
83 }
84 let standalone_re = regex::Regex::new(r#"standalone="([^"]*)""#).ok()?;
85 if let Some(cap) = standalone_re.captures(decl_content) {
86 decl.insert("@standalone".to_string(), Value::String(cap[1].to_string()));
87 }
88 Some(Value::Object(decl))
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn extract_xmlns_from_raw_finds_namespace() {
97 let xml = r#"<root xmlns="http://soap.sforce.com/2006/04/metadata"><a/></root>"#;
98 assert_eq!(
99 extract_xmlns_from_raw(xml),
100 Some("http://soap.sforce.com/2006/04/metadata".to_string())
101 );
102 }
103
104 #[test]
105 fn extract_xmlns_from_raw_returns_none_when_absent() {
106 let xml = r#"<root><a/></root>"#;
107 assert_eq!(extract_xmlns_from_raw(xml), None);
108 }
109
110 #[test]
111 fn extract_xml_declaration_from_raw_parses_version_and_encoding() {
112 let xml = r#"<?xml version="1.0" encoding="UTF-8"?><root/>"#;
113 let decl = extract_xml_declaration_from_raw(xml).unwrap();
114 let obj = decl.as_object().unwrap();
115 assert_eq!(obj.get("@version").and_then(|v| v.as_str()), Some("1.0"));
116 assert_eq!(obj.get("@encoding").and_then(|v| v.as_str()), Some("UTF-8"));
117 }
118
119 #[test]
120 fn extract_xml_declaration_from_raw_parses_standalone() {
121 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><root/>"#;
122 let decl = extract_xml_declaration_from_raw(xml).unwrap();
123 let obj = decl.as_object().unwrap();
124 assert_eq!(obj.get("@standalone").and_then(|v| v.as_str()), Some("yes"));
125 }
126
127 #[test]
128 fn extract_xml_declaration_from_raw_returns_none_without_declaration() {
129 let xml = r#"<root/>"#;
130 assert!(extract_xml_declaration_from_raw(xml).is_none());
131 }
132
133 #[test]
134 fn extract_xml_declaration_from_raw_returns_none_when_version_missing() {
135 let xml = r#"<?xml encoding="UTF-8"?><root/>"#;
136 assert!(extract_xml_declaration_from_raw(xml).is_none());
137 }
138
139 #[test]
140 fn extract_xml_declaration_from_raw_version_only_no_encoding_no_standalone() {
141 let xml = r#"<?xml version="1.0"?><root/>"#;
144 let decl = extract_xml_declaration_from_raw(xml).unwrap();
145 let obj = decl.as_object().unwrap();
146 assert_eq!(obj.get("@version").and_then(|v| v.as_str()), Some("1.0"));
147 assert!(
148 obj.get("@encoding").is_none(),
149 "encoding must be absent when not in source"
150 );
151 assert!(
152 obj.get("@standalone").is_none(),
153 "standalone must be absent when not in source"
154 );
155 }
156
157 #[test]
158 fn parse_xml_from_str_invalid_xml_returns_none() {
159 let result = parse_xml_from_str("<<", "test.xml");
160 assert!(result.is_none());
161 }
162
163 #[tokio::test]
164 async fn parse_xml_missing_file_returns_none() {
165 let result = parse_xml("/nonexistent/path/file.xml").await;
166 assert!(result.is_none());
167 }
168}