Coverage Report

Created: 2026-03-31 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}