lewp_css/domain/selectors/
language_range.rs

1// This file is part of css. It is subject to the license terms in the COPYRIGHT file found in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/css/master/COPYRIGHT. No part of predicator, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the COPYRIGHT file.
2// Copyright © 2017 The developers of css. See the COPYRIGHT file in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/css/master/COPYRIGHT.
3
4use {
5    crate::{
6        domain::Atom,
7        parsers::separators::{Comma, Separated},
8    },
9    cssparser::{serialize_identifier, serialize_string, ToCss},
10    std::fmt,
11};
12
13#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
14pub struct LanguageRange(pub Atom);
15
16impl Separated for LanguageRange {
17    type Delimiter = Comma;
18}
19
20impl ToCss for LanguageRange {
21    fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
22        let value = &(self.0).0;
23
24        let isCssIdentifier = if value.is_empty() {
25            false
26        } else {
27            let mut characters = value.chars();
28
29            match characters.next().unwrap() {
30                'a'..='z' | 'A'..='Z' | '-' => {
31                    let mut isCssIdentifier = true;
32                    for character in characters {
33                        match character {
34                            'a'..='z' | 'A'..='Z' | '-' | '0'..='9' => continue,
35                            _ => {
36                                isCssIdentifier = false;
37                                break;
38                            }
39                        }
40                    }
41                    isCssIdentifier
42                }
43                _ => false,
44            }
45        };
46
47        if isCssIdentifier {
48            serialize_identifier(value, dest)
49        } else {
50            serialize_string(value, dest)
51        }
52    }
53}
54
55impl LanguageRange {
56    /// Returns whether the language is matched, as defined by [RFC 4647](<https://tools.ietf.org/html/rfc4647#section-3.3.2>).
57    pub fn matches_language(&self, tag: &str) -> bool {
58        // step 1
59        let mut range_subtags = (self.0).0.split('\x2d');
60        let mut tag_subtags = tag.split('\x2d');
61
62        // step 2
63        // Note: [Level-4 spec](https://drafts.csswg.org/selectors/#lang-pseudo) check for wild card
64        if let (Some(range_subtag), Some(tag_subtag)) =
65            (range_subtags.next(), tag_subtags.next())
66        {
67            if !(range_subtag.eq_ignore_ascii_case(tag_subtag)
68                || range_subtag.eq_ignore_ascii_case("*"))
69            {
70                return false;
71            }
72        }
73
74        let mut current_tag_subtag = tag_subtags.next();
75
76        // step 3
77        for range_subtag in range_subtags {
78            // step 3a
79            if range_subtag == "*" {
80                continue;
81            }
82
83            match current_tag_subtag {
84                Some(tag_subtag) => {
85                    // step 3c
86                    if range_subtag.eq_ignore_ascii_case(tag_subtag) {
87                        current_tag_subtag = tag_subtags.next();
88                        continue;
89                    }
90
91                    // step 3d
92                    if tag_subtag.len() == 1 {
93                        return false;
94                    }
95
96                    // else step 3e - continue with loop
97                    current_tag_subtag = tag_subtags.next();
98                    if current_tag_subtag.is_none() {
99                        return false;
100                    }
101                }
102
103                // step 3b
104                None => {
105                    return false;
106                }
107            }
108        }
109
110        // step 4
111        true
112    }
113}