xml_disassembler/
multi_level.rs1use serde_json::{Map, Value};
4
5use crate::builders::build_xml_string;
6use crate::types::{MultiLevelConfig, XmlElement};
7
8pub fn strip_root_and_build_xml(parsed: &XmlElement, element_to_strip: &str) -> Option<String> {
13 let obj = parsed.as_object()?;
14 let root_key = obj.keys().find(|k| *k != "?xml")?.clone();
15 let root_val = obj.get(&root_key)?.as_object()?;
16 let decl = obj.get("?xml").cloned().unwrap_or_else(|| {
17 let mut d = Map::new();
18 d.insert("@version".to_string(), Value::String("1.0".to_string()));
19 d.insert("@encoding".to_string(), Value::String("UTF-8".to_string()));
20 Value::Object(d)
21 });
22
23 if root_key == element_to_strip {
24 let mut new_obj = Map::new();
26 new_obj.insert("?xml".to_string(), decl);
27 for (k, v) in root_val {
28 new_obj.insert(k.clone(), v.clone());
29 }
30 return Some(build_xml_string(&Value::Object(new_obj)));
31 }
32
33 let inner = root_val.get(element_to_strip)?.as_object()?;
35 let mut new_root_val = Map::new();
36 for (k, v) in root_val {
37 if k != element_to_strip {
38 new_root_val.insert(k.clone(), v.clone());
39 }
40 }
41 for (k, v) in inner {
42 new_root_val.insert(k.clone(), v.clone());
43 }
44 let mut new_obj = Map::new();
45 new_obj.insert("?xml".to_string(), decl);
46 new_obj.insert(root_key, Value::Object(new_root_val));
47 Some(build_xml_string(&Value::Object(new_obj)))
48}
49
50pub fn capture_xmlns_from_root(parsed: &XmlElement) -> Option<String> {
52 let obj = parsed.as_object()?;
53 let root_key = obj.keys().find(|k| *k != "?xml")?.clone();
54 let root_val = obj.get(&root_key)?.as_object()?;
55 let xmlns = root_val.get("@xmlns")?.as_str()?;
56 Some(xmlns.to_string())
57}
58
59pub fn path_segment_from_file_pattern(file_pattern: &str) -> String {
61 if let Some(prefix) = file_pattern.split('-').next() {
62 prefix.to_string()
63 } else {
64 file_pattern.to_string()
65 }
66}
67
68pub async fn load_multi_level_config(dir_path: &std::path::Path) -> Option<MultiLevelConfig> {
70 let path = dir_path.join(".multi_level.json");
71 let content = tokio::fs::read_to_string(&path).await.ok()?;
72 serde_json::from_str(&content).ok()
73}
74
75pub async fn save_multi_level_config(
77 dir_path: &std::path::Path,
78 config: &MultiLevelConfig,
79) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
80 let path = dir_path.join(".multi_level.json");
81 let content = serde_json::to_string_pretty(config)?;
82 tokio::fs::write(path, content).await?;
83 Ok(())
84}
85
86pub async fn ensure_segment_files_structure(
90 dir_path: &std::path::Path,
91 document_root: &str,
92 inner_wrapper: &str,
93 xmlns: &str,
94) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
95 use crate::parsers::parse_xml_from_str;
96 use serde_json::Map;
97
98 let mut entries = Vec::new();
99 let mut read_dir = tokio::fs::read_dir(dir_path).await?;
100 while let Some(entry) = read_dir.next_entry().await? {
101 entries.push(entry);
102 }
103 entries.sort_by_key(|e| e.file_name());
105
106 for entry in entries {
107 let path = entry.path();
108 if !path.is_file() {
109 continue;
110 }
111 let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
112 if !name.ends_with(".xml") {
113 continue;
114 }
115 let path_str = path.to_string_lossy();
116 let content = match tokio::fs::read_to_string(&path).await {
117 Ok(c) => c,
118 Err(_) => continue,
119 };
120 let parsed = match parse_xml_from_str(&content, &path_str) {
121 Some(p) => p,
122 None => continue,
123 };
124 let obj = match parsed.as_object() {
125 Some(o) => o,
126 None => continue,
127 };
128 let root_key = obj.keys().find(|k| *k != "?xml").cloned();
129 let Some(current_root_key) = root_key else {
130 continue;
131 };
132 let root_val = obj
133 .get(¤t_root_key)
134 .and_then(|v| v.as_object())
135 .cloned();
136 let Some(root_val) = root_val else {
137 continue;
138 };
139
140 let decl = obj.get("?xml").cloned().unwrap_or_else(|| {
141 let mut d = Map::new();
142 d.insert(
143 "@version".to_string(),
144 serde_json::Value::String("1.0".to_string()),
145 );
146 d.insert(
147 "@encoding".to_string(),
148 serde_json::Value::String("UTF-8".to_string()),
149 );
150 serde_json::Value::Object(d)
151 });
152
153 let non_attr_keys: Vec<&String> = root_val.keys().filter(|k| *k != "@xmlns").collect();
154 let single_inner = non_attr_keys.len() == 1 && non_attr_keys[0].as_str() == inner_wrapper;
155 let inner_content: serde_json::Value = if current_root_key == document_root && single_inner
156 {
157 let inner_obj = root_val
158 .get(inner_wrapper)
159 .and_then(|v| v.as_object())
160 .cloned()
161 .unwrap_or_else(Map::new);
162 let mut inner_clean = Map::new();
163 for (k, v) in &inner_obj {
164 if k != "@xmlns" {
165 inner_clean.insert(k.clone(), v.clone());
166 }
167 }
168 serde_json::Value::Object(inner_clean)
169 } else {
170 serde_json::Value::Object(root_val.clone())
171 };
172
173 let already_correct = current_root_key == document_root
174 && root_val.get("@xmlns").is_some()
175 && single_inner
176 && root_val
177 .get(inner_wrapper)
178 .and_then(|v| v.as_object())
179 .map(|o| !o.contains_key("@xmlns"))
180 .unwrap_or(true);
181 if already_correct {
182 continue;
183 }
184
185 let mut root_val_new = Map::new();
187 if !xmlns.is_empty() {
188 root_val_new.insert(
189 "@xmlns".to_string(),
190 serde_json::Value::String(xmlns.to_string()),
191 );
192 }
193 root_val_new.insert(inner_wrapper.to_string(), inner_content);
194
195 let mut top = Map::new();
196 top.insert("?xml".to_string(), decl);
197 top.insert(
198 document_root.to_string(),
199 serde_json::Value::Object(root_val_new),
200 );
201 let wrapped = serde_json::Value::Object(top);
202 let xml_string = build_xml_string(&wrapped);
203 tokio::fs::write(&path, xml_string).await?;
204 }
205 Ok(())
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn path_segment_from_file_pattern_strips_suffix() {
214 assert_eq!(
215 path_segment_from_file_pattern("programProcesses-meta"),
216 "programProcesses"
217 );
218 }
219
220 #[test]
221 fn path_segment_from_file_pattern_no_dash() {
222 assert_eq!(path_segment_from_file_pattern("foo"), "foo");
223 }
224}