mod eval;
pub mod parser;
pub mod types;
pub use parser::CssSelectorError;
pub use types::SelectorGroup;
use crate::tree::{Document, NodeId};
pub fn select(
doc: &Document,
scope: NodeId,
selector: &str,
) -> Result<Vec<NodeId>, CssSelectorError> {
let group = parser::parse_selector(selector)?;
Ok(eval::select(doc, scope, &group))
}
pub fn select_with(doc: &Document, scope: NodeId, group: &SelectorGroup) -> Vec<NodeId> {
eval::select(doc, scope, group)
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
fn test_doc() -> Document {
Document::parse_str(
r#"<html>
<body>
<div id="main" class="container wide">
<h1>Title</h1>
<p class="intro">Hello</p>
<p class="body">World</p>
<ul>
<li class="active">One</li>
<li>Two</li>
<li>Three</li>
</ul>
<a href="https://example.com">Link</a>
<img src="photo.png"/>
<span lang="en-US">English</span>
</div>
<div class="sidebar">
<p>Side</p>
</div>
</body>
</html>"#,
)
.unwrap()
}
#[test]
fn test_select_by_tag() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let ps = select(&doc, root, "p").unwrap();
assert_eq!(ps.len(), 3);
}
#[test]
fn test_select_by_class() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, ".intro").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(doc.text_content(result[0]), "Hello");
}
#[test]
fn test_select_by_id() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "#main").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(doc.node_name(result[0]), Some("div"));
}
#[test]
fn test_select_descendant() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "div p").unwrap();
assert_eq!(result.len(), 3); }
#[test]
fn test_select_child() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "#main > p").unwrap();
assert_eq!(result.len(), 2);
}
#[test]
fn test_select_adjacent_sibling() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "h1 + p").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(doc.text_content(result[0]), "Hello");
}
#[test]
fn test_select_general_sibling() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "h1 ~ p").unwrap();
assert_eq!(result.len(), 2);
}
#[test]
fn test_select_group() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "h1, img").unwrap();
assert_eq!(result.len(), 2);
}
#[test]
fn test_select_attr_existence() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "[href]").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(doc.node_name(result[0]), Some("a"));
}
#[test]
fn test_select_attr_prefix() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "[href^=\"https\"]").unwrap();
assert_eq!(result.len(), 1);
}
#[test]
fn test_select_attr_suffix() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "[src$=\".png\"]").unwrap();
assert_eq!(result.len(), 1);
}
#[test]
fn test_select_attr_dash_prefix() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "[lang|=\"en\"]").unwrap();
assert_eq!(result.len(), 1);
}
#[test]
fn test_select_first_child() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "li:first-child").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(doc.text_content(result[0]), "One");
}
#[test]
fn test_select_last_child() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "li:last-child").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(doc.text_content(result[0]), "Three");
}
#[test]
fn test_select_not() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "li:not(.active)").unwrap();
assert_eq!(result.len(), 2);
}
#[test]
fn test_select_nth_child_odd() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "li:nth-child(odd)").unwrap();
assert_eq!(result.len(), 2); }
#[test]
fn test_select_empty() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, ":empty").unwrap();
assert!(result.iter().any(|&n| doc.node_name(n) == Some("img")));
}
#[test]
fn test_select_universal() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "#main > *").unwrap();
assert!(result.len() >= 5);
}
#[test]
fn test_select_multiple_classes() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, ".container.wide").unwrap();
assert_eq!(result.len(), 1);
}
#[test]
fn test_select_complex() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "div.container > ul li.active").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(doc.text_content(result[0]), "One");
}
#[test]
fn test_select_error() {
let doc = test_doc();
let root = doc.root_element().unwrap();
assert!(select(&doc, root, ">>>").is_err());
}
#[test]
fn test_id_map_auto_populated() {
let doc = test_doc();
let node = doc.element_by_id("main").unwrap();
assert_eq!(doc.node_name(node), Some("div"));
}
#[test]
fn test_fast_id_select() {
let doc = test_doc();
let root = doc.root_element().unwrap();
let result = select(&doc, root, "#main").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(doc.node_name(result[0]), Some("div"));
}
}