Skip to main content

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