#![allow(clippy::result_unit_err)]
mod ancestors;
mod descendants;
mod element_iterator;
#[cfg(feature = "namespaces")]
mod elements_in_namespace;
mod filter_iterators;
mod node_edge;
mod node_iterator;
mod node_ref_impls;
mod select;
mod siblings;
mod traverse;
pub use ancestors::Ancestors;
pub use descendants::Descendants;
pub use element_iterator::ElementIterator;
#[cfg(feature = "namespaces")]
pub use elements_in_namespace::ElementsInNamespace;
pub use filter_iterators::{Comments, Elements, TextNodes};
pub use node_edge::NodeEdge;
pub use node_iterator::NodeIterator;
pub use select::Select;
pub use siblings::Siblings;
pub use traverse::Traverse;
#[cfg(test)]
mod tests {
use crate::parser::parse_html;
use crate::traits::*;
#[test]
#[cfg(feature = "namespaces")]
fn elements_in_ns_filters_by_namespace() {
let html = r#"<!DOCTYPE html>
<html>
<body>
<div>HTML element 1</div>
<svg xmlns="http://www.w3.org/2000/svg">
<circle r="10"/>
<rect width="20" height="20"/>
</svg>
<p>HTML element 2</p>
</body>
</html>"#;
let doc = parse_html().one(html);
let svg_elements: Vec<_> = doc
.descendants()
.elements()
.elements_in_ns(ns!(svg))
.collect();
assert_eq!(svg_elements.len(), 3); assert!(svg_elements.iter().all(|e| e.namespace_uri() == &ns!(svg)));
}
#[test]
#[cfg(feature = "namespaces")]
fn elements_in_ns_empty_when_no_match() {
let html = r#"<div><p>Only HTML elements</p></div>"#;
let doc = parse_html().one(html);
let svg_elements: Vec<_> = doc
.descendants()
.elements()
.elements_in_ns(ns!(svg))
.collect();
assert_eq!(svg_elements.len(), 0);
}
#[test]
#[cfg(feature = "namespaces")]
fn elements_in_ns_works_with_nested_elements() {
let html = r#"<!DOCTYPE html>
<html>
<body>
<svg xmlns="http://www.w3.org/2000/svg">
<g>
<circle r="10"/>
<circle r="20"/>
</g>
</svg>
</body>
</html>"#;
let doc = parse_html().one(html);
let svg_elements: Vec<_> = doc
.descendants()
.elements()
.elements_in_ns(ns!(svg))
.collect();
assert_eq!(svg_elements.len(), 4);
}
#[test]
#[cfg(feature = "namespaces")]
fn elements_in_ns_double_ended_iteration() {
let html = r#"<!DOCTYPE html>
<html>
<body>
<svg xmlns="http://www.w3.org/2000/svg">
<circle r="10"/>
<rect width="20" height="20"/>
<line x1="0" y1="0" x2="10" y2="10"/>
</svg>
</body>
</html>"#;
let doc = parse_html().one(html);
let mut svg_elements = doc.descendants().elements().elements_in_ns(ns!(svg));
let first = svg_elements.next().unwrap();
assert_eq!(first.local_name().as_ref(), "svg");
let last = svg_elements.next_back().unwrap();
assert_eq!(last.local_name().as_ref(), "line");
}
#[test]
fn detach_all_removes_elements() {
let html = r#"<div><p>One</p><p>Two</p><p>Three</p></div>"#;
let doc = parse_html().one(html);
let div = doc.select_first("div").unwrap();
let initial_count = div.as_node().children().elements().count();
assert_eq!(initial_count, 3);
let paragraphs: Vec<_> = div
.as_node()
.descendants()
.elements()
.filter(|e| e.local_name().as_ref() == "p")
.collect();
paragraphs
.into_iter()
.map(|p| p.as_node().clone())
.detach_all();
assert_eq!(div.as_node().children().elements().count(), 0);
}
#[test]
fn detach_all_with_empty_iterator() {
let html = r#"<div><p>Test</p></div>"#;
let doc = parse_html().one(html);
doc.descendants()
.elements()
.filter(|e| e.local_name().as_ref() == "nonexistent")
.map(|e| e.as_node().clone())
.detach_all();
}
#[test]
#[cfg(feature = "namespaces")]
fn detach_all_with_mixed_namespaces() {
let html = r#"<!DOCTYPE html>
<html>
<body>
<div>HTML</div>
<svg xmlns="http://www.w3.org/2000/svg">
<circle r="10"/>
</svg>
<p>More HTML</p>
</body>
</html>"#;
let doc = parse_html().one(html);
let body = doc.select_first("body").unwrap();
let svg_elements: Vec<_> = body
.as_node()
.descendants()
.elements()
.elements_in_ns(ns!(svg))
.collect();
svg_elements
.into_iter()
.map(|e| e.as_node().clone())
.detach_all();
let remaining_html: Vec<_> = body
.as_node()
.descendants()
.elements()
.elements_in_ns(ns!(html))
.collect();
assert_eq!(remaining_html.len(), 2);
let remaining_svg: Vec<_> = body
.as_node()
.descendants()
.elements()
.elements_in_ns(ns!(svg))
.collect();
assert_eq!(remaining_svg.len(), 0);
}
#[test]
fn text_nodes() {
let html = r"
<!doctype html>
<title>Test case</title>
<p>Content contains <b>Important</b> data</p>";
let document = parse_html().one(html);
let paragraph = document.select("p").unwrap().collect::<Vec<_>>();
assert_eq!(paragraph.len(), 1);
assert_eq!(
paragraph[0].text_contents(),
"Content contains Important data"
);
let texts = paragraph[0]
.as_node()
.descendants()
.text_nodes()
.collect::<Vec<_>>();
assert_eq!(texts.len(), 3);
assert_eq!(&*texts[0].borrow(), "Content contains ");
assert_eq!(&*texts[1].borrow(), "Important");
assert_eq!(&*texts[2].borrow(), " data");
{
let mut x = texts[0].borrow_mut();
x.truncate(0);
x.push_str("Content doesn't contain ");
}
assert_eq!(&*texts[0].borrow(), "Content doesn't contain ");
}
#[test]
fn elements_double_ended() {
let html = "<div><p>1</p><span>2</span><b>3</b><i>4</i></div>";
let doc = parse_html().one(html);
let div = doc.select_first("div").unwrap();
let mut elements = div.as_node().descendants().elements();
let first = elements.next().unwrap();
assert_eq!(first.name.local.as_ref(), "p");
let last = elements.next_back().unwrap();
assert_eq!(last.name.local.as_ref(), "i");
let second = elements.next().unwrap();
assert_eq!(second.name.local.as_ref(), "span");
let second_last = elements.next_back().unwrap();
assert_eq!(second_last.name.local.as_ref(), "b");
assert!(elements.next().is_none());
}
#[test]
fn comments_double_ended() {
let html = "<div><!-- first --><p>text</p><!-- second --><!-- third --></div>";
let doc = parse_html().one(html);
let div = doc.select_first("div").unwrap();
let mut comments = div.as_node().descendants().comments();
let first = comments.next().unwrap();
assert_eq!(&*first.borrow(), " first ");
let last = comments.next_back().unwrap();
assert_eq!(&*last.borrow(), " third ");
let middle = comments.next().unwrap();
assert_eq!(&*middle.borrow(), " second ");
assert!(comments.next().is_none());
}
#[test]
fn descendants_double_ended() {
let html = "<div><p>1</p><span>2</span><b>3</b></div>";
let doc = parse_html().one(html);
let div = doc.select_first("div").unwrap();
let mut descendants = div.as_node().descendants();
let first = descendants.next().unwrap();
assert_eq!(first.as_element().unwrap().name.local.as_ref(), "p");
let last = descendants.next_back().unwrap();
assert!(last.as_text().is_some());
let second = descendants.next().unwrap();
assert!(second.as_text().is_some()); }
#[test]
fn siblings_double_ended() {
let html = "<div><p>1</p><span>2</span><b>3</b><i>4</i></div>";
let doc = parse_html().one(html);
let div = doc.select_first("div").unwrap();
let mut siblings = div.as_node().children();
let first = siblings.next().unwrap();
assert_eq!(first.as_element().unwrap().name.local.as_ref(), "p");
let last = siblings.next_back().unwrap();
assert_eq!(last.as_element().unwrap().name.local.as_ref(), "i");
let second = siblings.next().unwrap();
assert_eq!(second.as_element().unwrap().name.local.as_ref(), "span");
let second_last = siblings.next_back().unwrap();
assert_eq!(second_last.as_element().unwrap().name.local.as_ref(), "b");
assert!(siblings.next().is_none());
}
#[test]
fn traverse_double_ended() {
let html = "<div><p>text</p></div>";
let doc = parse_html().one(html);
let div = doc.select_first("div").unwrap();
let mut traverse = div.as_node().traverse();
let first = traverse.next().unwrap();
if let crate::iter::NodeEdge::Start(node) = first {
assert_eq!(node.as_element().unwrap().name.local.as_ref(), "p");
} else {
panic!("Expected Start edge");
}
let last = traverse.next_back().unwrap();
assert!(matches!(last, crate::iter::NodeEdge::End(_)));
}
#[test]
fn node_edge_basics() {
use crate::iter::NodeEdge;
let html = "<div></div>";
let doc = parse_html().one(html);
let div = doc.select_first("div").unwrap();
let start = NodeEdge::Start(div.as_node().clone());
let end = NodeEdge::End(div.as_node().clone());
let start_clone = start.clone();
assert_eq!(start, start_clone);
assert_eq!(start, start_clone);
assert_ne!(start, end);
let debug_str = format!("{start:?}");
assert!(debug_str.contains("Start"));
}
}