use std::str::Chars;
use std::iter::Peekable;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct UnexpectedTokenError(pub char);
#[derive(Clone, Debug)]
pub struct CompoundSelector {
pub scope: Scope,
pub parts: Vec<Selector>,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Scope {
DirectChild,
IndirectChild,
}
#[derive(Clone, Debug)]
pub enum Selector {
Id(String),
TagName(String),
Attribute(String, MatchType, String),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MatchType {
Equals,
}
macro_rules! expect_token {
($token_option: expr, $token: expr) => {
match $token_option {
Some($token) => { },
Some(token) => return Err(UnexpectedTokenError(token)),
None => return Err(UnexpectedTokenError(' ')),
}
}
}
#[inline]
fn non_digit(c: char) -> bool {
('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
}
#[inline]
fn allowed_character(c: char) -> bool {
non_digit(c) || ('0' <= c && c <= '9') || c == '-' || c == '_'
}
#[inline]
fn valid_start_token(c: char) -> bool {
c == '#' || c == '['
}
fn extract_valid_string(chars: &mut Peekable<Chars>) -> Result<String, UnexpectedTokenError> {
extract_valid_string_until_token(chars, ' ')
}
fn extract_valid_string_until_token(chars: &mut Peekable<Chars>, stop_token: char) -> Result<String, UnexpectedTokenError> {
let mut string = String::new();
while let Some(&c) = chars.peek() {
if c == stop_token {
chars.next().unwrap();
break;
} else if allowed_character(c) {
string.push(chars.next().unwrap());
} else if valid_start_token(c) {
break;
} else {
return Err(UnexpectedTokenError(c));
}
}
return Ok(string);
}
impl Selector {
fn create_list(string: &str) -> Result<Vec<Selector>, UnexpectedTokenError> {
let mut selectors = Vec::new();
let mut chars = string.chars().peekable();
while let Some(&c) = chars.peek() {
match Selector::next_selector(c, &mut chars) {
Ok(selector) =>
selectors.push(selector),
Err(err) =>
return Err(err),
}
}
return Ok(selectors);
}
fn next_selector(c: char, chars: &mut Peekable<Chars>) -> Result<Selector, UnexpectedTokenError> {
if non_digit(c) {
Selector::create_tag_name(chars)
} else if c == '#' {
Selector::create_id(chars)
} else if c == '[' {
Selector::create_attribute(chars)
} else {
Err(UnexpectedTokenError(c))
}
}
fn create_tag_name(chars: &mut Peekable<Chars>) -> Result<Selector, UnexpectedTokenError> {
extract_valid_string(chars).map(|s| Selector::TagName(s))
}
fn create_id(chars: &mut Peekable<Chars>) -> Result<Selector, UnexpectedTokenError> {
match chars.next() {
Some('#') =>
return extract_valid_string(chars).map(|s| Selector::Id(s)),
Some(token) =>
return Err(UnexpectedTokenError(token)),
None =>
return Err(UnexpectedTokenError(' ')),
}
}
fn create_attribute(chars: &mut Peekable<Chars>) -> Result<Selector, UnexpectedTokenError> {
expect_token!(chars.next(), '[');
extract_valid_string_until_token(chars, '=').and_then(|attribute| {
Ok((attribute, MatchType::Equals))
}).and_then(|(attribute, match_type)| {
let result = if Some(&'"') == chars.peek() {
chars.next().unwrap();
let result = extract_valid_string_until_token(chars, '"');
expect_token!(chars.next(), ']');
result
} else {
extract_valid_string_until_token(chars, ']')
};
result.map(|value| {
Selector::Attribute(attribute, match_type, value)
})
})
}
}
struct SelectorParts<I: Iterator<Item=String>> {
inner_iter: I,
}
impl<I: Iterator<Item=String>> Iterator for SelectorParts<I> {
type Item = (Scope, String);
fn next(&mut self) -> Option<Self::Item> {
self.inner_iter.next().and_then(|next_part| {
if &next_part == ">" {
Some((Scope::DirectChild, self.inner_iter.next().unwrap()))
} else {
Some((Scope::IndirectChild, next_part))
}
})
}
}
impl CompoundSelector {
pub fn parse(selector: &str) -> Result<Vec<CompoundSelector>, UnexpectedTokenError> {
let normalized_selector = selector.split(">")
.collect::<Vec<&str>>()
.join(" > ");
let selector_parts = SelectorParts {
inner_iter: normalized_selector.split_whitespace().into_iter().map(|s| s.to_string()),
};
selector_parts
.fold(Ok(Vec::new()), |result_so_far, (scope, part)| {
if let Ok(mut compound_selectors) = result_so_far {
Selector::create_list(&part).map(|parts| {
compound_selectors.push(CompoundSelector {
scope: scope,
parts: parts
});
compound_selectors
})
} else {
result_so_far
}
})
}
}