/rust/registry/src/index.crates.io-1949cf8c6b5b557f/cssparser-0.35.0/src/unicode_range.rs
Line | Count | Source |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | | |
5 | | //! https://drafts.csswg.org/css-syntax/#urange |
6 | | |
7 | | use crate::tokenizer::Token; |
8 | | use crate::{BasicParseError, Parser, ToCss}; |
9 | | use std::char; |
10 | | use std::fmt; |
11 | | |
12 | | /// One contiguous range of code points. |
13 | | /// |
14 | | /// Can not be empty. Can represent a single code point when start == end. |
15 | | #[derive(PartialEq, Eq, Clone, Hash)] |
16 | | #[repr(C)] |
17 | | pub struct UnicodeRange { |
18 | | /// Inclusive start of the range. In [0, end]. |
19 | | pub start: u32, |
20 | | |
21 | | /// Inclusive end of the range. In [0, 0x10FFFF]. |
22 | | pub end: u32, |
23 | | } |
24 | | |
25 | | impl UnicodeRange { |
26 | | /// https://drafts.csswg.org/css-syntax/#urange-syntax |
27 | 0 | pub fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Self, BasicParseError<'i>> { |
28 | | // <urange> = |
29 | | // u '+' <ident-token> '?'* | |
30 | | // u <dimension-token> '?'* | |
31 | | // u <number-token> '?'* | |
32 | | // u <number-token> <dimension-token> | |
33 | | // u <number-token> <number-token> | |
34 | | // u '+' '?'+ |
35 | | |
36 | 0 | input.expect_ident_matching("u")?; |
37 | 0 | let after_u = input.position(); |
38 | 0 | parse_tokens(input)?; |
39 | | |
40 | | // This deviates from the spec in case there are CSS comments |
41 | | // between tokens in the middle of one <unicode-range>, |
42 | | // but oh well… |
43 | 0 | let concatenated_tokens = input.slice_from(after_u); |
44 | | |
45 | 0 | let range = match parse_concatenated(concatenated_tokens.as_bytes()) { |
46 | 0 | Ok(range) => range, |
47 | | Err(()) => { |
48 | 0 | return Err(input |
49 | 0 | .new_basic_unexpected_token_error(Token::Ident(concatenated_tokens.into()))) |
50 | | } |
51 | | }; |
52 | 0 | if range.end > char::MAX as u32 || range.start > range.end { |
53 | 0 | Err(input.new_basic_unexpected_token_error(Token::Ident(concatenated_tokens.into()))) |
54 | | } else { |
55 | 0 | Ok(range) |
56 | | } |
57 | 0 | } |
58 | | } |
59 | | |
60 | 0 | fn parse_tokens<'i>(input: &mut Parser<'i, '_>) -> Result<(), BasicParseError<'i>> { |
61 | 0 | match input.next_including_whitespace()?.clone() { |
62 | | Token::Delim('+') => { |
63 | 0 | match *input.next_including_whitespace()? { |
64 | 0 | Token::Ident(_) => {} |
65 | 0 | Token::Delim('?') => {} |
66 | 0 | ref t => { |
67 | 0 | let t = t.clone(); |
68 | 0 | return Err(input.new_basic_unexpected_token_error(t)); |
69 | | } |
70 | | } |
71 | 0 | parse_question_marks(input) |
72 | | } |
73 | 0 | Token::Dimension { .. } => parse_question_marks(input), |
74 | | Token::Number { .. } => { |
75 | 0 | let after_number = input.state(); |
76 | 0 | match input.next_including_whitespace() { |
77 | 0 | Ok(&Token::Delim('?')) => parse_question_marks(input), |
78 | 0 | Ok(&Token::Dimension { .. }) => {} |
79 | 0 | Ok(&Token::Number { .. }) => {} |
80 | 0 | _ => input.reset(&after_number), |
81 | | } |
82 | | } |
83 | 0 | t => return Err(input.new_basic_unexpected_token_error(t)), |
84 | | } |
85 | 0 | Ok(()) |
86 | 0 | } |
87 | | |
88 | | /// Consume as many '?' as possible |
89 | 0 | fn parse_question_marks(input: &mut Parser) { |
90 | | loop { |
91 | 0 | let start = input.state(); |
92 | 0 | match input.next_including_whitespace() { |
93 | 0 | Ok(&Token::Delim('?')) => {} |
94 | | _ => { |
95 | 0 | input.reset(&start); |
96 | 0 | return; |
97 | | } |
98 | | } |
99 | | } |
100 | 0 | } |
101 | | |
102 | 0 | fn parse_concatenated(text: &[u8]) -> Result<UnicodeRange, ()> { |
103 | 0 | let mut text = match text.split_first() { |
104 | 0 | Some((&b'+', text)) => text, |
105 | 0 | _ => return Err(()), |
106 | | }; |
107 | 0 | let (first_hex_value, hex_digit_count) = consume_hex(&mut text, 6)?; |
108 | 0 | let question_marks = consume_question_marks(&mut text); |
109 | 0 | let consumed = hex_digit_count + question_marks; |
110 | 0 | if consumed == 0 || consumed > 6 { |
111 | 0 | return Err(()); |
112 | 0 | } |
113 | | |
114 | 0 | if question_marks > 0 { |
115 | 0 | if text.is_empty() { |
116 | 0 | return Ok(UnicodeRange { |
117 | 0 | start: first_hex_value << (question_marks * 4), |
118 | 0 | end: ((first_hex_value + 1) << (question_marks * 4)) - 1, |
119 | 0 | }); |
120 | 0 | } |
121 | 0 | } else if text.is_empty() { |
122 | 0 | return Ok(UnicodeRange { |
123 | 0 | start: first_hex_value, |
124 | 0 | end: first_hex_value, |
125 | 0 | }); |
126 | 0 | } else if let Some((&b'-', mut text)) = text.split_first() { |
127 | 0 | let (second_hex_value, hex_digit_count) = consume_hex(&mut text, 6)?; |
128 | 0 | if hex_digit_count > 0 && hex_digit_count <= 6 && text.is_empty() { |
129 | 0 | return Ok(UnicodeRange { |
130 | 0 | start: first_hex_value, |
131 | 0 | end: second_hex_value, |
132 | 0 | }); |
133 | 0 | } |
134 | 0 | } |
135 | 0 | Err(()) |
136 | 0 | } |
137 | | |
138 | | // Consume hex digits, but return an error if more than digit_limit are found. |
139 | 0 | fn consume_hex(text: &mut &[u8], digit_limit: usize) -> Result<(u32, usize), ()> { |
140 | 0 | let mut value = 0; |
141 | 0 | let mut digits = 0; |
142 | 0 | while let Some((&byte, rest)) = text.split_first() { |
143 | 0 | if let Some(digit_value) = (byte as char).to_digit(16) { |
144 | 0 | if digits == digit_limit { |
145 | 0 | return Err(()); |
146 | 0 | } |
147 | 0 | value = value * 0x10 + digit_value; |
148 | 0 | digits += 1; |
149 | 0 | *text = rest; |
150 | | } else { |
151 | 0 | break; |
152 | | } |
153 | | } |
154 | 0 | Ok((value, digits)) |
155 | 0 | } |
156 | | |
157 | 0 | fn consume_question_marks(text: &mut &[u8]) -> usize { |
158 | 0 | let mut question_marks = 0; |
159 | 0 | while let Some((&b'?', rest)) = text.split_first() { |
160 | 0 | question_marks += 1; |
161 | 0 | *text = rest |
162 | | } |
163 | 0 | question_marks |
164 | 0 | } |
165 | | |
166 | | impl fmt::Debug for UnicodeRange { |
167 | 0 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { |
168 | 0 | self.to_css(formatter) |
169 | 0 | } |
170 | | } |
171 | | |
172 | | impl ToCss for UnicodeRange { |
173 | 0 | fn to_css<W>(&self, dest: &mut W) -> fmt::Result |
174 | 0 | where |
175 | 0 | W: fmt::Write, |
176 | | { |
177 | 0 | write!(dest, "U+{:X}", self.start)?; |
178 | 0 | if self.end != self.start { |
179 | 0 | write!(dest, "-{:X}", self.end)?; |
180 | 0 | } |
181 | 0 | Ok(()) |
182 | 0 | } |
183 | | } |