cssparser/
unicode_range.rs1use crate::tokenizer::Token;
8use crate::{BasicParseError, Parser, ToCss};
9use std::char;
10use std::fmt;
11
12#[derive(PartialEq, Eq, Clone, Hash)]
16#[repr(C)]
17pub struct UnicodeRange {
18 pub start: u32,
20
21 pub end: u32,
23}
24
25impl UnicodeRange {
26 pub fn parse(input: &mut Parser) -> Result<Self, BasicParseError> {
28 input.expect_ident_matching("u")?;
37 let after_u = input.position();
38 parse_tokens(input)?;
39
40 let concatenated_tokens = input.slice_from(after_u);
44
45 let range = match parse_concatenated(concatenated_tokens.as_bytes()) {
46 Ok(range) => range,
47 Err(()) => return Err(BasicParseError::unexpected_token()),
48 };
49 if range.end > char::MAX as u32 || range.start > range.end {
50 Err(BasicParseError::unexpected_token())
51 } else {
52 Ok(range)
53 }
54 }
55}
56
57fn parse_tokens(input: &mut Parser) -> Result<(), BasicParseError> {
58 match *input.next_including_whitespace()? {
59 Token::Delim('+') => {
60 match *input.next_including_whitespace()? {
61 Token::Ident(_) => {}
62 Token::Delim('?') => {}
63 _ => return Err(BasicParseError::unexpected_token()),
64 }
65 parse_question_marks(input)
66 }
67 Token::Dimension { .. } => parse_question_marks(input),
68 Token::Number { .. } => {
69 let after_number = input.state();
70 match input.next_including_whitespace() {
71 Ok(&Token::Delim('?')) => parse_question_marks(input),
72 Ok(&Token::Dimension { .. }) => {}
73 Ok(&Token::Number { .. }) => {}
74 _ => input.reset(&after_number),
75 }
76 }
77 _ => return Err(BasicParseError::unexpected_token()),
78 }
79 Ok(())
80}
81
82fn parse_question_marks(input: &mut Parser) {
84 loop {
85 let start = input.state();
86 match input.next_including_whitespace() {
87 Ok(&Token::Delim('?')) => {}
88 _ => {
89 input.reset(&start);
90 return;
91 }
92 }
93 }
94}
95
96fn parse_concatenated(text: &[u8]) -> Result<UnicodeRange, ()> {
97 let mut text = match text.split_first() {
98 Some((&b'+', text)) => text,
99 _ => return Err(()),
100 };
101 let (first_hex_value, hex_digit_count) = consume_hex(&mut text, 6)?;
102 let question_marks = consume_question_marks(&mut text);
103 let consumed = hex_digit_count + question_marks;
104 if consumed == 0 || consumed > 6 {
105 return Err(());
106 }
107
108 if question_marks > 0 {
109 if text.is_empty() {
110 return Ok(UnicodeRange {
111 start: first_hex_value << (question_marks * 4),
112 end: ((first_hex_value + 1) << (question_marks * 4)) - 1,
113 });
114 }
115 } else if text.is_empty() {
116 return Ok(UnicodeRange {
117 start: first_hex_value,
118 end: first_hex_value,
119 });
120 } else if let Some((&b'-', mut text)) = text.split_first() {
121 let (second_hex_value, hex_digit_count) = consume_hex(&mut text, 6)?;
122 if hex_digit_count > 0 && hex_digit_count <= 6 && text.is_empty() {
123 return Ok(UnicodeRange {
124 start: first_hex_value,
125 end: second_hex_value,
126 });
127 }
128 }
129 Err(())
130}
131
132fn consume_hex(text: &mut &[u8], digit_limit: usize) -> Result<(u32, usize), ()> {
134 let mut value = 0;
135 let mut digits = 0;
136 while let Some((&byte, rest)) = text.split_first() {
137 if let Some(digit_value) = (byte as char).to_digit(16) {
138 if digits == digit_limit {
139 return Err(());
140 }
141 value = value * 0x10 + digit_value;
142 digits += 1;
143 *text = rest;
144 } else {
145 break;
146 }
147 }
148 Ok((value, digits))
149}
150
151fn consume_question_marks(text: &mut &[u8]) -> usize {
152 let mut question_marks = 0;
153 while let Some((&b'?', rest)) = text.split_first() {
154 question_marks += 1;
155 *text = rest
156 }
157 question_marks
158}
159
160impl fmt::Debug for UnicodeRange {
161 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
162 self.to_css(formatter)
163 }
164}
165
166impl ToCss for UnicodeRange {
167 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
168 where
169 W: fmt::Write,
170 {
171 write!(dest, "U+{:X}", self.start)?;
172 if self.end != self.start {
173 write!(dest, "-{:X}", self.end)?;
174 }
175 Ok(())
176 }
177}