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}