svg_simple_parser/
stringify.rs

1use std::{cell::RefCell, collections::HashMap, rc::Rc};
2
3use crate::ast::Element;
4
5const TAB: &str = "  ";
6const LINE: &str = "\r\n";
7
8/// hashmap to string
9#[inline(always)]
10fn stringify_attrubutes_hash(attribute_hash: &RefCell<HashMap<String, &str>>) -> String {
11    let mut arr: Vec<(String, &str)> = attribute_hash
12        .borrow_mut()
13        .iter()
14        .map(|(k, v)| ((*k).clone(), *v))
15        .collect();
16    arr.sort_by(|(ak, _), (bk, _)| ak.cmp(bk));
17    arr.iter()
18        .fold("".to_string(), |c, (k, v)| format!("{} {}=\"{}\"", c, k, v))
19}
20
21/// element to string
22fn trasverse(
23    ele: Rc<Element>,
24    z: usize,
25    (tab_mark, line_mark): (&'static str, &'static str),
26) -> String {
27    let Element {
28        ele_type,
29        attributes,
30        parent: _,
31        children,
32    } = &*ele;
33    let attrs_str = stringify_attrubutes_hash(attributes);
34    let content = if children.borrow().is_empty() {
35        format!(
36            "{}<{}{}/>{}",
37            tab_mark.repeat(z),
38            ele_type,
39            attrs_str,
40            line_mark
41        )
42    } else {
43        let children_str = children
44            .borrow()
45            .iter()
46            .map(|t| trasverse(t.clone(), z + 1, (tab_mark, line_mark)))
47            .collect::<Vec<String>>()
48            .join("");
49        format!(
50            "{}<{}{}>{}{}{}</{}>{}",
51            tab_mark.repeat(z),
52            ele_type,
53            attrs_str,
54            line_mark,
55            children_str,
56            tab_mark.repeat(z),
57            ele_type,
58            line_mark
59        )
60    };
61    content
62}
63
64/// transform a Element(AST struct) to svg
65///
66/// return string.
67///
68/// ## Example
69/// ```rust
70/// use svg_simple_parser::{Element,stringify};
71/// use std::collections::HashMap;
72///
73/// let root = Element::new_width_children((
74///   "rect",
75///   HashMap::from([
76///     ("width".to_owned(),"100"),
77///   ]),
78///   vec![
79///     Element::new_width_children((
80///       "rect",
81///       HashMap::from([
82///         ("width".to_owned(),"100"),
83///       ]),
84///       vec![],
85///     )),
86///   ],
87/// ));
88/// let svg = stringify(root);
89/// assert_eq!(svg,"<rect width=\"100\"><rect width=\"100\"/></rect>");
90/// ```
91///
92pub fn stringify(ele: Rc<Element>) -> String {
93    trasverse(ele, 0, ("", ""))
94}
95
96/// transform a Element(AST struct) to svg with pretty format
97///
98/// return string.
99///
100/// ## Example
101/// ```rust
102/// use svg_simple_parser::{Element,stringify_pretty};
103/// use std::collections::HashMap;
104///
105/// let root = Element::new_width_children((
106///   "rect",
107///   HashMap::from([
108///     ("width".to_owned(),"100"),
109///   ]),
110///   vec![
111///     Element::new_width_children((
112///       "rect",
113///       HashMap::from([
114///         ("width".to_owned(),"100"),
115///       ]),
116///       vec![],
117///     )),
118///   ],
119/// ));
120/// let svg = stringify_pretty(root);
121/// assert_eq!(svg,"<rect width=\"100\">\r\n  <rect width=\"100\"/>\r\n</rect>\r\n");
122/// ```
123///
124pub fn stringify_pretty(ele: Rc<Element>) -> String {
125    trasverse(ele, 0, (TAB, LINE))
126}
127
128#[cfg(test)]
129mod tests {
130    use std::{cell::RefCell, collections::HashMap};
131
132    use crate::{
133        stringify::{stringify_attrubutes_hash, trasverse},
134        Element,
135    };
136
137    #[test]
138    fn test_stringify_attrubutes_hash() {
139        let attrs = RefCell::new(HashMap::from([
140            ("cx".to_owned(), "100"),
141            ("cy".to_owned(), "50"),
142            ("r".to_owned(), "40"),
143            ("stroke".to_owned(), "black"),
144            ("stroke-width".to_owned(), "2"),
145            ("fill".to_owned(), "red"),
146        ]));
147        assert_eq!(
148            stringify_attrubutes_hash(&attrs),
149            r#" cx="100" cy="50" fill="red" r="40" stroke="black" stroke-width="2""#
150        );
151    }
152
153    #[test]
154    fn test_trasverse() {
155        let root = Element::new_width_children((
156            "svg",
157            HashMap::from([
158                ("xmlns".to_owned(), "http://www.w3.org/2000/svg"),
159                ("version".to_owned(), "1.1"),
160            ]),
161            vec![Element::new((
162                "circle",
163                HashMap::from([
164                    ("cx".to_owned(), "100"),
165                    ("cy".to_owned(), "50"),
166                    ("r".to_owned(), "40"),
167                    ("stroke".to_owned(), "black"),
168                    ("stroke-width".to_owned(), "2"),
169                    ("fill".to_owned(), "red"),
170                ]),
171            ))],
172        ));
173        assert_eq!(
174            trasverse(root.clone(), 0, ("", "")),
175            r#"<svg version="1.1" xmlns="http://www.w3.org/2000/svg"><circle cx="100" cy="50" fill="red" r="40" stroke="black" stroke-width="2"/></svg>"#
176        );
177
178        assert_eq!(
179            trasverse(root.clone(), 0, ("  ", "\r\n")),
180            "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\r\n  <circle cx=\"100\" cy=\"50\" fill=\"red\" r=\"40\" stroke=\"black\" stroke-width=\"2\"/>\r\n</svg>\r\n"
181        );
182    }
183}