use super::*;
pub(crate) fn parse_selector_chain(selector: &str) -> Result<Vec<SelectorPart>> {
let selector = selector.trim();
if selector.is_empty() {
return Err(Error::UnsupportedSelector(selector.into()));
}
let tokens = tokenize_selector(selector)?;
let mut steps = Vec::new();
let mut pending_combinator: Option<SelectorCombinator> = None;
for token in tokens {
if token == ">" || token == "+" || token == "~" {
if pending_combinator.is_some() || steps.is_empty() {
return Err(Error::UnsupportedSelector(selector.into()));
}
pending_combinator = Some(match token.as_str() {
">" => SelectorCombinator::Child,
"+" => SelectorCombinator::AdjacentSibling,
"~" => SelectorCombinator::GeneralSibling,
_ => unreachable!(),
});
continue;
}
let step = parse_selector_step(&token)?;
let combinator = if steps.is_empty() {
None
} else {
Some(
pending_combinator
.take()
.unwrap_or(SelectorCombinator::Descendant),
)
};
steps.push(SelectorPart { step, combinator });
}
if steps.is_empty() || pending_combinator.is_some() {
return Err(Error::UnsupportedSelector(selector.into()));
}
Ok(steps)
}
pub(crate) fn parse_selector_groups(selector: &str) -> Result<Vec<Vec<SelectorPart>>> {
let groups = split_selector_groups(selector)?;
let mut parsed = Vec::with_capacity(groups.len());
for group in groups {
parsed.push(parse_selector_chain(&group)?);
}
Ok(parsed)
}
pub(crate) fn split_selector_groups(selector: &str) -> Result<Vec<String>> {
let mut groups = Vec::new();
let mut current = String::new();
let mut bracket_depth = 0usize;
let mut paren_depth = 0usize;
for ch in selector.chars() {
match ch {
'[' => {
bracket_depth += 1;
current.push(ch);
}
']' => {
if bracket_depth == 0 {
return Err(Error::UnsupportedSelector(selector.into()));
}
bracket_depth -= 1;
current.push(ch);
}
'(' => {
paren_depth += 1;
current.push(ch);
}
')' => {
if paren_depth == 0 {
return Err(Error::UnsupportedSelector(selector.into()));
}
paren_depth -= 1;
current.push(ch);
}
',' if bracket_depth == 0 && paren_depth == 0 => {
let trimmed = current.trim();
if trimmed.is_empty() {
return Err(Error::UnsupportedSelector(selector.into()));
}
groups.push(trimmed.to_string());
current.clear();
}
_ => current.push(ch),
}
}
if bracket_depth != 0 {
return Err(Error::UnsupportedSelector(selector.into()));
}
if paren_depth != 0 {
return Err(Error::UnsupportedSelector(selector.into()));
}
let trimmed = current.trim();
if trimmed.is_empty() {
return Err(Error::UnsupportedSelector(selector.into()));
}
groups.push(trimmed.to_string());
Ok(groups)
}
pub(crate) fn tokenize_selector(selector: &str) -> Result<Vec<String>> {
let mut tokens = Vec::new();
let mut current = String::new();
let mut bracket_depth = 0usize;
let mut paren_depth = 0usize;
for ch in selector.chars() {
match ch {
'[' => {
bracket_depth += 1;
current.push(ch);
}
']' => {
if bracket_depth == 0 {
return Err(Error::UnsupportedSelector(selector.into()));
}
bracket_depth -= 1;
current.push(ch);
}
'(' => {
paren_depth += 1;
current.push(ch);
}
')' => {
if paren_depth == 0 {
return Err(Error::UnsupportedSelector(selector.into()));
}
paren_depth -= 1;
current.push(ch);
}
'>' | '+' | '~' if bracket_depth == 0 && paren_depth == 0 => {
if !current.trim().is_empty() {
tokens.push(current.trim().to_string());
}
current.clear();
tokens.push(ch.to_string());
}
ch if ch.is_ascii_whitespace() && bracket_depth == 0 && paren_depth == 0 => {
if !current.trim().is_empty() {
tokens.push(current.trim().to_string());
}
current.clear();
}
_ => current.push(ch),
}
}
if bracket_depth != 0 {
return Err(Error::UnsupportedSelector(selector.into()));
}
if paren_depth != 0 {
return Err(Error::UnsupportedSelector(selector.into()));
}
if !current.trim().is_empty() {
tokens.push(current.trim().to_string());
}
Ok(tokens)
}