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}