node_html_parser/css_select/helpers/
selectors.rs

1use crate::css_select::types::{AttributeAction, AttributeSelector, InternalSelector, PseudoData};
2
3// Determine relative execution cost (lower = earlier) similar to js/css-select helpers/selectors.ts
4pub fn get_quality(token: &InternalSelector) -> i32 {
5	match token {
6		InternalSelector::Universal => 50,
7		InternalSelector::Tag { .. } => 30,
8		InternalSelector::Attribute(attr) => get_attribute_quality(attr),
9		InternalSelector::Pseudo { data, name } => match data {
10			PseudoData::None => 3,
11			PseudoData::SubSelectors(_) => {
12				if matches!(name.as_str(), "has" | "contains" | "icontains") { 0 } else { 2 }
13			}
14			PseudoData::Nth(_) => 3, // nth 自身判定成本中等,先给 3(可再调优)
15		}
16		_ => -1,
17	}
18}
19
20fn get_attribute_quality(attr: &AttributeSelector) -> i32 {
21	let base = match attr.action {
22		AttributeAction::Exists => 10,
23		AttributeAction::Equals => {
24			if attr.name == "id" {
25				9
26			} else {
27				8
28			}
29		}
30		AttributeAction::Not => 7,
31		AttributeAction::Start | AttributeAction::End => 6,
32		AttributeAction::Any => 5,
33		AttributeAction::Hyphen => 4,
34		AttributeAction::Element => 3,
35	};
36	if attr.ignore_case { base / 2 } else { base }
37}
38
39pub fn sort_rules(arr: &mut Vec<InternalSelector>) {
40	// insertion sort by quality ascending (negative qualities stay in place)
41	for i in 1..arr.len() {
42		let q_new = get_quality(&arr[i]);
43		if q_new < 0 {
44			continue;
45		}
46		let mut j = i;
47		while j > 0 {
48			let q_prev = get_quality(&arr[j - 1]);
49			if q_prev <= q_new {
50				break;
51			}
52			arr.swap(j, j - 1);
53			j -= 1;
54		}
55	}
56}
57
58pub fn is_traversal(token: &InternalSelector) -> bool {
59	matches!(
60		token,
61		InternalSelector::Descendant
62			| InternalSelector::Child
63			| InternalSelector::Sibling
64			| InternalSelector::Adjacent
65			| InternalSelector::FlexibleDescendant
66	)
67}
68
69pub fn includes_scope_pseudo(token: &InternalSelector) -> bool {
70	match token {
71		InternalSelector::Pseudo { name, data } => {
72			if name == "scope" { return true; }
73			match data {
74				PseudoData::SubSelectors(groups) => groups.iter().any(|g| g.iter().any(includes_scope_pseudo)),
75				_ => false,
76			}
77		}
78		_ => false,
79	}
80}