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<'i>(input: &mut Parser<'i, '_>) -> Result<Self, BasicParseError<'i>> {
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(()) => {
48                return Err(input
49                    .new_basic_unexpected_token_error(Token::Ident(concatenated_tokens.into())))
50            }
51        };
52        if range.end > char::MAX as u32 || range.start > range.end {
53            Err(input.new_basic_unexpected_token_error(Token::Ident(concatenated_tokens.into())))
54        } else {
55            Ok(range)
56        }
57    }
58}
59
60fn parse_tokens<'i>(input: &mut Parser<'i, '_>) -> Result<(), BasicParseError<'i>> {
61    match input.next_including_whitespace()?.clone() {
62        Token::Delim('+') => {
63            match *input.next_including_whitespace()? {
64                Token::Ident(_) => {}
65                Token::Delim('?') => {}
66                ref t => {
67                    let t = t.clone();
68                    return Err(input.new_basic_unexpected_token_error(t));
69                }
70            }
71            parse_question_marks(input)
72        }
73        Token::Dimension { .. } => parse_question_marks(input),
74        Token::Number { .. } => {
75            let after_number = input.state();
76            match input.next_including_whitespace() {
77                Ok(&Token::Delim('?')) => parse_question_marks(input),
78                Ok(&Token::Dimension { .. }) => {}
79                Ok(&Token::Number { .. }) => {}
80                _ => input.reset(&after_number),
81            }
82        }
83        t => return Err(input.new_basic_unexpected_token_error(t)),
84    }
85    Ok(())
86}
87
88fn parse_question_marks(input: &mut Parser) {
90    loop {
91        let start = input.state();
92        match input.next_including_whitespace() {
93            Ok(&Token::Delim('?')) => {}
94            _ => {
95                input.reset(&start);
96                return;
97            }
98        }
99    }
100}
101
102fn parse_concatenated(text: &[u8]) -> Result<UnicodeRange, ()> {
103    let mut text = match text.split_first() {
104        Some((&b'+', text)) => text,
105        _ => return Err(()),
106    };
107    let (first_hex_value, hex_digit_count) = consume_hex(&mut text, 6)?;
108    let question_marks = consume_question_marks(&mut text);
109    let consumed = hex_digit_count + question_marks;
110    if consumed == 0 || consumed > 6 {
111        return Err(());
112    }
113
114    if question_marks > 0 {
115        if text.is_empty() {
116            return Ok(UnicodeRange {
117                start: first_hex_value << (question_marks * 4),
118                end: ((first_hex_value + 1) << (question_marks * 4)) - 1,
119            });
120        }
121    } else if text.is_empty() {
122        return Ok(UnicodeRange {
123            start: first_hex_value,
124            end: first_hex_value,
125        });
126    } else if let Some((&b'-', mut text)) = text.split_first() {
127        let (second_hex_value, hex_digit_count) = consume_hex(&mut text, 6)?;
128        if hex_digit_count > 0 && hex_digit_count <= 6 && text.is_empty() {
129            return Ok(UnicodeRange {
130                start: first_hex_value,
131                end: second_hex_value,
132            });
133        }
134    }
135    Err(())
136}
137
138fn consume_hex(text: &mut &[u8], digit_limit: usize) -> Result<(u32, usize), ()> {
140    let mut value = 0;
141    let mut digits = 0;
142    while let Some((&byte, rest)) = text.split_first() {
143        if let Some(digit_value) = (byte as char).to_digit(16) {
144            if digits == digit_limit {
145                return Err(());
146            }
147            value = value * 0x10 + digit_value;
148            digits += 1;
149            *text = rest;
150        } else {
151            break;
152        }
153    }
154    Ok((value, digits))
155}
156
157fn consume_question_marks(text: &mut &[u8]) -> usize {
158    let mut question_marks = 0;
159    while let Some((&b'?', rest)) = text.split_first() {
160        question_marks += 1;
161        *text = rest
162    }
163    question_marks
164}
165
166impl fmt::Debug for UnicodeRange {
167    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
168        self.to_css(formatter)
169    }
170}
171
172impl ToCss for UnicodeRange {
173    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
174    where
175        W: fmt::Write,
176    {
177        write!(dest, "U+{:X}", self.start)?;
178        if self.end != self.start {
179            write!(dest, "-{:X}", self.end)?;
180        }
181        Ok(())
182    }
183}