Skip to content

Commit

Permalink
Merge pull request #17 from niklak/feature/selector-only-text
Browse files Browse the repository at this point in the history
added `:only-text` pseudo-class support
  • Loading branch information
niklak authored Nov 1, 2024
2 parents 478c8ba + 56fe743 commit 50566cf
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ All notable changes to the `dom_query` crate will be documented in this file.
- Added `NodeRef::replace_with_html` method that allows to replace a node with a new node created from the given HTML.
- Added `NodeIdProver` trait and implementations for `NodeRef` and `Node`. Which allows to call some node functions with a `&NodeRef` and `&NodeId`.
Previously these functions required `NodeId` as a parameter.
- Added a new pseudo-class `:only-text` that allows selecting a node with no child elements except a single **text** child node.

### Fixed
- Fixed `Tree::append_prev_siblings_from_another_tree` method. It didn't assign `TreeNode.prev_sibling` properly.
Expand Down
6 changes: 6 additions & 0 deletions src/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ impl<'i> parser::Parser<'i> for InnerSelectorParser {
Ok(Checked)
} else if name.eq_ignore_ascii_case("indeterminate") {
Ok(Indeterminate)

} else if name.eq_ignore_ascii_case("only-text") {
Ok(OnlyText)
} else {
Err(
location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(
Expand Down Expand Up @@ -239,6 +242,8 @@ pub enum NonTSPseudoClass {
Disabled,
Checked,
Indeterminate,
/// `:only-text` pseudo-class allows selecting a node with no child elements except a single **text** child node.
OnlyText,
/// `:has-text` pseudo-class represents a selection for the element or one of its descendant element that contains the specified text.
HasText(CssString),
/// `:contains` pseudo-class represents a selection for the element that contains the specified text (it's own text and text of all his descendant elements).
Expand All @@ -261,6 +266,7 @@ impl ToCss for NonTSPseudoClass {
NonTSPseudoClass::Disabled => dest.write_str(":disabled"),
NonTSPseudoClass::Checked => dest.write_str(":checked"),
NonTSPseudoClass::Indeterminate => dest.write_str(":indeterminate"),
NonTSPseudoClass::OnlyText => dest.write_str(":only-text"),
NonTSPseudoClass::HasText(s) => {
dest.write_str(":has-text(")?;
s.to_css(dest)?;
Expand Down
8 changes: 8 additions & 0 deletions src/node/node_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,4 +501,12 @@ impl<'a> NodeRef<'a> {
}
false
}

pub fn has_only_text(&self) -> bool {
if self.children_it().count() == 1 {
self.first_child().unwrap().is_text()
}else {
false
}
}
}
2 changes: 1 addition & 1 deletion src/node/selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ impl<'a> selectors::Element for NodeRef<'a> {
}
None => false,
},

OnlyText => self.has_only_text(),
HasText(s) => self.has_text(s.as_str()),
Contains(s) => self.text().contains(s.as_str()),
}
Expand Down
27 changes: 27 additions & 0 deletions tests/node-manipulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,30 @@ fn test_node_replace_with_reparent() {
// #inline is a child of #outline now
assert!(doc.select("#outline > #inline").exists());
}


#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_node_replace_text_node() {

let content = r#"<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<div id="main">
<p><a href="javascript:void(0)">Some text</a></p>
</div>
</body>
</html>"#;
let doc = Document::from(content);
// :only-text pseudo-class allows to select nodes that contain only one text node
let a_sel = doc.select_single(r#"a[href^="javascript:"]:only-text"#);
assert!(a_sel.exists());
let a_node = a_sel.nodes().first().unwrap();
let text_node =a_node.first_child().unwrap();
assert!(text_node.is_text());
a_node.replace_with(&text_node);

assert_eq!(doc.select("#main > p").inner_html(), "Some text".into());

}
27 changes: 27 additions & 0 deletions tests/pseudo-classes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,33 @@ fn pseudo_class_contains() {
assert_eq!(text, "It is not how it works");
}


#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn pseudo_class_only_text() {
//! :only-text pseudo-class allows selecting a node with no child elements except a single **text** child node.
let html = r#"<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
<div>
<a href="/1">One</a>
<a href="/2">Two</a>
<a href="/3">Three</a>
</div>
<div>Only text</div>
</body>
</html>
"#;
let document = Document::from(html);
let sel = document.select("body div:only-text");
assert_eq!(sel.inner_html(), "Only text".into());

}

#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn pseudo_class_not() {
Expand Down

0 comments on commit 50566cf

Please sign in to comment.