Skip to main content

cssparser/
nth.rs

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
5use super::{BasicParseError, Parser, Token};
6
7/// Parse the *An+B* notation, as found in the `:nth-child()` selector.
8/// The input is typically the arguments of a function,
9/// in which case the caller needs to check if the arguments’ parser is exhausted.
10/// Return `Ok((A, B))`, or an `Err(..)` for a syntax error.
11pub fn parse_nth(input: &mut Parser) -> Result<(i32, i32), BasicParseError> {
12    match *input.next()? {
13        Token::Number {
14            int_value: Some(b), ..
15        } => Ok((0, b)),
16        Token::Dimension {
17            int_value: Some(a),
18            ref unit,
19            ..
20        } => {
21            match_ignore_ascii_case! {
22                unit,
23                "n" => parse_b(input, a),
24                "n-" => parse_signless_b(input, a, -1),
25                _ => match parse_n_dash_digits(unit) {
26                    Ok(b) => Ok((a, b)),
27                    Err(()) => {
28                        Err(BasicParseError::unexpected_token())
29                    }
30                }
31            }
32        }
33        Token::Ident(ref value) => {
34            match_ignore_ascii_case! { value,
35                "even" => Ok((2, 0)),
36                "odd" => Ok((2, 1)),
37                "n" => parse_b(input, 1),
38                "-n" => parse_b(input, -1),
39                "n-" => parse_signless_b(input, 1, -1),
40                "-n-" => parse_signless_b(input, -1, -1),
41                _ => {
42                    let (slice, a) = if let Some(stripped) = value.strip_prefix('-') {
43                        (stripped, -1)
44                    } else {
45                        (&**value, 1)
46                    };
47                    match parse_n_dash_digits(slice) {
48                        Ok(b) => Ok((a, b)),
49                        Err(()) => {
50                            Err(BasicParseError::unexpected_token())
51                        }
52                    }
53                }
54            }
55        }
56        Token::Delim('+') => match *input.next_including_whitespace()? {
57            Token::Ident(ref value) => {
58                match_ignore_ascii_case! { value,
59                    "n" => parse_b(input, 1),
60                    "n-" => parse_signless_b(input, 1, -1),
61                    _ => match parse_n_dash_digits(value) {
62                        Ok(b) => Ok((1, b)),
63                        Err(()) => {
64                            Err(BasicParseError::unexpected_token())
65                        }
66                    }
67                }
68            }
69            _ => Err(BasicParseError::unexpected_token()),
70        },
71        _ => Err(BasicParseError::unexpected_token()),
72    }
73}
74
75fn parse_b(input: &mut Parser, a: i32) -> Result<(i32, i32), BasicParseError> {
76    let start = input.state();
77    match input.next() {
78        Ok(&Token::Delim('+')) => parse_signless_b(input, a, 1),
79        Ok(&Token::Delim('-')) => parse_signless_b(input, a, -1),
80        Ok(&Token::Number {
81            has_sign: true,
82            int_value: Some(b),
83            ..
84        }) => Ok((a, b)),
85        _ => {
86            input.reset(&start);
87            Ok((a, 0))
88        }
89    }
90}
91
92fn parse_signless_b(
93    input: &mut Parser,
94    a: i32,
95    b_sign: i32,
96) -> Result<(i32, i32), BasicParseError> {
97    match input.next()? {
98        &Token::Number {
99            has_sign: false,
100            int_value: Some(b),
101            ..
102        } => Ok((a, b_sign * b)),
103        _ => Err(BasicParseError::unexpected_token()),
104    }
105}
106
107fn parse_n_dash_digits(string: &str) -> Result<i32, ()> {
108    let bytes = string.as_bytes();
109    if bytes.len() >= 3
110        && bytes[..2].eq_ignore_ascii_case(b"n-")
111        && bytes[2..].iter().all(|&c| c.is_ascii_digit())
112    {
113        Ok(parse_number_saturate(&string[1..]).unwrap()) // Include the minus sign
114    } else {
115        Err(())
116    }
117}
118
119fn parse_number_saturate(string: &str) -> Result<i32, ()> {
120    let mut parser = Parser::new(string);
121    let int = if let Ok(&Token::Number {
122        int_value: Some(int),
123        ..
124    }) = parser.next_including_whitespace_and_comments()
125    {
126        int
127    } else {
128        return Err(());
129    };
130    if !parser.is_exhausted() {
131        return Err(());
132    }
133    Ok(int)
134}