node2object/
lib.rs

1//! Convert between XML nodes ([treexml](https://github.com/rahulg/treexml-rs)) and JSON objects ([serde-json](https://github.com/serde-rs/json)).
2//!
3//! ## Example
4//! ```
5//! extern crate treexml;
6//!
7//! #[macro_use]
8//! extern crate serde_json;
9//!
10//! extern crate node2object;
11//!
12//! fn main() {
13//!     let dom_root = treexml::Document::parse("
14//!         <population>
15//!           <entry>
16//!             <name>Alex</name>
17//!             <height>173.5</height>
18//!           </entry>
19//!           <entry>
20//!             <name>Mel</name>
21//!             <height>180.4</height>
22//!           </entry>
23//!         </population>
24//!     ".as_bytes()).unwrap().root.unwrap();
25//!
26//!     assert_eq!(serde_json::Value::Object(node2object::node2object(&dom_root)), json!(
27//!         {
28//!           "population": {
29//!             "entry": [
30//!               { "name": "Alex", "height": 173.5 },
31//!               { "name": "Mel", "height": 180.4 }
32//!             ]
33//!           }
34//!         }
35//!     ));
36//! }
37//! ```
38
39extern crate treexml;
40
41#[macro_use]
42extern crate serde_json;
43
44use serde_json::{Map, Number, Value};
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47enum XMLNodeType {
48    Empty,
49    Text,
50    Attributes,
51    TextAndAttributes,
52    Parent,
53    SemiStructured,
54}
55
56fn scan_xml_node(e: &treexml::Element) -> XMLNodeType {
57    if e.children.is_empty() {
58        if e.text.is_none() && e.cdata.is_none() {
59            if e.attributes.is_empty() {
60                XMLNodeType::Empty
61            } else {
62                XMLNodeType::Attributes
63            }
64        } else {
65            if e.attributes.is_empty() {
66                XMLNodeType::Text
67            } else {
68                XMLNodeType::TextAndAttributes
69            }
70        }
71    } else {
72        if e.text.is_some() || e.cdata.is_some() {
73            XMLNodeType::SemiStructured
74        } else {
75            XMLNodeType::Parent
76        }
77    }
78}
79
80fn parse_text(text: &str) -> Value {
81    match text.parse::<f64>() {
82        Ok(v) => match Number::from_f64(v) {
83            Some(v) => {
84                return Value::Number(v);
85            }
86            _ => {}
87        },
88        _ => {}
89    }
90
91    match text.parse::<bool>() {
92        Ok(v) => {
93            return Value::Bool(v);
94        }
95        _ => {}
96    }
97
98    Value::String(text.into())
99}
100
101fn parse_text_contents(e: &treexml::Element) -> Value {
102    let text = format!(
103        "{}{}",
104        &e.text.clone().unwrap_or(String::new()),
105        &e.cdata.clone().unwrap_or(String::new())
106    );
107    parse_text(&text)
108}
109
110fn convert_node_aux(e: &treexml::Element) -> Option<Value> {
111    match scan_xml_node(e) {
112        XMLNodeType::Parent => {
113            let mut data = Map::new();
114            let mut firstpass = std::collections::HashSet::new();
115            let mut vectorized = std::collections::HashSet::new();
116
117            for c in &e.children {
118                match convert_node_aux(c) {
119                    Some(v) => {
120                        if !firstpass.contains(&c.name) {
121                            data.insert(c.name.clone(), v);
122                            firstpass.insert(c.name.clone());
123                        } else {
124                            if !vectorized.contains(&c.name) {
125                                let elem = data.remove(&c.name).unwrap();
126                                data.insert(c.name.clone(), Value::Array(vec![elem, v]));
127                                vectorized.insert(c.name.clone());
128                            } else {
129                                data.get_mut(&c.name)
130                                    .unwrap()
131                                    .as_array_mut()
132                                    .unwrap()
133                                    .push(v);
134                            }
135                        }
136                    }
137                    _ => {}
138                }
139            }
140            Some(Value::Object(data))
141        }
142        XMLNodeType::Text => Some(parse_text_contents(e)),
143        XMLNodeType::Attributes => Some(Value::Object(
144            e.attributes
145                .clone()
146                .into_iter()
147                .map(|(k, v)| (format!("@{}", k), parse_text(&v)))
148                .collect(),
149        )),
150        XMLNodeType::TextAndAttributes => Some(Value::Object(
151            e.attributes
152                .clone()
153                .into_iter()
154                .map(|(k, v)| (format!("@{}", k), parse_text(&v)))
155                .chain(vec![("#text".to_string(), parse_text_contents(&e))])
156                .collect(),
157        )),
158        _ => None,
159    }
160}
161
162/// Converts treexml::Element into a serde_json hashmap. The latter can be wrapped in Value::Object.
163pub fn node2object(e: &treexml::Element) -> Map<String, Value> {
164    let mut data = Map::new();
165    data.insert(e.name.clone(), convert_node_aux(e).unwrap_or(Value::Null));
166    data
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    #[test]
174    fn node2object_empty() {
175        let fixture = treexml::Element::new("e");
176        let scan_result = XMLNodeType::Empty;
177        let conv_result = json!({ "e": null });
178
179        assert_eq!(scan_result, scan_xml_node(&fixture));
180        assert_eq!(conv_result, Value::Object(node2object(&fixture)));
181    }
182
183    #[test]
184    fn node2object_text() {
185        let mut fixture = treexml::Element::new("player");
186        fixture.text = Some("Kolya".into());
187        let scan_result = XMLNodeType::Text;
188        let conv_result = json!({"player": "Kolya"});
189
190        assert_eq!(scan_result, scan_xml_node(&fixture));
191        assert_eq!(conv_result, Value::Object(node2object(&fixture)));
192    }
193
194    #[test]
195    fn node2object_attributes() {
196        let mut fixture = treexml::Element::new("player");
197        fixture.attributes.insert("score".into(), "9000".into());
198        let scan_result = XMLNodeType::Attributes;
199        let conv_result = json!({ "player": json!({"@score": 9000.0}) });
200
201        assert_eq!(scan_result, scan_xml_node(&fixture));
202        assert_eq!(conv_result, Value::Object(node2object(&fixture)));
203    }
204
205    #[test]
206    fn node2object_text_and_attributes() {
207        let mut fixture = treexml::Element::new("player");
208        fixture.text = Some("Kolya".into());
209        fixture.attributes.insert("score".into(), "9000".into());
210        let scan_result = XMLNodeType::TextAndAttributes;
211        let conv_result = json!({ "player": json!({"#text": "Kolya", "@score": 9000.0}) });
212
213        assert_eq!(scan_result, scan_xml_node(&fixture));
214        assert_eq!(conv_result, Value::Object(node2object(&fixture)));
215    }
216
217    #[test]
218    fn node2object_parent() {
219        let mut fixture = treexml::Element::new("ServerData");
220        fixture.children = vec![
221            {
222                let mut node = treexml::Element::new("Player");
223                node.text = Some("Kolya".into());
224                node
225            },
226            {
227                let mut node = treexml::Element::new("Player");
228                node.text = Some("Petya".into());
229                node
230            },
231            {
232                let mut node = treexml::Element::new("Player");
233                node.text = Some("Misha".into());
234                node
235            },
236        ];
237        let scan_result = XMLNodeType::Parent;
238        let conv_result =
239            json!({ "ServerData": json!({ "Player": [ "Kolya", "Petya", "Misha" ] }) });
240
241        assert_eq!(scan_result, scan_xml_node(&fixture));
242        assert_eq!(conv_result, Value::Object(node2object(&fixture)));
243    }
244}