use super::SelectorParseError;
use super::query::{Query, QuerySection, QuerySectionId, TransitionId};
use super::transition::Transition;
#[derive(PartialEq, Debug, Default, Clone, Copy)]
pub struct Save {
pub inner_html: bool,
pub text_content: bool,
}
impl Save {
pub fn only_inner_html() -> Self {
Self {
inner_html: true,
text_content: false,
}
}
pub fn only_text_content() -> Self {
Self {
inner_html: false,
text_content: true,
}
}
pub fn all() -> Self {
Self {
inner_html: true,
text_content: true,
}
}
pub fn none() -> Self {
Self {
inner_html: false,
text_content: false,
}
}
}
#[derive(PartialEq, Debug, Clone, Copy)]
pub enum SelectionKind {
All,
First,
}
#[derive(Debug, Clone)]
pub struct QueryBuilder<'query> {
pub states: Vec<Transition<'query>>,
pub selection: Vec<QuerySection<'query>>,
}
impl<'query> QueryBuilder<'query> {
pub fn all(mut self, query: &'query str, save: Save) -> Result<Self, SelectorParseError> {
assert!(!self.selection.is_empty());
let current_state_len = self.states.len();
let mut states = Transition::generate_transitions_from_string(query)?;
let parent_index = QuerySectionId(self.selection.len() - 1);
let range = TransitionId(current_state_len)..TransitionId(current_state_len + states.len());
self.selection.push(QuerySection::new(
query,
save,
SelectionKind::All,
range,
Some(parent_index),
));
self.states.append(&mut states);
Ok(self)
}
pub fn first(mut self, query: &'query str, save: Save) -> Result<Self, SelectorParseError> {
assert!(!self.selection.is_empty());
let current_state_len = self.states.len();
let mut states = Transition::generate_transitions_from_string(query)?;
let parent_index = QuerySectionId(self.selection.len() - 1);
let range = TransitionId(current_state_len)..TransitionId(current_state_len + states.len());
self.selection.push(QuerySection::new(
query,
save,
SelectionKind::First,
range,
Some(parent_index),
));
self.states.append(&mut states);
Ok(self)
}
pub fn append(&mut self, parent: QuerySectionId, mut other: Self) {
let state_length = self.states.len();
let selection_length = self.selection.len();
let mut last_sibling: Option<QuerySectionId> = {
if parent.index() + 1 == self.selection.len() {
None
} else {
let mut sibling_index = QuerySectionId(parent.index() + 1);
while self.selection[sibling_index.index()].next_sibling.is_some() {
sibling_index = self.selection[sibling_index.index()].next_sibling.unwrap();
}
Some(sibling_index)
}
};
for index in 0..other.selection.len() {
let query = &mut other.selection[index];
query.range.start = TransitionId(query.range.start.index() + state_length);
query.range.end = TransitionId(query.range.end.index() + state_length);
if let Some(next_sibling) = query.next_sibling {
query.next_sibling = Some(QuerySectionId(next_sibling.index() + selection_length));
}
if let Some(idx) = query.parent {
query.parent = Some(QuerySectionId(idx.index() + selection_length));
} else {
query.parent = Some(parent);
let current_index = QuerySectionId(selection_length + index);
last_sibling = match last_sibling {
Some(sibling) => {
if sibling.index() < selection_length {
self.selection[sibling.index()].next_sibling = Some(current_index);
} else {
other.selection[sibling.index() - selection_length].next_sibling =
Some(current_index);
}
Some(current_index)
}
None => Some(current_index),
};
}
}
self.states.append(&mut other.states);
self.selection.append(&mut other.selection);
}
pub fn then<F, I>(mut self, func: F) -> Result<Self, SelectorParseError>
where
F: FnOnce(QueryFactory) -> Result<I, SelectorParseError>,
I: IntoIterator<Item = Self>,
{
let factory = QueryFactory {};
let children = func(factory)?;
let current_index = QuerySectionId(self.selection.len() - 1);
for child in children {
self.append(current_index, child);
}
Ok(self)
}
fn exit_at_section(&self) -> Option<QuerySectionId> {
fn search_for_single_exit_section(
index: QuerySectionId,
list: &[QuerySection<'_>],
) -> Option<QuerySectionId> {
if index.index() >= list.len() {
return None;
}
let section = &list[index.index()];
let stop_here = match §ion.kind {
SelectionKind::All => return None,
SelectionKind::First => section.save != Save::none(),
};
if stop_here {
return Some(index);
}
let mut child = QuerySectionId(index.index() + 1);
if child.index() >= list.len() {
return Some(index);
}
let mut child_response: Option<QuerySectionId> = None;
if let Some(parent) = list[child.index()].parent
&& parent == index
{
loop {
child_response = match child_response {
None => search_for_single_exit_section(child, list),
Some(_) => {
return Some(index);
}
};
if let Some(sibling) = list[child.index()].next_sibling {
child = sibling;
} else {
break;
}
}
}
if child_response.is_some() {
return child_response;
}
Some(index)
}
search_for_single_exit_section(QuerySectionId(0), &self.selection)
}
}
impl<'query> QueryBuilder<'query> {
pub fn build(self) -> Query<'query> {
let exit_at_section_end = self.exit_at_section();
let states_box = self.states.into_boxed_slice();
let query_box = self.selection.into_boxed_slice();
Query {
states: states_box,
queries: query_box,
exit_at_section_end,
}
}
}
pub struct QueryFactory {}
impl<'query> QueryFactory {
pub fn all(
&self,
query: &'query str,
save: Save,
) -> Result<QueryBuilder<'query>, SelectorParseError> {
Query::all(query, save)
}
pub fn first(
&self,
query: &'query str,
save: Save,
) -> Result<QueryBuilder<'query>, SelectorParseError> {
Query::first(query, save)
}
}
#[cfg(test)]
mod tests {
use crate::{ClassSelections, Query, QuerySectionId, Save, SelectionKind};
#[test]
fn test_builder_with_class_chaining() {
let query = Query::all("a.blue.exit", Save::all()).unwrap().build();
assert_eq!(
query.states[0].predicate.classes,
ClassSelections::from_static(&["blue", "exit"])
);
}
#[test]
fn test_early_exit() {
let query = Query::all("a", Save::all()).unwrap();
assert_eq!(query.exit_at_section(), None);
let query = Query::all("a", Save::none()).unwrap();
assert_eq!(query.exit_at_section(), None);
let query = Query::first("a", Save::all()).unwrap();
assert_eq!(query.exit_at_section(), Some(QuerySectionId(0)));
let query = Query::first("a", Save::none()).unwrap();
assert_eq!(query.exit_at_section(), Some(QuerySectionId(0)));
let query = Query::all("p", Save::all())
.unwrap()
.first("a", Save::all())
.unwrap();
assert_eq!(query.exit_at_section(), None);
let query = Query::first("p", Save::all())
.unwrap()
.all("a", Save::all())
.unwrap();
assert_eq!(query.exit_at_section(), Some(QuerySectionId(0)));
let query = Query::first("p", Save::all())
.unwrap()
.first("a", Save::all())
.unwrap();
assert_eq!(query.exit_at_section(), Some(QuerySectionId(0)));
let query = Query::first("p", Save::none())
.unwrap()
.first("a", Save::none())
.unwrap();
assert_eq!(query.exit_at_section(), Some(QuerySectionId(1)));
}
#[test]
fn test_invalid_selectors_fail_to_build() {
let invalid = [
"",
"a > ",
".",
"#",
" a ~ b",
"a + b",
"a[]",
"*",
"a[123=\"321\"]",
];
for selector in invalid {
assert!(Query::all(selector, Save::none()).is_err(), "{selector}");
}
}
#[test]
fn test_then_with_all_and_first() {
let query = Query::all("article", Save::none())
.unwrap()
.then(|article| {
Ok([
article.all("a[href]", Save::all())?,
article.first("h1", Save::only_text_content())?,
])
})
.unwrap()
.build();
assert_eq!(query.queries.len(), 3);
assert_eq!(query.queries[1].source, "a[href]");
assert_eq!(query.queries[2].source, "h1");
}
#[test]
fn test_then_propagates_invalid_selector_from_callback() {
let error = Query::all("article", Save::none())
.unwrap()
.then(|article| {
Ok([
article.all("a[href]", Save::all())?,
article.first("a + b", Save::only_text_content())?,
])
})
.unwrap_err();
assert_eq!(error.message(), "unsupported combinator '+'");
}
#[test]
fn test_then_builds_sibling_links_in_callback_order() {
let query = Query::all("article", Save::none())
.unwrap()
.then(|article| {
Ok([
article.all("a[href]", Save::all())?,
article.first("h1", Save::only_text_content())?,
article.all("p", Save::none())?,
])
})
.unwrap()
.build();
assert_eq!(query.queries.len(), 4);
assert_eq!(query.queries[1].parent, Some(QuerySectionId(0)));
assert_eq!(query.queries[2].parent, Some(QuerySectionId(0)));
assert_eq!(query.queries[3].parent, Some(QuerySectionId(0)));
assert_eq!(query.queries[1].next_sibling, Some(QuerySectionId(2)));
assert_eq!(query.queries[2].next_sibling, Some(QuerySectionId(3)));
assert_eq!(query.queries[3].next_sibling, None);
assert_eq!(query.queries[1].kind, SelectionKind::All);
assert_eq!(query.queries[2].kind, SelectionKind::First);
assert_eq!(query.queries[3].kind, SelectionKind::All);
}
#[test]
fn test_nested_then_supports_all_and_first() {
let query = Query::all("article", Save::none())
.unwrap()
.then(|article| {
Ok([article.all("section", Save::none())?.then(|section| {
Ok([
section.first("h2", Save::only_text_content())?,
section.all("a[href]", Save::all())?,
])
})?])
})
.unwrap()
.build();
assert_eq!(query.queries.len(), 4);
assert_eq!(query.queries[1].source, "section");
assert_eq!(query.queries[2].source, "h2");
assert_eq!(query.queries[3].source, "a[href]");
assert_eq!(query.queries[2].parent, Some(QuerySectionId(1)));
assert_eq!(query.queries[3].parent, Some(QuerySectionId(1)));
assert_eq!(query.queries[2].next_sibling, Some(QuerySectionId(3)));
}
#[test]
fn test_then_returns_error_without_partial_append() {
let builder = Query::all("article", Save::none())
.unwrap()
.then(|article| {
let first = article.all("section", Save::none())?;
let second = article.first("a + b", Save::all());
match second {
Ok(second) => Ok([first, second]),
Err(err) => Err(err),
}
});
assert!(builder.is_err());
let error = builder.unwrap_err();
assert_eq!(error.message(), "unsupported combinator '+'");
}
}