use super::builder::ElementPredicate;
use crate::Reader;
use crate::query::compiler::SelectorParseError;
#[derive(Debug, PartialEq, Clone)]
pub enum Combinator {
Child, Descendant, NextSibling,
SubsequentSibling,
Namespace, }
impl Combinator {
fn next<'a>(reader: &mut Reader<'a>) -> Option<Self> {
if let Some(token) = reader.peek()
&& !matches!(token, b'>' | b' ' | b'+' | b'~' | b'|')
{
return None;
}
match reader.next()? {
b'>' => Some(Self::Child),
b' ' => Some(Self::Descendant),
b'+' => Some(Self::NextSibling),
b'~' => Some(Self::SubsequentSibling),
b'|' => Some(Self::Namespace),
_ => panic!("Not possible root"),
}
}
}
impl<'a> Combinator {
pub fn try_from(reader: &mut Reader<'a>) -> Option<Self> {
let mut combinator: Option<Self> = None;
while let Some(next_combinator) = Combinator::next(reader) {
match combinator {
Option::None => combinator = Some(next_combinator),
Some(c) if c == Self::Descendant && next_combinator != Self::Descendant => {
combinator = Some(next_combinator);
}
_ => (),
}
}
combinator
}
pub(crate) fn evaluate(&self, last_depth: u16, current_depth: u16) -> bool {
match self {
Combinator::Child => last_depth + 1 == current_depth,
Combinator::Descendant => last_depth == 0 || current_depth != last_depth,
Combinator::NextSibling => last_depth == current_depth,
Combinator::SubsequentSibling => true,
Combinator::Namespace => panic!("Why are you using Namespace Selector ???"),
}
}
}
pub struct Lexer {}
impl Lexer {
#[cfg(test)]
pub fn next<'query>(
reader: &mut Reader<'query>,
) -> Option<(Combinator, ElementPredicate<'query>)> {
Self::try_next(reader, false).unwrap()
}
pub fn try_next<'query>(
reader: &mut Reader<'query>,
seen_selector: bool,
) -> Result<Option<(Combinator, ElementPredicate<'query>)>, SelectorParseError> {
if reader.eof() {
return Ok(None);
}
let combinator = Self::parse_combinator(reader, seen_selector)?;
let element = ElementPredicate::try_from(reader)?;
Ok(Some((combinator, element)))
}
fn parse_combinator<'query>(
reader: &mut Reader<'query>,
seen_selector: bool,
) -> Result<Combinator, SelectorParseError> {
let mut saw_whitespace = false;
while let Some(token) = reader.peek() {
if !token.is_ascii_whitespace() {
break;
}
saw_whitespace = true;
reader.skip();
}
match reader.peek() {
None => Err(SelectorParseError::new(
"missing selector after combinator",
reader.get_position(),
)),
Some(b'>') => {
reader.skip();
while let Some(token) = reader.peek() {
if !token.is_ascii_whitespace() {
break;
}
reader.skip();
}
Ok(Combinator::Child)
}
Some(b'+') => Err(SelectorParseError::new(
"unsupported combinator '+'",
reader.get_position(),
)),
Some(b'~') => Err(SelectorParseError::new(
"unsupported combinator '~'",
reader.get_position(),
)),
Some(b'|') => Err(SelectorParseError::new(
"unsupported combinator '|'",
reader.get_position(),
)),
Some(_) if saw_whitespace || !seen_selector => Ok(Combinator::Descendant),
Some(_) => Ok(Combinator::Descendant),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{AttributeSelections, ClassSelections};
#[test]
fn test_whitespace_only_returns_none() {
let mut reader = Reader::new(" \n\t ");
assert_eq!(Lexer::try_next(&mut reader, false).unwrap(), None);
}
#[test]
fn test_leading_whitespace_uses_descendant_combinator() {
let mut reader = Reader::new(" article#main.hero");
let (combinator, element) = Lexer::try_next(&mut reader, false).unwrap().unwrap();
assert_eq!(combinator, Combinator::Descendant);
assert_eq!(
element,
ElementPredicate {
name: Some("article"),
id: Some("main"),
classes: ClassSelections::from_static(&["hero"]),
attributes: AttributeSelections::from_static(&[]),
}
);
}
#[test]
fn test_basic_element_selection_with_combinator() {
let mut reader = Reader::new("element#id.class > other#other_id.other_class");
let (first_combinator, first_element) = Lexer::next(&mut reader).unwrap();
let (second_combinator, second_element) = Lexer::next(&mut reader).unwrap();
assert_eq!(first_combinator, Combinator::Descendant);
assert_eq!(
first_element,
ElementPredicate {
name: Some("element"),
id: Some("id"),
classes: ClassSelections::from_static(&["class"]),
attributes: AttributeSelections::from_static(&[])
}
);
assert_eq!(second_combinator, Combinator::Child);
assert_eq!(
second_element,
ElementPredicate {
name: Some("other"),
id: Some("other_id"),
classes: ClassSelections::from_static(&["other_class"]),
attributes: AttributeSelections::from_static(&[]),
}
);
}
#[test]
fn test_unsupported_combinator_leading_selector() {
let mut reader = Reader::new("~ element#id.class > other#other_id.other_class");
let error = Lexer::try_next(&mut reader, false).unwrap_err();
assert_eq!(error.message(), "unsupported combinator '~'");
}
#[test]
fn test_child_combinator_after_seen_selector() {
let mut reader = Reader::new("> span.highlight");
let (combinator, element) = Lexer::try_next(&mut reader, true).unwrap().unwrap();
assert_eq!(combinator, Combinator::Child);
assert_eq!(
element,
ElementPredicate {
name: Some("span"),
id: None,
classes: ClassSelections::from_static(&["highlight"]),
attributes: AttributeSelections::from_static(&[]),
}
);
}
#[test]
fn test_missing_selector_after_child_combinator() {
let mut reader = Reader::new("> ");
let error = Lexer::try_next(&mut reader, true).unwrap_err();
assert_eq!(error.message(), "missing selector element");
}
#[test]
fn test_unsupported_adjacent_sibling_combinator_after_selector() {
let mut reader = Reader::new("+ a");
let error = Lexer::try_next(&mut reader, true).unwrap_err();
assert_eq!(error.message(), "unsupported combinator '+'");
}
#[test]
fn test_unsupported_namespace_combinator_after_selector() {
let mut reader = Reader::new("| a");
let error = Lexer::try_next(&mut reader, true).unwrap_err();
assert_eq!(error.message(), "unsupported combinator '|'");
}
#[test]
fn test_illegal_character_bang() {
let mut reader = Reader::new("!");
let error = Lexer::try_next(&mut reader, false).unwrap_err();
assert_eq!(error.message(), "illegal selector token");
}
#[test]
fn test_illegal_character_at() {
let mut reader = Reader::new("@");
let error = Lexer::try_next(&mut reader, false).unwrap_err();
assert_eq!(error.message(), "illegal selector token");
}
#[test]
fn test_illegal_character_dollar() {
let mut reader = Reader::new("$");
let error = Lexer::try_next(&mut reader, false).unwrap_err();
assert_eq!(error.message(), "illegal selector token");
}
#[test]
fn test_illegal_character_percent() {
let mut reader = Reader::new("%");
let error = Lexer::try_next(&mut reader, false).unwrap_err();
assert_eq!(error.message(), "illegal selector token");
}
#[test]
fn test_illegal_character_caret() {
let mut reader = Reader::new("^");
let error = Lexer::try_next(&mut reader, false).unwrap_err();
assert_eq!(error.message(), "illegal selector token");
}
#[test]
fn test_illegal_character_ampersand() {
let mut reader = Reader::new("&");
let error = Lexer::try_next(&mut reader, false).unwrap_err();
assert_eq!(error.message(), "illegal selector token");
}
#[test]
fn test_illegal_character_open_paren() {
let mut reader = Reader::new("(");
let error = Lexer::try_next(&mut reader, false).unwrap_err();
assert_eq!(error.message(), "illegal selector token");
}
#[test]
fn test_illegal_character_close_paren() {
let mut reader = Reader::new(")");
let error = Lexer::try_next(&mut reader, false).unwrap_err();
assert_eq!(error.message(), "illegal selector token");
}
#[test]
fn test_illegal_character_question_mark() {
let mut reader = Reader::new("?");
let error = Lexer::try_next(&mut reader, false).unwrap_err();
assert_eq!(error.message(), "illegal selector token");
}
#[test]
fn test_illegal_character_backtick() {
let mut reader = Reader::new("`");
let error = Lexer::try_next(&mut reader, false).unwrap_err();
assert_eq!(error.message(), "illegal selector token");
}
#[test]
fn test_illegal_character_less_than() {
let mut reader = Reader::new("<");
let error = Lexer::try_next(&mut reader, false).unwrap_err();
assert_eq!(error.message(), "illegal selector token");
}
#[test]
fn test_illegal_character_colon() {
let mut reader = Reader::new(":");
let error = Lexer::try_next(&mut reader, false).unwrap_err();
assert_eq!(error.message(), "illegal selector token");
}
#[test]
fn test_illegal_character_semicolon() {
let mut reader = Reader::new(";");
let error = Lexer::try_next(&mut reader, false).unwrap_err();
assert_eq!(error.message(), "illegal selector token");
}
}