Skip to main content

xml_disassembler/builders/
build_xml_string.rs

1//! Build XML string from XmlElement structure.
2
3use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
4use quick_xml::Writer;
5use serde_json::{Map, Value};
6
7use crate::types::XmlElement;
8
9fn value_to_string(v: &Value) -> String {
10    match v {
11        Value::String(s) => s.clone(),
12        Value::Number(n) => n.to_string(),
13        Value::Bool(b) => b.to_string(),
14        Value::Null => String::new(),
15        _ => serde_json::to_string(v).unwrap_or_default(),
16    }
17}
18
19fn write_element<W: std::io::Write>(
20    writer: &mut Writer<W>,
21    name: &str,
22    content: &Value,
23    indent_level: usize,
24) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
25    let indent = "    ".repeat(indent_level);
26    let child_indent = "    ".repeat(indent_level + 1);
27
28    match content {
29        Value::Object(obj) => {
30            let (attrs, children): (Vec<_>, Vec<_>) =
31                obj.iter().partition(|(k, _)| k.starts_with('@'));
32
33            let attr_name = |k: &str| k.trim_start_matches('@').to_string();
34
35            let mut text_content = String::new();
36            let child_elements: Vec<(&String, &Value)> = children
37                .iter()
38                .filter_map(|(k, v)| {
39                    if *k == "#text" {
40                        text_content = value_to_string(v);
41                        None
42                    } else {
43                        Some((*k, *v))
44                    }
45                })
46                .collect();
47
48            let has_children = child_elements.iter().any(|(_, v)| {
49                v.is_object()
50                    || (v.is_array() && v.as_array().map(|a| !a.is_empty()).unwrap_or(false))
51            });
52
53            let attrs: Vec<(String, String)> = attrs
54                .iter()
55                .map(|(k, v)| (attr_name(k), value_to_string(v)))
56                .collect();
57
58            let mut start = BytesStart::new(name);
59            for (k, v) in &attrs {
60                start.push_attribute((k.as_str(), v.as_str()));
61            }
62            writer.write_event(Event::Start(start))?;
63
64            if has_children || !child_elements.is_empty() {
65                writer.write_event(Event::Text(BytesText::new(
66                    format!("\n{}", child_indent).as_str(),
67                )))?;
68
69                let child_count = child_elements.len();
70                for (idx, (child_name, child_value)) in child_elements.iter().enumerate() {
71                    let is_last = idx == child_count - 1;
72                    match child_value {
73                        Value::Array(arr) => {
74                            let arr_len = arr.len();
75                            for (i, item) in arr.iter().enumerate() {
76                                let arr_last = i == arr_len - 1;
77                                write_element(writer, child_name, item, indent_level + 1)?;
78                                if !arr_last {
79                                    writer.write_event(Event::Text(BytesText::new(
80                                        format!("\n{}", child_indent).as_str(),
81                                    )))?;
82                                }
83                            }
84                            if !is_last {
85                                writer.write_event(Event::Text(BytesText::new(
86                                    format!("\n{}", child_indent).as_str(),
87                                )))?;
88                            }
89                        }
90                        Value::Object(_) => {
91                            write_element(writer, child_name, child_value, indent_level + 1)?;
92                            if !is_last {
93                                writer.write_event(Event::Text(BytesText::new(
94                                    format!("\n{}", child_indent).as_str(),
95                                )))?;
96                            }
97                        }
98                        _ => {
99                            writer
100                                .write_event(Event::Start(BytesStart::new(child_name.as_str())))?;
101                            // BytesText::new() expects unescaped content; the writer escapes when writing
102                            writer.write_event(Event::Text(BytesText::new(
103                                value_to_string(child_value).as_str(),
104                            )))?;
105                            writer.write_event(Event::End(BytesEnd::new(child_name.as_str())))?;
106                            if !is_last {
107                                writer.write_event(Event::Text(BytesText::new(
108                                    format!("\n{}", child_indent).as_str(),
109                                )))?;
110                            }
111                        }
112                    }
113                }
114
115                writer.write_event(Event::Text(BytesText::new(
116                    format!("\n{}", indent).as_str(),
117                )))?;
118            } else if !text_content.is_empty() {
119                // BytesText::new() expects unescaped content; the writer escapes when writing
120                writer.write_event(Event::Text(BytesText::new(text_content.as_str())))?;
121            }
122
123            writer.write_event(Event::End(BytesEnd::new(name)))?;
124        }
125        Value::Array(arr) => {
126            for item in arr {
127                write_element(writer, name, item, indent_level)?;
128            }
129        }
130        _ => {
131            writer.write_event(Event::Start(BytesStart::new(name)))?;
132            // BytesText::new() expects unescaped content; the writer escapes when writing
133            writer.write_event(Event::Text(BytesText::new(
134                value_to_string(content).as_str(),
135            )))?;
136            writer.write_event(Event::End(BytesEnd::new(name)))?;
137        }
138    }
139
140    Ok(())
141}
142
143fn build_xml_from_object(
144    element: &Map<String, Value>,
145) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
146    let mut writer = Writer::new_with_indent(Vec::new(), b' ', 4);
147
148    let (declaration, root_key, root_value) = if let Some(decl) = element.get("?xml") {
149        let root_key = element
150            .keys()
151            .find(|k| *k != "?xml")
152            .cloned()
153            .unwrap_or_else(|| "root".to_string());
154        let root_value = element
155            .get(&root_key)
156            .cloned()
157            .unwrap_or_else(|| Value::Object(Map::new()));
158        (Some(decl), root_key, root_value)
159    } else {
160        let root_key = element
161            .keys()
162            .next()
163            .cloned()
164            .unwrap_or_else(|| "root".to_string());
165        let root_value = element
166            .get(&root_key)
167            .cloned()
168            .unwrap_or_else(|| Value::Object(Map::new()));
169        (None, root_key, root_value)
170    };
171
172    if declaration.is_some() {
173        if let Some(decl) = declaration {
174            if let Some(obj) = decl.as_object() {
175                let version = obj
176                    .get("@version")
177                    .and_then(|v| v.as_str())
178                    .unwrap_or("1.0");
179                let encoding = obj.get("@encoding").and_then(|v| v.as_str());
180                let standalone = obj.get("@standalone").and_then(|v| v.as_str());
181                writer.write_event(Event::Decl(BytesDecl::new(version, encoding, standalone)))?;
182            }
183        }
184    }
185
186    write_element(&mut writer, &root_key, &root_value, 0)?;
187
188    let result = String::from_utf8(writer.into_inner())?;
189    Ok(result.trim_end().to_string())
190}
191
192/// Build XML string from XmlElement.
193pub fn build_xml_string(element: &XmlElement) -> String {
194    match element {
195        Value::Object(obj) => build_xml_from_object(obj).unwrap_or_default(),
196        _ => String::new(),
197    }
198}