scrape_core/query/compiled.rs
1//! Pre-compiled CSS selectors for efficient repeated matching.
2
3use selectors::SelectorList;
4
5use super::{QueryResult, ScrapeSelector, selector::parse_selector};
6
7/// A pre-compiled CSS selector for efficient repeated matching.
8///
9/// Compiled selectors avoid the overhead of parsing the selector string on each query.
10/// They can be reused across multiple documents and queries.
11///
12/// # Examples
13///
14/// ```rust
15/// use scrape_core::{Soup, query::CompiledSelector};
16///
17/// let selector = CompiledSelector::compile("div.item > span").unwrap();
18/// let soup = Soup::parse("<div class='item'><span>A</span></div>");
19///
20/// let results = soup.select_compiled(&selector);
21/// assert_eq!(results.len(), 1);
22/// ```
23#[derive(Debug, Clone)]
24pub struct CompiledSelector {
25 selector_list: SelectorList<ScrapeSelector>,
26 source: String,
27}
28
29impl CompiledSelector {
30 /// Compiles a CSS selector string.
31 ///
32 /// # Errors
33 ///
34 /// Returns [`QueryError::InvalidSelector`] if the selector syntax is invalid.
35 ///
36 /// # Examples
37 ///
38 /// ```rust
39 /// use scrape_core::query::CompiledSelector;
40 ///
41 /// let selector = CompiledSelector::compile("div.container > span").unwrap();
42 /// assert_eq!(selector.source(), "div.container > span");
43 /// ```
44 pub fn compile(selector: &str) -> QueryResult<Self> {
45 let selector_list = parse_selector(selector)?;
46 Ok(Self { selector_list, source: selector.to_string() })
47 }
48
49 /// Returns the underlying selector list for matching.
50 #[must_use]
51 pub fn selector_list(&self) -> &SelectorList<ScrapeSelector> {
52 &self.selector_list
53 }
54
55 /// Returns the original selector string.
56 ///
57 /// # Examples
58 ///
59 /// ```rust
60 /// use scrape_core::query::CompiledSelector;
61 ///
62 /// let selector = CompiledSelector::compile("div").unwrap();
63 /// assert_eq!(selector.source(), "div");
64 /// ```
65 #[must_use]
66 pub fn source(&self) -> &str {
67 &self.source
68 }
69}
70
71/// Compiles a CSS selector string (convenience function).
72///
73/// This is equivalent to [`CompiledSelector::compile`].
74///
75/// # Errors
76///
77/// Returns [`QueryError::InvalidSelector`] if the selector syntax is invalid.
78///
79/// # Examples
80///
81/// ```rust
82/// use scrape_core::query::compile_selector;
83///
84/// let selector = compile_selector("div > span").unwrap();
85/// ```
86pub fn compile_selector(selector: &str) -> QueryResult<CompiledSelector> {
87 CompiledSelector::compile(selector)
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_compile_valid_selector() {
96 let selector = CompiledSelector::compile("div.class > span").unwrap();
97 assert_eq!(selector.source(), "div.class > span");
98 }
99
100 #[test]
101 fn test_compile_invalid_selector_returns_error() {
102 let result = CompiledSelector::compile("[");
103 assert!(result.is_err());
104 }
105
106 #[test]
107 fn test_compile_selector_function() {
108 let selector = compile_selector("div").unwrap();
109 assert_eq!(selector.source(), "div");
110 }
111
112 #[test]
113 fn test_selector_list_accessor() {
114 let selector = CompiledSelector::compile("span").unwrap();
115 assert_eq!(selector.selector_list().slice().len(), 1);
116 }
117
118 #[test]
119 fn test_clone() {
120 let selector = CompiledSelector::compile("div").unwrap();
121 let cloned = selector.clone();
122 assert_eq!(selector.source(), cloned.source());
123 }
124}