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, ParserInput, 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<'i>(input: &mut Parser<'i, '_>) -> Result<(i32, i32), BasicParseError<'i>> {
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" => Ok(parse_b(input, a)?),
24                "n-" => Ok(parse_signless_b(input, a, -1)?),
25                _ => match parse_n_dash_digits(unit) {
26                    Ok(b) => Ok((a, b)),
27                    Err(()) => {
28                        let unit = unit.clone();
29                        Err(input.new_basic_unexpected_token_error(Token::Ident(unit)))
30                    }
31                }
32            }
33        }
34        Token::Ident(ref value) => {
35            match_ignore_ascii_case! { value,
36                "even" => Ok((2, 0)),
37                "odd" => Ok((2, 1)),
38                "n" => Ok(parse_b(input, 1)?),
39                "-n" => Ok(parse_b(input, -1)?),
40                "n-" => Ok(parse_signless_b(input, 1, -1)?),
41                "-n-" => Ok(parse_signless_b(input, -1, -1)?),
42                _ => {
43                    let (slice, a) = if let Some(stripped) = value.strip_prefix('-') {
44                        (stripped, -1)
45                    } else {
46                        (&**value, 1)
47                    };
48                    match parse_n_dash_digits(slice) {
49                        Ok(b) => Ok((a, b)),
50                        Err(()) => {
51                            let value = value.clone();
52                            Err(input.new_basic_unexpected_token_error(Token::Ident(value)))
53                        }
54                    }
55                }
56            }
57        }
58        Token::Delim('+') => match *input.next_including_whitespace()? {
59            Token::Ident(ref value) => {
60                match_ignore_ascii_case! { value,
61                    "n" => parse_b(input, 1),
62                    "n-" => parse_signless_b(input, 1, -1),
63                    _ => match parse_n_dash_digits(value) {
64                        Ok(b) => Ok((1, b)),
65                        Err(()) => {
66                            let value = value.clone();
67                            Err(input.new_basic_unexpected_token_error(Token::Ident(value)))
68                        }
69                    }
70                }
71            }
72            ref token => {
73                let token = token.clone();
74                Err(input.new_basic_unexpected_token_error(token))
75            }
76        },
77        ref token => {
78            let token = token.clone();
79            Err(input.new_basic_unexpected_token_error(token))
80        }
81    }
82}
83
84fn parse_b<'i>(input: &mut Parser<'i, '_>, a: i32) -> Result<(i32, i32), BasicParseError<'i>> {
85    let start = input.state();
86    match input.next() {
87        Ok(&Token::Delim('+')) => parse_signless_b(input, a, 1),
88        Ok(&Token::Delim('-')) => parse_signless_b(input, a, -1),
89        Ok(&Token::Number {
90            has_sign: true,
91            int_value: Some(b),
92            ..
93        }) => Ok((a, b)),
94        _ => {
95            input.reset(&start);
96            Ok((a, 0))
97        }
98    }
99}
100
101fn parse_signless_b<'i>(
102    input: &mut Parser<'i, '_>,
103    a: i32,
104    b_sign: i32,
105) -> Result<(i32, i32), BasicParseError<'i>> {
106    // FIXME: remove .clone() when lifetimes are non-lexical.
107    match input.next()?.clone() {
108        Token::Number {
109            has_sign: false,
110            int_value: Some(b),
111            ..
112        } => Ok((a, b_sign * b)),
113        token => Err(input.new_basic_unexpected_token_error(token)),
114    }
115}
116
117fn parse_n_dash_digits(string: &str) -> Result<i32, ()> {
118    let bytes = string.as_bytes();
119    if bytes.len() >= 3
120        && bytes[..2].eq_ignore_ascii_case(b"n-")
121        && bytes[2..].iter().all(|&c| c.is_ascii_digit())
122    {
123        Ok(parse_number_saturate(&string[1..]).unwrap()) // Include the minus sign
124    } else {
125        Err(())
126    }
127}
128
129fn parse_number_saturate(string: &str) -> Result<i32, ()> {
130    let mut input = ParserInput::new(string);
131    let mut parser = Parser::new(&mut input);
132    let int = if let Ok(&Token::Number {
133        int_value: Some(int),
134        ..
135    }) = parser.next_including_whitespace_and_comments()
136    {
137        int
138    } else {
139        return Err(());
140    };
141    if !parser.is_exhausted() {
142        return Err(());
143    }
144    Ok(int)
145}