1
//! Conditional processing attributes: `requiredExtensions`, `requiredFeatures`, `systemLanguage`.
2

            
3
#[allow(unused_imports, deprecated)]
4
use std::ascii::AsciiExt;
5

            
6
use std::str::FromStr;
7

            
8
use language_tags::LanguageTag;
9

            
10
use crate::accept_language::{LanguageTags, UserLanguage};
11
use crate::error::*;
12
use crate::rsvg_log;
13
use crate::session::Session;
14

            
15
// No extensions at the moment.
16
static IMPLEMENTED_EXTENSIONS: &[&str] = &[];
17

            
18
2
#[derive(Debug, PartialEq)]
19
1
pub struct RequiredExtensions(pub bool);
20

            
21
impl RequiredExtensions {
22
    /// Parse a requiredExtensions attribute.
23
    ///
24
    /// <http://www.w3.org/TR/SVG/struct.html#RequiredExtensionsAttribute>
25
2
    pub fn from_attribute(s: &str) -> RequiredExtensions {
26
2
        RequiredExtensions(
27
2
            s.split_whitespace()
28
2
                .all(|f| IMPLEMENTED_EXTENSIONS.binary_search(&f).is_ok()),
29
        )
30
2
    }
31

            
32
    /// Evaluate a requiredExtensions value for conditional processing.
33
1
    pub fn eval(&self) -> bool {
34
1
        self.0
35
1
    }
36
}
37

            
38
// Keep these sorted alphabetically for binary_search.
39
static IMPLEMENTED_FEATURES: &[&str] = &[
40
    "http://www.w3.org/TR/SVG11/feature#BasicFilter",
41
    "http://www.w3.org/TR/SVG11/feature#BasicGraphicsAttribute",
42
    "http://www.w3.org/TR/SVG11/feature#BasicPaintAttribute",
43
    "http://www.w3.org/TR/SVG11/feature#BasicStructure",
44
    "http://www.w3.org/TR/SVG11/feature#BasicText",
45
    "http://www.w3.org/TR/SVG11/feature#ConditionalProcessing",
46
    "http://www.w3.org/TR/SVG11/feature#ContainerAttribute",
47
    "http://www.w3.org/TR/SVG11/feature#Filter",
48
    "http://www.w3.org/TR/SVG11/feature#Gradient",
49
    "http://www.w3.org/TR/SVG11/feature#Image",
50
    "http://www.w3.org/TR/SVG11/feature#Marker",
51
    "http://www.w3.org/TR/SVG11/feature#Mask",
52
    "http://www.w3.org/TR/SVG11/feature#OpacityAttribute",
53
    "http://www.w3.org/TR/SVG11/feature#Pattern",
54
    "http://www.w3.org/TR/SVG11/feature#SVG",
55
    "http://www.w3.org/TR/SVG11/feature#SVG-static",
56
    "http://www.w3.org/TR/SVG11/feature#Shape",
57
    "http://www.w3.org/TR/SVG11/feature#Structure",
58
    "http://www.w3.org/TR/SVG11/feature#Style",
59
    "http://www.w3.org/TR/SVG11/feature#View",
60
    "org.w3c.svg.static", // deprecated SVG 1.0 feature string
61
];
62

            
63
8
#[derive(Debug, PartialEq)]
64
4
pub struct RequiredFeatures(pub bool);
65

            
66
impl RequiredFeatures {
67
    // Parse a requiredFeatures attribute
68
    // http://www.w3.org/TR/SVG/struct.html#RequiredFeaturesAttribute
69
6
    pub fn from_attribute(s: &str) -> RequiredFeatures {
70
6
        RequiredFeatures(
71
6
            s.split_whitespace()
72
8
                .all(|f| IMPLEMENTED_FEATURES.binary_search(&f).is_ok()),
73
        )
74
6
    }
75

            
76
    /// Evaluate a requiredFeatures value for conditional processing.
77
2
    pub fn eval(&self) -> bool {
78
2
        self.0
79
2
    }
80
}
81

            
82
/// The systemLanguage attribute inside `<cond>` element's children.
83
///
84
/// Parsing the value of a `systemLanguage` attribute may fail if the document supplies
85
/// invalid BCP47 language tags.  In that case, we store an `Invalid` variant.
86
///
87
/// That variant is used later, during [`SystemLanguage::eval`], to see whether the
88
/// `<cond>` should match or not.
89
#[derive(Debug, PartialEq)]
90
pub enum SystemLanguage {
91
    Valid(LanguageTags),
92
    Invalid,
93
}
94

            
95
impl SystemLanguage {
96
    /// Parse a `systemLanguage` attribute and match it against a given `Locale`
97
    ///
98
    /// The [`systemLanguage`] conditional attribute is a
99
    /// comma-separated list of [BCP47] Language Tags.  This function
100
    /// parses the attribute and matches the result against a given
101
    /// `locale`.  If there is a match, i.e. if the given locale
102
    /// supports one of the languages listed in the `systemLanguage`
103
    /// attribute, then the `SystemLanguage.0` will be `true`;
104
    /// otherwise it will be `false`.
105
    ///
106
    /// Normally, calling code will pass `&Locale::current()` for the
107
    /// `locale` attribute; this is the user's current locale.
108
    ///
109
    /// [`systemLanguage`]: https://www.w3.org/TR/SVG/struct.html#ConditionalProcessingSystemLanguageAttribute
110
    /// [BCP47]: http://www.ietf.org/rfc/bcp/bcp47.txt
111
23
    pub fn from_attribute(s: &str, session: &Session) -> SystemLanguage {
112
23
        let attribute_tags = s
113
            .split(',')
114
            .map(str::trim)
115
24
            .map(|s| {
116
27
                LanguageTag::from_str(s).map_err(|e| {
117
3
                    ValueErrorKind::parse_error(&format!("invalid language tag \"{s}\": {e}"))
118
3
                })
119
24
            })
120
            .collect::<Result<Vec<LanguageTag>, _>>();
121

            
122
23
        match attribute_tags {
123
20
            Ok(tags) => SystemLanguage::Valid(LanguageTags::from(tags)),
124

            
125
3
            Err(e) => {
126
3
                rsvg_log!(
127
                    session,
128
                    "ignoring systemLanguage attribute with invalid value: {}",
129
                    e
130
                );
131
3
                SystemLanguage::Invalid
132
3
            }
133
        }
134
23
    }
135

            
136
    /// Evaluate a systemLanguage value for conditional processing.
137
19
    pub fn eval(&self, user_language: &UserLanguage) -> bool {
138
19
        match *self {
139
18
            SystemLanguage::Valid(ref tags) => user_language.any_matches(tags),
140
1
            SystemLanguage::Invalid => false,
141
        }
142
19
    }
143
}
144

            
145
#[cfg(test)]
146
mod tests {
147
    use super::*;
148
    use locale_config::Locale;
149

            
150
    #[test]
151
2
    fn required_extensions() {
152
1
        assert_eq!(
153
1
            RequiredExtensions::from_attribute("http://test.org/NotExisting/1.0"),
154
            RequiredExtensions(false)
155
        );
156
2
    }
157

            
158
    #[test]
159
2
    fn required_features() {
160
1
        assert_eq!(
161
1
            RequiredFeatures::from_attribute("http://www.w3.org/TR/SVG11/feature#NotExisting"),
162
            RequiredFeatures(false)
163
        );
164

            
165
1
        assert_eq!(
166
1
            RequiredFeatures::from_attribute("http://www.w3.org/TR/SVG11/feature#BasicFilter"),
167
            RequiredFeatures(true)
168
        );
169

            
170
1
        assert_eq!(
171
1
            RequiredFeatures::from_attribute(
172
                "http://www.w3.org/TR/SVG11/feature#BasicFilter \
173
                 http://www.w3.org/TR/SVG11/feature#NotExisting",
174
            ),
175
            RequiredFeatures(false)
176
        );
177

            
178
1
        assert_eq!(
179
1
            RequiredFeatures::from_attribute(
180
                "http://www.w3.org/TR/SVG11/feature#BasicFilter \
181
                 http://www.w3.org/TR/SVG11/feature#BasicText",
182
            ),
183
            RequiredFeatures(true)
184
        );
185
2
    }
186

            
187
    #[test]
188
2
    fn system_language() {
189
1
        let session = Session::new_for_test_suite();
190

            
191
1
        let locale = Locale::new("de,en-US").unwrap();
192
1
        let user_language = UserLanguage::LanguageTags(LanguageTags::from_locale(&locale).unwrap());
193

            
194
1
        assert!(matches!(
195
1
            SystemLanguage::from_attribute("", &session),
196
            SystemLanguage::Invalid
197
        ));
198

            
199
1
        assert!(matches!(
200
1
            SystemLanguage::from_attribute("12345", &session),
201
            SystemLanguage::Invalid
202
        ));
203

            
204
1
        assert!(!SystemLanguage::from_attribute("fr", &session).eval(&user_language));
205

            
206
1
        assert!(!SystemLanguage::from_attribute("en", &session).eval(&user_language));
207

            
208
1
        assert!(SystemLanguage::from_attribute("de", &session).eval(&user_language));
209

            
210
1
        assert!(SystemLanguage::from_attribute("en-US", &session).eval(&user_language));
211

            
212
1
        assert!(!SystemLanguage::from_attribute("en-GB", &session).eval(&user_language));
213

            
214
1
        assert!(SystemLanguage::from_attribute("DE", &session).eval(&user_language));
215

            
216
1
        assert!(SystemLanguage::from_attribute("de-LU", &session).eval(&user_language));
217

            
218
1
        assert!(SystemLanguage::from_attribute("fr, de", &session).eval(&user_language));
219
2
    }
220
}