1pub mod ast;
57pub mod bloom;
59pub mod matcher;
61pub mod parser;
63pub mod xpath;
65
66use std::cell::RefCell;
67use std::collections::{HashMap, VecDeque};
68use std::sync::Arc;
69
70use fhp_core::error::{SelectorError, XPathError};
71use fhp_core::tag::Tag;
72use fhp_tree::node::{NodeFlags, NodeId};
73use fhp_tree::{Document, NodeRef};
74
75use matcher::{select_all_list, select_first_list};
76use parser::parse_selector;
77use xpath::ast::XPathResult;
78
79#[inline]
80fn is_document_element(n: &fhp_tree::node::Node) -> bool {
81 n.depth > 0
82 && !n.flags.has(NodeFlags::IS_TEXT)
83 && !n.flags.has(NodeFlags::IS_COMMENT)
84 && !n.flags.has(NodeFlags::IS_DOCTYPE)
85}
86
87#[derive(Clone)]
105pub struct CompiledSelector {
106 list: Arc<ast::SelectorList>,
107}
108
109impl CompiledSelector {
110 pub fn new(css: &str) -> Result<Self, SelectorError> {
116 Ok(Self {
117 list: Arc::new(parse_selector(css)?),
118 })
119 }
120
121 pub fn as_list(&self) -> &ast::SelectorList {
123 &self.list
124 }
125}
126
127impl core::fmt::Debug for CompiledSelector {
128 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
129 f.debug_struct("CompiledSelector")
130 .field("selectors", &self.list.selectors.len())
131 .finish()
132 }
133}
134
135const SELECTOR_CACHE_CAPACITY: usize = 256;
137const MAX_CACHED_SELECTOR_LEN: usize = 512;
139
140struct SelectorCache {
141 map: HashMap<String, Arc<ast::SelectorList>>,
142 order: VecDeque<String>,
143}
144
145impl SelectorCache {
146 fn new() -> Self {
147 Self {
148 map: HashMap::with_capacity(SELECTOR_CACHE_CAPACITY),
149 order: VecDeque::with_capacity(SELECTOR_CACHE_CAPACITY),
150 }
151 }
152
153 fn get(&self, css: &str) -> Option<Arc<ast::SelectorList>> {
154 self.map.get(css).map(Arc::clone)
155 }
156
157 fn insert(&mut self, css: &str, list: Arc<ast::SelectorList>) {
158 if self.map.contains_key(css) {
159 self.map.insert(css.to_owned(), list);
160 return;
161 }
162
163 if self.map.len() >= SELECTOR_CACHE_CAPACITY {
164 if let Some(old_key) = self.order.pop_front() {
165 self.map.remove(&old_key);
166 }
167 }
168
169 let key = css.to_owned();
170 self.order.push_back(key.clone());
171 self.map.insert(key, list);
172 }
173}
174
175thread_local! {
176 static SELECTOR_CACHE: RefCell<SelectorCache> = RefCell::new(SelectorCache::new());
177}
178
179#[inline]
180fn parse_selector_cached(css: &str) -> Result<Arc<ast::SelectorList>, SelectorError> {
181 if css.len() > MAX_CACHED_SELECTOR_LEN {
182 return Ok(Arc::new(parse_selector(css)?));
183 }
184
185 SELECTOR_CACHE.with(|cache| {
186 if let Some(list) = cache.borrow().get(css) {
187 return Ok(list);
188 }
189
190 let parsed = Arc::new(parse_selector(css)?);
191 cache.borrow_mut().insert(css, Arc::clone(&parsed));
192 Ok(parsed)
193 })
194}
195
196fn merge_dedup_results(
201 _arena: &fhp_tree::arena::Arena,
202 roots: &[NodeId],
203 mut query: impl FnMut(NodeId) -> Vec<NodeId>,
204) -> Vec<NodeId> {
205 let mut results = Vec::new();
206 let mut max_seen = u32::MAX; for &root in roots {
208 for id in query(root) {
209 let idx = id.index() as u32;
210 if results.is_empty() || idx > max_seen {
211 max_seen = idx;
212 results.push(id);
213 } else if idx != max_seen {
214 if !results.contains(&id) {
217 results.push(id);
218 }
219 }
220 }
221 }
222 results
223}
224
225pub struct Selection<'a> {
230 doc: &'a Document,
231 nodes: Vec<NodeId>,
232}
233
234impl<'a> Selection<'a> {
235 fn new(doc: &'a Document, nodes: Vec<NodeId>) -> Self {
237 Self { doc, nodes }
238 }
239
240 pub fn first(&self) -> Option<NodeRef<'a>> {
242 self.nodes.first().map(|&id| self.doc.get(id))
243 }
244
245 pub fn iter(&self) -> impl Iterator<Item = NodeRef<'a>> + '_ {
247 self.nodes.iter().map(|&id| self.doc.get(id))
248 }
249
250 pub fn node_ids(&self) -> &[NodeId] {
252 &self.nodes
253 }
254
255 pub fn text(&self) -> String {
257 self.iter()
258 .map(|n| n.text_content())
259 .collect::<Vec<_>>()
260 .join("")
261 }
262
263 pub fn attr(&self, name: &str) -> Option<&'a str> {
265 self.first()?.attr(name)
266 }
267
268 pub fn inner_html(&self) -> String {
270 self.first().map(|n| n.inner_html()).unwrap_or_default()
271 }
272
273 pub fn len(&self) -> usize {
275 self.nodes.len()
276 }
277
278 pub fn is_empty(&self) -> bool {
280 self.nodes.is_empty()
281 }
282
283 pub fn select_compiled(&self, sel: &CompiledSelector) -> Result<Selection<'a>, SelectorError> {
288 let list = &sel.list;
289 if self.nodes.len() == 1 {
290 let results = select_all_list(self.doc.arena(), self.nodes[0], list);
291 return Ok(Selection::new(self.doc, results));
292 }
293 let results = merge_dedup_results(self.doc.arena(), &self.nodes, |root| {
294 select_all_list(self.doc.arena(), root, list)
295 });
296 Ok(Selection::new(self.doc, results))
297 }
298
299 pub fn select(&self, css: &str) -> Result<Selection<'a>, SelectorError> {
304 let list = parse_selector_cached(css)?;
305 if self.nodes.len() == 1 {
306 let results = select_all_list(self.doc.arena(), self.nodes[0], &list);
308 return Ok(Selection::new(self.doc, results));
309 }
310 let results = merge_dedup_results(self.doc.arena(), &self.nodes, |root| {
311 select_all_list(self.doc.arena(), root, &list)
312 });
313 Ok(Selection::new(self.doc, results))
314 }
315
316 pub fn xpath(&self, expr: &str) -> Result<XPathResult, XPathError> {
328 let parsed = xpath::parser::parse_xpath(expr)?;
329 let mut all_nodes = Vec::new();
330 let mut all_strings = Vec::new();
331 let mut seen = std::collections::HashSet::new();
332
333 for &node_id in &self.nodes {
334 let result = xpath::eval::evaluate(&parsed, self.doc.arena(), node_id);
335 match result {
336 XPathResult::Nodes(nodes) => {
337 for id in nodes {
338 if seen.insert(id) {
339 all_nodes.push(id);
340 }
341 }
342 }
343 XPathResult::Strings(strings) => {
344 all_strings.extend(strings);
345 }
346 XPathResult::Boolean(b) => return Ok(XPathResult::Boolean(b)),
347 }
348 }
349
350 if !all_strings.is_empty() {
351 Ok(XPathResult::Strings(all_strings))
352 } else {
353 Ok(XPathResult::Nodes(all_nodes))
354 }
355 }
356}
357
358impl<'a> IntoIterator for &'a Selection<'a> {
359 type Item = NodeRef<'a>;
360 type IntoIter = SelectionIter<'a>;
361
362 fn into_iter(self) -> Self::IntoIter {
363 SelectionIter {
364 doc: self.doc,
365 inner: self.nodes.iter(),
366 }
367 }
368}
369
370pub struct SelectionIter<'a> {
372 doc: &'a Document,
373 inner: std::slice::Iter<'a, NodeId>,
374}
375
376impl<'a> Iterator for SelectionIter<'a> {
377 type Item = NodeRef<'a>;
378
379 fn next(&mut self) -> Option<Self::Item> {
380 self.inner.next().map(|&id| self.doc.get(id))
381 }
382
383 fn size_hint(&self) -> (usize, Option<usize>) {
384 self.inner.size_hint()
385 }
386}
387
388impl<'a> ExactSizeIterator for SelectionIter<'a> {}
389
390pub trait Selectable {
394 fn select(&self, css: &str) -> Result<Selection<'_>, SelectorError>;
411
412 fn select_compiled(&self, sel: &CompiledSelector) -> Result<Selection<'_>, SelectorError>;
417
418 fn select_first_compiled(
420 &self,
421 sel: &CompiledSelector,
422 ) -> Result<Option<NodeRef<'_>>, SelectorError>;
423
424 fn select_first(&self, css: &str) -> Result<Option<NodeRef<'_>>, SelectorError>;
426
427 fn find_by_tag(&self, tag: Tag) -> Selection<'_>;
429
430 fn find_by_id(&self, id: &str) -> Option<NodeRef<'_>>;
435
436 fn find_by_class(&self, class: &str) -> Selection<'_>;
438
439 fn find_by_attr(&self, name: &str, value: &str) -> Selection<'_>;
441
442 fn xpath(&self, expr: &str) -> Result<XPathResult, XPathError>;
463}
464
465impl Selectable for Document {
466 fn select(&self, css: &str) -> Result<Selection<'_>, SelectorError> {
467 let list = parse_selector_cached(css)?;
468 let nodes = select_all_list(self.arena(), self.root_id(), &list);
469 Ok(Selection::new(self, nodes))
470 }
471
472 fn select_compiled(&self, sel: &CompiledSelector) -> Result<Selection<'_>, SelectorError> {
473 let nodes = select_all_list(self.arena(), self.root_id(), &sel.list);
474 Ok(Selection::new(self, nodes))
475 }
476
477 fn select_first_compiled(
478 &self,
479 sel: &CompiledSelector,
480 ) -> Result<Option<NodeRef<'_>>, SelectorError> {
481 let node = select_first_list(self.arena(), self.root_id(), &sel.list);
482 Ok(node.map(|id| self.get(id)))
483 }
484
485 fn select_first(&self, css: &str) -> Result<Option<NodeRef<'_>>, SelectorError> {
486 let list = parse_selector_cached(css)?;
487 let node = select_first_list(self.arena(), self.root_id(), &list);
488 Ok(node.map(|id| self.get(id)))
489 }
490
491 fn find_by_tag(&self, tag: Tag) -> Selection<'_> {
492 let arena = self.arena();
493 let mut nodes = Vec::new();
494 for i in 0..arena.len() {
495 let id = NodeId(i as u32);
496 let n = arena.get(id);
497 if n.tag == tag && is_document_element(n) {
498 nodes.push(id);
499 }
500 }
501 Selection::new(self, nodes)
502 }
503
504 fn find_by_id(&self, id: &str) -> Option<NodeRef<'_>> {
505 let arena = self.arena();
506 for i in 0..arena.len() {
507 let node_id = NodeId(i as u32);
508 let attrs = arena.attrs(node_id);
509 for attr in attrs {
510 if arena.attr_name(attr).eq_ignore_ascii_case("id")
511 && arena.attr_value(attr) == Some(id)
512 {
513 return Some(self.get(node_id));
514 }
515 }
516 }
517 None
518 }
519
520 fn find_by_class(&self, class: &str) -> Selection<'_> {
521 let arena = self.arena();
522 let mut nodes = Vec::new();
523 for i in 0..arena.len() {
524 let id = NodeId(i as u32);
525 let attrs = arena.attrs(id);
526 for attr in attrs {
527 if arena.attr_name(attr).eq_ignore_ascii_case("class") {
528 if let Some(val) = arena.attr_value(attr) {
529 if val.split_whitespace().any(|c| c == class) {
530 nodes.push(id);
531 break;
532 }
533 }
534 }
535 }
536 }
537 Selection::new(self, nodes)
538 }
539
540 fn find_by_attr(&self, name: &str, value: &str) -> Selection<'_> {
541 let arena = self.arena();
542 let mut nodes = Vec::new();
543 for i in 0..arena.len() {
544 let id = NodeId(i as u32);
545 let attrs = arena.attrs(id);
546 for attr in attrs {
547 if arena.attr_name(attr).eq_ignore_ascii_case(name)
548 && arena.attr_value(attr) == Some(value)
549 {
550 nodes.push(id);
551 break;
552 }
553 }
554 }
555 Selection::new(self, nodes)
556 }
557
558 fn xpath(&self, expr: &str) -> Result<XPathResult, XPathError> {
559 let parsed = xpath::parser::parse_xpath(expr)?;
560 Ok(xpath::eval::evaluate(&parsed, self.arena(), self.root_id()))
561 }
562}
563
564pub struct DocumentIndex {
568 id_map: HashMap<String, NodeId>,
569 class_map: HashMap<String, Vec<NodeId>>,
570 tag_map: HashMap<Tag, Vec<NodeId>>,
571}
572
573impl DocumentIndex {
574 pub fn build(doc: &Document) -> Self {
579 let arena = doc.arena();
580 let mut id_map = HashMap::with_capacity(arena.len() / 8);
581 let mut class_map: HashMap<String, Vec<NodeId>> = HashMap::with_capacity(arena.len() / 4);
582
583 let tag_map = if let Some(pre_built) = arena.tag_index() {
585 let mut map = HashMap::with_capacity(64);
586 for (tag_u8, ids) in pre_built.iter().enumerate() {
587 let filtered_ids: Vec<_> = ids
588 .iter()
589 .copied()
590 .filter(|&id| is_document_element(arena.get(id)))
591 .collect();
592 if !filtered_ids.is_empty() {
593 let tag: Tag = unsafe { std::mem::transmute(tag_u8 as u8) };
597 map.insert(tag, filtered_ids);
598 }
599 }
600 map
601 } else {
602 let mut map: HashMap<Tag, Vec<NodeId>> = HashMap::with_capacity(64);
603 for i in 0..arena.len() {
604 let node_id = NodeId(i as u32);
605 let n = arena.get(node_id);
606 if !is_document_element(n) {
607 continue;
608 }
609 map.entry(n.tag).or_default().push(node_id);
610 }
611 map
612 };
613
614 for i in 0..arena.len() {
616 let node_id = NodeId(i as u32);
617 let n = arena.get(node_id);
618
619 if !is_document_element(n) {
621 continue;
622 }
623
624 let attrs = arena.attrs(node_id);
625 for attr in attrs {
626 let attr_name = arena.attr_name(attr);
627 if attr_name.eq_ignore_ascii_case("id") {
628 if let Some(val) = arena.attr_value(attr) {
629 if let Some(existing) = id_map.get_mut(val) {
630 *existing = node_id;
631 } else {
632 id_map.insert(val.to_owned(), node_id);
633 }
634 }
635 }
636 if attr_name.eq_ignore_ascii_case("class") {
637 if let Some(val) = arena.attr_value(attr) {
638 for class in val.split_whitespace() {
639 if let Some(ids) = class_map.get_mut(class) {
640 ids.push(node_id);
641 } else {
642 class_map.insert(class.to_owned(), vec![node_id]);
643 }
644 }
645 }
646 }
647 }
648 }
649
650 Self {
651 id_map,
652 class_map,
653 tag_map,
654 }
655 }
656
657 pub fn find_by_id<'a>(&self, doc: &'a Document, id: &str) -> Option<NodeRef<'a>> {
659 self.id_map.get(id).map(|&node_id| doc.get(node_id))
660 }
661
662 pub fn find_by_class<'a>(&self, doc: &'a Document, class: &str) -> Vec<NodeRef<'a>> {
664 self.class_map
665 .get(class)
666 .map(|ids| ids.iter().map(|&id| doc.get(id)).collect())
667 .unwrap_or_default()
668 }
669
670 pub fn find_by_tag<'a>(&self, doc: &'a Document, tag: Tag) -> Vec<NodeRef<'a>> {
672 self.tag_map
673 .get(&tag)
674 .map(|ids| ids.iter().map(|&id| doc.get(id)).collect())
675 .unwrap_or_default()
676 }
677}
678
679#[cfg(test)]
680mod tests {
681 use super::*;
682 use fhp_tree::parse;
683
684 #[test]
685 fn select_basic() {
686 let doc = parse("<div><p>Hello</p></div>").unwrap();
687 let sel = doc.select("p").unwrap();
688 assert_eq!(sel.len(), 1);
689 assert_eq!(sel.text(), "Hello");
690 }
691
692 #[test]
693 fn select_first_found() {
694 let doc = parse("<div><p>a</p><p>b</p></div>").unwrap();
695 let first = doc.select_first("p").unwrap();
696 assert!(first.is_some());
697 assert_eq!(first.unwrap().text_content(), "a");
698 }
699
700 #[test]
701 fn select_chaining() {
702 let doc = parse("<ul><li><a>1</a></li><li><a>2</a></li></ul>").unwrap();
703 let lis = doc.select("li").unwrap();
704 assert_eq!(lis.len(), 2);
705 let links = lis.select("a").unwrap();
706 assert_eq!(links.len(), 2);
707 assert_eq!(links.text(), "12");
708 }
709
710 #[test]
711 fn find_by_tag_works() {
712 let doc = parse("<div><span>a</span><span>b</span></div>").unwrap();
713 let sel = doc.find_by_tag(Tag::Span);
714 assert_eq!(sel.len(), 2);
715 }
716
717 #[test]
718 fn find_by_id_works() {
719 let doc = parse("<div id=\"main\">x</div><div>y</div>").unwrap();
720 let node = doc.find_by_id("main");
721 assert!(node.is_some());
722 assert_eq!(node.unwrap().text_content(), "x");
723 }
724
725 #[test]
726 fn find_by_id_missing() {
727 let doc = parse("<div>x</div>").unwrap();
728 assert!(doc.find_by_id("nope").is_none());
729 }
730
731 #[test]
732 fn find_by_class_works() {
733 let doc = parse("<div class=\"a b\">x</div><div class=\"c\">y</div>").unwrap();
734 let sel = doc.find_by_class("a");
735 assert_eq!(sel.len(), 1);
736 assert_eq!(sel.text(), "x");
737 }
738
739 #[test]
740 fn find_by_attr_works() {
741 let doc = parse("<a href=\"x\">a</a><a href=\"y\">b</a>").unwrap();
742 let sel = doc.find_by_attr("href", "x");
743 assert_eq!(sel.len(), 1);
744 assert_eq!(sel.text(), "a");
745 }
746
747 #[test]
748 fn selection_attr() {
749 let doc = parse("<a href=\"url\">link</a>").unwrap();
750 let sel = doc.select("a").unwrap();
751 assert_eq!(sel.attr("href"), Some("url"));
752 }
753
754 #[test]
755 fn selection_inner_html() {
756 let doc = parse("<div><p>Hello</p></div>").unwrap();
757 let sel = doc.select("div").unwrap();
758 assert_eq!(sel.inner_html(), "<p>Hello</p>");
759 }
760
761 #[test]
762 fn selection_empty() {
763 let doc = parse("<div>x</div>").unwrap();
764 let sel = doc.select("span").unwrap();
765 assert!(sel.is_empty());
766 assert_eq!(sel.len(), 0);
767 assert!(sel.first().is_none());
768 }
769
770 #[test]
771 fn document_index_o1() {
772 let doc = parse("<div id=\"a\">x</div><div id=\"b\">y</div>").unwrap();
773 let index = DocumentIndex::build(&doc);
774 let node = index.find_by_id(&doc, "b").unwrap();
775 assert_eq!(node.text_content(), "y");
776 }
777
778 #[test]
779 fn document_index_find_by_class() {
780 let doc = parse("<div class=\"a b\">x</div><span class=\"b c\">y</span><p>z</p>").unwrap();
781 let index = DocumentIndex::build(&doc);
782
783 let class_b = index.find_by_class(&doc, "b");
784 assert_eq!(class_b.len(), 2);
785
786 let class_a = index.find_by_class(&doc, "a");
787 assert_eq!(class_a.len(), 1);
788 assert_eq!(class_a[0].text_content(), "x");
789
790 let class_missing = index.find_by_class(&doc, "nope");
791 assert!(class_missing.is_empty());
792 }
793
794 #[test]
795 fn document_index_find_by_tag() {
796 let doc = parse("<div>a</div><div>b</div><span>c</span>").unwrap();
797 let index = DocumentIndex::build(&doc);
798
799 let divs = index.find_by_tag(&doc, Tag::Div);
800 assert_eq!(divs.len(), 2);
801
802 let spans = index.find_by_tag(&doc, Tag::Span);
803 assert_eq!(spans.len(), 1);
804 assert_eq!(spans[0].text_content(), "c");
805
806 let links = index.find_by_tag(&doc, Tag::A);
807 assert!(links.is_empty());
808 }
809
810 #[test]
811 fn document_index_handles_uppercase_html_attributes() {
812 let doc = parse("<div ID=\"main\" CLASS=\"active hero\">x</div>").unwrap();
813 let index = DocumentIndex::build(&doc);
814
815 assert_eq!(index.find_by_id(&doc, "main").unwrap().text_content(), "x");
816 assert_eq!(index.find_by_class(&doc, "active").len(), 1);
817 assert_eq!(index.find_by_class(&doc, "hero").len(), 1);
818 }
819
820 #[test]
821 fn selection_into_iter() {
822 let doc = parse("<div><p>a</p><p>b</p></div>").unwrap();
823 let sel = doc.select("p").unwrap();
824 let texts: Vec<String> = (&sel).into_iter().map(|n| n.text_content()).collect();
825 assert_eq!(texts, vec!["a", "b"]);
826 }
827
828 #[test]
829 fn xpath_descendant() {
830 let doc = parse("<div><p>Hello</p></div>").unwrap();
831 let result = doc.xpath("//p").unwrap();
832 match result {
833 XPathResult::Nodes(nodes) => assert_eq!(nodes.len(), 1),
834 _ => panic!("expected Nodes"),
835 }
836 }
837
838 #[test]
839 fn xpath_text_extract() {
840 let doc = parse("<div><p>Hello</p></div>").unwrap();
841 let result = doc.xpath("//p/text()").unwrap();
842 match result {
843 XPathResult::Strings(texts) => {
844 assert_eq!(texts.len(), 1);
845 assert_eq!(texts[0], "Hello");
846 }
847 _ => panic!("expected Strings"),
848 }
849 }
850
851 #[test]
852 fn xpath_invalid() {
853 let doc = parse("<div>x</div>").unwrap();
854 assert!(doc.xpath("").is_err());
855 assert!(doc.xpath("bad").is_err());
856 }
857
858 #[test]
859 fn selection_xpath_chaining() {
860 let doc = parse("<ul><li>1</li><li>2</li></ul><ol><li>3</li></ol>").unwrap();
861 let sel = doc.select("ul").unwrap();
862 let result = sel.xpath("//li").unwrap();
863 match result {
864 XPathResult::Nodes(nodes) => assert_eq!(nodes.len(), 2),
865 _ => panic!("expected Nodes"),
866 }
867 }
868
869 #[test]
870 fn compiled_selector_basic() {
871 let sel = CompiledSelector::new("p").unwrap();
872 let doc = parse("<div><p>Hello</p></div>").unwrap();
873 let results = doc.select_compiled(&sel).unwrap();
874 assert_eq!(results.len(), 1);
875 assert_eq!(results.text(), "Hello");
876 }
877
878 #[test]
879 fn compiled_selector_first() {
880 let sel = CompiledSelector::new("p").unwrap();
881 let doc = parse("<div><p>a</p><p>b</p></div>").unwrap();
882 let first = doc.select_first_compiled(&sel).unwrap();
883 assert!(first.is_some());
884 assert_eq!(first.unwrap().text_content(), "a");
885 }
886
887 #[test]
888 fn compiled_selector_reuse_across_docs() {
889 let sel = CompiledSelector::new("span.active").unwrap();
890 let doc1 = parse("<span class=\"active\">one</span>").unwrap();
891 let doc2 = parse("<div><span class=\"active\">two</span></div>").unwrap();
892 assert_eq!(doc1.select_compiled(&sel).unwrap().text(), "one");
893 assert_eq!(doc2.select_compiled(&sel).unwrap().text(), "two");
894 }
895
896 #[test]
897 fn compiled_selector_chaining() {
898 let sel = CompiledSelector::new("a").unwrap();
899 let doc = parse("<ul><li><a>1</a></li><li><a>2</a></li></ul>").unwrap();
900 let lis = doc.select("li").unwrap();
901 let links = lis.select_compiled(&sel).unwrap();
902 assert_eq!(links.len(), 2);
903 }
904
905 #[test]
906 fn compiled_selector_invalid() {
907 assert!(CompiledSelector::new("").is_err());
908 }
909
910 #[test]
911 fn compiled_selector_clone() {
912 let sel = CompiledSelector::new("div").unwrap();
913 let sel2 = sel.clone();
914 let doc = parse("<div>ok</div>").unwrap();
915 assert_eq!(doc.select_compiled(&sel2).unwrap().len(), 1);
916 }
917}