1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use error_chain::bail;
use flange_flat_tree::Tree;
use regex::RegexBuilder;
use svg::parser::Event;
use svg::Parser;

use super::Tag;
use super::TreeHash;
use crate::diff::MatchingState;
use crate::errors::*;
use flange_flat_tree::Builder;
use flange_flat_tree::VecTree;

pub struct SVG {
    pub tags: VecTree<Tag>,
}

pub type SVGWithIDs<'a> = flange_flat_tree::FlangedTree<&'a VecTree<Tag>, Option<String>>;
pub(crate) type SVGWithMatchingState<'a> =
    flange_flat_tree::FlangedTree<&'a VecTree<Tag>, Option<MatchingState>>;
pub type SVGWithTreeHash<'a> = flange_flat_tree::FlangedTree<&'a VecTree<Tag>, TreeHash>;
pub type SVGWithTreeHashSubtree<'a> =
    <flange_flat_tree::FlangedTree<&'a VecTree<Tag>, TreeHash> as Tree<'a>>::SubtreeType;

impl SVG {
    pub fn with_ids(&self, ids: Vec<Option<String>>) -> SVGWithIDs {
        self.tags.flange(ids)
    }

    pub(crate) fn with_matching_states(
        &self,
        states: Vec<Option<MatchingState>>,
    ) -> SVGWithMatchingState {
        self.tags.flange(states)
    }

    pub fn parse_svg_string(input: &str) -> Result<SVG> {
        // Extract the svg part
        let re = RegexBuilder::new(r"<svg.*</svg>")
            .multi_line(true)
            .dot_matches_new_line(true)
            .build()
            .unwrap();
        if let Some(svg_string) = re.find(input) {
            // Parse the svg part
            let mut p = svg::read(svg_string.as_str())?;
            SVG::parse_svg(&mut p)
        } else {
            bail!(format!("{} does not contain an svg", input))
        }
    }

    pub fn parse_svg(events: &mut Parser) -> Result<SVG> {
        let mut tags = Builder::new();
        // Go through svg event stream
        let mut current_index: usize = 0;
        for event in events.by_ref() {
            match event {
                Event::Error(e) => bail!(e),
                Event::Tag(tag_name, tag_type, tag_args) => match tag_type {
                    svg::node::element::tag::Type::Start => {
                        current_index = tags.start_element(Tag::new(
                            tag_name.to_string(),
                            "".to_string(),
                            tag_args,
                        )?);
                    }
                    svg::node::element::tag::Type::End => {
                        tags.end_element();
                    }
                    svg::node::element::tag::Type::Empty => {
                        tags.start_end_element(Tag::new(
                            tag_name.to_string(),
                            "".to_string(),
                            tag_args,
                        )?);
                    }
                },
                Event::Text(t) => {
                    tags.get_mut(current_index).unwrap().text = t.to_string();
                }
                Event::Comment(_) => {
                    log::info!("ignoring comment")
                }
                Event::Declaration(t) => {
                    tags.get_mut(current_index).unwrap().text = t.to_string();
                }
                Event::Instruction(_) => {
                    log::warn!("ignoring instruction")
                }
            }
        }
        Ok(SVG { tags: tags.build() })
    }
}

#[cfg(test)]
mod tests {
    use flange_flat_tree::{Subtree, Tree};

    use super::*;

    #[test]
    fn test_simple() {
        let data = r#"
        <svg height="100" width="100">
          <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
          Sorry, your browser does not support inline SVG.
        </svg>
        "#;
        let mut parser = svg::read(data).unwrap();
        let result = SVG::parse_svg(&mut parser).unwrap();

        assert_eq!(result.tags.root().children().len(), 1);
        assert_eq!(result.tags.root().children()[0].value().name, "circle");
        assert_eq!(
            result.tags.root().value().text,
            "Sorry, your browser does not support inline SVG."
        );
    }
}