1
//! Processing of the `xml:space` attribute.
2

            
3
use itertools::Itertools;
4

            
5
pub struct NormalizeDefault {
6
    pub has_element_before: bool,
7
    pub has_element_after: bool,
8
}
9

            
10
pub enum XmlSpaceNormalize {
11
    Default(NormalizeDefault),
12
    Preserve,
13
}
14

            
15
/// Implements `xml:space` handling per the SVG spec
16
///
17
/// Normalizes a string as it comes out of the XML parser's handler
18
/// for character data according to the SVG rules in
19
/// <https://www.w3.org/TR/SVG/text.html#WhiteSpace>
20
1033
pub fn xml_space_normalize(mode: XmlSpaceNormalize, s: &str) -> String {
21
1033
    match mode {
22
1029
        XmlSpaceNormalize::Default(d) => normalize_default(d, s),
23
4
        XmlSpaceNormalize::Preserve => normalize_preserve(s),
24
    }
25
1033
}
26

            
27
// From https://www.w3.org/TR/SVG/text.html#WhiteSpace
28
//
29
// When xml:space="default", the SVG user agent will do the following
30
// using a copy of the original character data content. First, it will
31
// remove all newline characters. Then it will convert all tab
32
// characters into space characters. Then, it will strip off all
33
// leading and trailing space characters. Then, all contiguous space
34
// characters will be consolidated.
35
1029
fn normalize_default(elements: NormalizeDefault, mut s: &str) -> String {
36
1029
    if !elements.has_element_before {
37
982
        s = s.trim_start();
38
    }
39

            
40
1029
    if !elements.has_element_after {
41
968
        s = s.trim_end();
42
    }
43

            
44
1029
    s.chars()
45
14618
        .filter(|ch| *ch != '\n')
46
14585
        .map(|ch| match ch {
47
10
            '\t' => ' ',
48
14575
            c => c,
49
14585
        })
50
13595
        .coalesce(|current, next| match (current, next) {
51
198
            (' ', ' ') => Ok(' '),
52
13397
            (_, _) => Err((current, next)),
53
13595
        })
54
        .collect::<String>()
55
1029
}
56

            
57
// From https://www.w3.org/TR/SVG/text.html#WhiteSpace
58
//
59
// When xml:space="preserve", the SVG user agent will do the following
60
// using a copy of the original character data content. It will
61
// convert all newline and tab characters into space characters. Then,
62
// it will draw all space characters, including leading, trailing and
63
// multiple contiguous space characters. Thus, when drawn with
64
// xml:space="preserve", the string "a   b" (three spaces between "a"
65
// and "b") will produce a larger separation between "a" and "b" than
66
// "a b" (one space between "a" and "b").
67
4
fn normalize_preserve(s: &str) -> String {
68
4
    s.chars()
69
166
        .map(|ch| match ch {
70
25
            '\n' | '\t' => ' ',
71

            
72
141
            c => c,
73
166
        })
74
        .collect()
75
4
}
76

            
77
#[cfg(test)]
78
mod tests {
79
    use super::*;
80

            
81
    #[test]
82
2
    fn xml_space_default() {
83
2
        assert_eq!(
84
1
            xml_space_normalize(
85
1
                XmlSpaceNormalize::Default(NormalizeDefault {
86
                    has_element_before: false,
87
                    has_element_after: false,
88
                }),
89
                "\n    WS example\n    indented lines\n  "
90
            ),
91
            "WS example indented lines"
92
        );
93
2
        assert_eq!(
94
1
            xml_space_normalize(
95
1
                XmlSpaceNormalize::Default(NormalizeDefault {
96
                    has_element_before: false,
97
                    has_element_after: false,
98
                }),
99
                "\n  \t  \tWS \t\t\texample\n  \t  indented lines\t\t  \n  "
100
            ),
101
            "WS example indented lines"
102
        );
103
2
        assert_eq!(
104
1
            xml_space_normalize(
105
1
                XmlSpaceNormalize::Default(NormalizeDefault {
106
                    has_element_before: false,
107
                    has_element_after: false,
108
                }),
109
                "\n  \t  \tWS \t\t\texample\n  \t  duplicate letters\t\t  \n  "
110
            ),
111
            "WS example duplicate letters"
112
        );
113
2
        assert_eq!(
114
1
            xml_space_normalize(
115
1
                XmlSpaceNormalize::Default(NormalizeDefault {
116
                    has_element_before: false,
117
                    has_element_after: false,
118
                }),
119
                "\nWS example\nnon-indented lines\n  "
120
            ),
121
            "WS examplenon-indented lines"
122
        );
123
2
        assert_eq!(
124
1
            xml_space_normalize(
125
1
                XmlSpaceNormalize::Default(NormalizeDefault {
126
                    has_element_before: false,
127
                    has_element_after: false,
128
                }),
129
                "\nWS example\tnon-indented lines\n  "
130
            ),
131
            "WS example non-indented lines"
132
        );
133
2
    }
134

            
135
    #[test]
136
2
    fn xml_space_default_with_elements() {
137
2
        assert_eq!(
138
1
            xml_space_normalize(
139
1
                XmlSpaceNormalize::Default(NormalizeDefault {
140
                    has_element_before: true,
141
                    has_element_after: false,
142
                }),
143
                " foo \n\t  bar "
144
            ),
145
            " foo bar"
146
        );
147

            
148
2
        assert_eq!(
149
1
            xml_space_normalize(
150
1
                XmlSpaceNormalize::Default(NormalizeDefault {
151
                    has_element_before: false,
152
                    has_element_after: true,
153
                }),
154
                " foo   \nbar "
155
            ),
156
            "foo bar "
157
        );
158
2
    }
159

            
160
    #[test]
161
2
    fn xml_space_preserve() {
162
2
        assert_eq!(
163
1
            xml_space_normalize(
164
1
                XmlSpaceNormalize::Preserve,
165
                "\n    WS example\n    indented lines\n  "
166
            ),
167
            "     WS example     indented lines   "
168
        );
169
2
        assert_eq!(
170
1
            xml_space_normalize(
171
1
                XmlSpaceNormalize::Preserve,
172
                "\n  \t  \tWS \t\t\texample\n  \t  indented lines\t\t  \n  "
173
            ),
174
            "       WS    example      indented lines       "
175
        );
176
2
        assert_eq!(
177
1
            xml_space_normalize(
178
1
                XmlSpaceNormalize::Preserve,
179
                "\n  \t  \tWS \t\t\texample\n  \t  duplicate letters\t\t  \n  "
180
            ),
181
            "       WS    example      duplicate letters       "
182
        );
183
2
    }
184
}