pub struct NodeRef<'a> { /* private fields */ }Expand description
A lightweight, Copy read handle to a single node, bound to its Dom.
All traversal (parent, children, descendants, ancestors, siblings)
is O(1) per step thanks to the arena’s links.
Implementations§
Source§impl NodeRef<'_>
impl NodeRef<'_>
Sourcepub fn table_rows(&self) -> Vec<Vec<String>>
pub fn table_rows(&self) -> Vec<Vec<String>>
The rows of a <table> as cell text, with colspan and rowspan
expanded — ready for CSV (to_csv), a data frame or an LLM prompt.
Rows come from the table’s <thead>, <tbody>, <tfoot> and direct
<tr>s in document order (stray cells outside any row form a row of
their own). Each cell is its readable text (NodeRef::to_text)
without the content of tables nested in it — such a table reads as
a paragraph break — since a nested table has rows of its own (call
table_rows on it). This keeps extracting every table of a page, as
domtree --csv does, linear in the page’s size. Returns an empty list
if this node is not a <table>.
A cell spanning several columns or rows is repeated in every slot
it covers, so each row lists the value under each column. Span values
follow HTML’s rules for non-negative integers (" 2", "+2" and
"2px" are 2; an invalid or missing value is 1). rowspan=0 spans the
rest of the row group; spans stop at the end of their row group, as in
HTML. Rows are not padded to equal length, except where a rowspan from
an earlier row reaches past a row’s own cells.
Bounded work on hostile input: spans are clamped at 1000, and once the spans of a table have added about four times the table’s own cells and text (plus a fixed allowance of 100,000 cells and 4 MiB), further spans count as 1.
let dom = domtree::parse(
"<table>
<thead><tr><th>Region</th><th>Q1</th><th>Q2</th></tr></thead>
<tbody>
<tr><td rowspan=2>EU</td><td>10</td><td>12</td></tr>
<tr><td colspan=2>n/a</td></tr>
</tbody>
</table>",
);
let table = dom.find_by_tag("table").next().unwrap();
assert_eq!(table.table_rows(), [
["Region", "Q1", "Q2"],
["EU", "10", "12"],
["EU", "n/a", "n/a"],
]);Source§impl<'a> NodeRef<'a>
impl<'a> NodeRef<'a>
Sourcepub fn source_range(&self) -> Option<Range<usize>>
pub fn source_range(&self) -> Option<Range<usize>>
The byte range this node was parsed from, in the original input
string (pair it with crate::line_col for line/column positions).
- Text, comments, doctypes, CDATA: exactly their markup, raw —
&in the source spans 5 bytes even though the text is&. - Elements: from the
<of the start tag to the>of the end tag. Void and self-closing elements span just their start tag. An element with no end tag of its own ends where recovery closed it: at the start of the tag that closed it (a start tag like the next<li>, or a mismatched end tag like</ul>), or at the end of the input. So a parent’s range always contains its children’s. Nonefor nodes created by editing (Dom::create_element,Dom::set_texton an element, …), including those inserted withDom::append_htmland friends, whose source is a different string.
Edits never move a range: a renamed element or rewritten text node still points at where it came from.
let src = "<ul>\n <li>Tom & Jerry\n <li>Spike</ul>";
let dom = domtree::parse(src);
let first = dom.find_by_tag("li").next().unwrap();
// Closed implicitly by the second `<li>`:
assert_eq!(&src[first.source_range().unwrap()], "<li>Tom & Jerry\n ");
let text = first.first_child().unwrap();
assert_eq!(&src[text.source_range().unwrap()], "Tom & Jerry\n ");
assert_eq!(&src[dom.root().unwrap().source_range().unwrap()], src);Sourcepub fn is_detached(&self) -> bool
pub fn is_detached(&self) -> bool
Whether this node is not part of the document: it was removed (or
sits inside a removed subtree), or was created by editing and not yet
attached. Detached nodes are skipped by Dom::nodes, selectors and
serializers of the whole document. The inverse of the DOM’s
isConnected.
let mut dom = domtree::parse("<p>a <b>b</b></p>");
let p = dom.root().unwrap().id();
let b = dom.find_by_tag("b").next().unwrap().id();
dom.remove(b);
assert!(dom.get(b).unwrap().is_detached());
dom.append_child(p, b); // re-attached
assert!(!dom.get(b).unwrap().is_detached());Sourcepub fn kind(&self) -> NodeKind<'a>
pub fn kind(&self) -> NodeKind<'a>
The node’s kind (element / text / comment / doctype) as a borrowed view.
Sourcepub fn is_element(&self) -> bool
pub fn is_element(&self) -> bool
Whether this node is an element.
Sourcepub fn tag_name(&self) -> Option<&'a str>
pub fn tag_name(&self) -> Option<&'a str>
The tag name (lower-cased), if this node is an element.
Sourcepub fn attr(&self, name: &str) -> Option<&'a str>
pub fn attr(&self, name: &str) -> Option<&'a str>
The value of attribute name, if this is an element with that
attribute. With duplicate attributes, the first one wins.
Sourcepub fn has_attr(&self, name: &str) -> bool
pub fn has_attr(&self, name: &str) -> bool
Whether attribute name is present (including valueless boolean attrs).
Sourcepub fn attributes(&self) -> impl Iterator<Item = (&'a str, &'a str)>
pub fn attributes(&self) -> impl Iterator<Item = (&'a str, &'a str)>
All (name, value) attribute pairs in source order, duplicates
included (empty iterator for non-elements).
Sourcepub fn classes(&self) -> impl Iterator<Item = &'a str>
pub fn classes(&self) -> impl Iterator<Item = &'a str>
The class names on this element. Like HTML, splits on ASCII whitespace only, so a non-breaking space stays part of a class name.
Sourcepub fn text(&self) -> Option<&'a str>
pub fn text(&self) -> Option<&'a str>
This node’s own text, if it is a text node (already entity-decoded).
Sourcepub fn text_content(&self) -> String
pub fn text_content(&self) -> String
Concatenated text of this node and all its descendants, in document
order (entity-decoded). Equivalent to the DOM textContent.
Sourcepub fn first_child(&self) -> Option<NodeRef<'a>>
pub fn first_child(&self) -> Option<NodeRef<'a>>
The first child, if any.
Sourcepub fn last_child(&self) -> Option<NodeRef<'a>>
pub fn last_child(&self) -> Option<NodeRef<'a>>
The last child, if any.
Sourcepub fn next_sibling(&self) -> Option<NodeRef<'a>>
pub fn next_sibling(&self) -> Option<NodeRef<'a>>
The next sibling, if any.
Sourcepub fn prev_sibling(&self) -> Option<NodeRef<'a>>
pub fn prev_sibling(&self) -> Option<NodeRef<'a>>
The previous sibling, if any.
Sourcepub fn next_element_sibling(&self) -> Option<NodeRef<'a>>
pub fn next_element_sibling(&self) -> Option<NodeRef<'a>>
The next sibling that is an element (skips text, comments, doctypes).
let dom = domtree::parse("<p><a>1</a> text <!-- c --><b>2</b></p>");
let a = dom.find_by_tag("a").next().unwrap();
assert_eq!(a.next_element_sibling().unwrap().tag_name(), Some("b"));
assert!(a.prev_element_sibling().is_none());Sourcepub fn prev_element_sibling(&self) -> Option<NodeRef<'a>>
pub fn prev_element_sibling(&self) -> Option<NodeRef<'a>>
The previous sibling that is an element (skips text, comments, doctypes).
let dom = domtree::parse("<ul><li>a</li> <li>b</li></ul>");
let b = dom.find_by_tag("li").nth(1).unwrap();
assert_eq!(b.prev_element_sibling().unwrap().text_content(), "a");Sourcepub fn children(&self) -> impl Iterator<Item = NodeRef<'a>>
pub fn children(&self) -> impl Iterator<Item = NodeRef<'a>>
This node’s direct children, in order.
Sourcepub fn child_elements(&self) -> impl Iterator<Item = NodeRef<'a>>
pub fn child_elements(&self) -> impl Iterator<Item = NodeRef<'a>>
Child elements only (skips text/comment/doctype nodes).
Sourcepub fn ancestors(&self) -> impl Iterator<Item = NodeRef<'a>>
pub fn ancestors(&self) -> impl Iterator<Item = NodeRef<'a>>
This node’s ancestors, from parent up to the root.
Sourcepub fn descendants(&self) -> Descendants<'a> ⓘ
pub fn descendants(&self) -> Descendants<'a> ⓘ
All descendants of this node, in document (pre-order) order. Does not include the node itself. Allocation-free.
Source§impl<'a> NodeRef<'a>
impl<'a> NodeRef<'a>
Sourcepub fn to_html(&self) -> String
pub fn to_html(&self) -> String
Serialize just this node and its subtree back to an HTML string.
let dom = domtree::parse("<div><img src=a.png><br></div>");
let div = dom.find_by_tag("div").next().unwrap();
assert_eq!(div.to_html(), r#"<div><img src="a.png"><br></div>"#);Sourcepub fn inner_html(&self) -> String
pub fn inner_html(&self) -> String
The HTML of this node’s children, without the node’s own tags (the DOM
innerHTML). Empty for text, comment and void nodes.
For a <script>, <style>, <textarea> or <title> it is exactly
the content NodeRef::to_html writes, with any end tag of the
element itself written <\/… (see Dom::set_text).
let dom = domtree::parse("<div id=x><b>hi</b> there</div>");
let div = dom.root().unwrap();
assert_eq!(div.inner_html(), "<b>hi</b> there");Sourcepub fn outer_html(&self) -> String
pub fn outer_html(&self) -> String
This node and its subtree as HTML (the DOM outerHTML); the same as
NodeRef::to_html.
let dom = domtree::parse("<div id=x><b>hi</b></div>");
let b = dom.find_by_tag("b").next().unwrap();
assert_eq!(b.outer_html(), "<b>hi</b>");Source§impl NodeRef<'_>
impl NodeRef<'_>
Sourcepub fn to_text(&self) -> String
pub fn to_text(&self) -> String
This node’s subtree as readable plain text; see Dom::to_text.
let dom = domtree::parse("<ul><li>One</li><li>Two <i>too</i></li></ul>");
assert_eq!(dom.root().unwrap().to_text(), "One\nTwo too");Source§impl NodeRef<'_>
impl NodeRef<'_>
Sourcepub fn to_markdown(&self) -> String
Available on crate feature markdown only.
pub fn to_markdown(&self) -> String
markdown only.This node’s subtree as Markdown; see Dom::to_markdown.
let dom = domtree::parse(
"<div><pre><code class='language-rust'>fn main() {}\n</code></pre></div>",
);
let pre = dom.find_by_tag("pre").next().unwrap();
assert_eq!(pre.to_markdown(), "```rust\nfn main() {}\n```");Source§impl NodeRef<'_>
impl NodeRef<'_>
Sourcepub fn to_markdown_chunks(&self, max_chars: usize) -> Vec<Chunk>
Available on crate feature markdown only.
pub fn to_markdown_chunks(&self, max_chars: usize) -> Vec<Chunk>
markdown only.This node’s subtree as Markdown chunks; see Dom::to_markdown_chunks.
let dom = domtree::parse(
"<nav>Home</nav><main><h2>Notes</h2><p>One.</p><p>Two.</p></main>",
);
let main = dom.find_by_tag("main").next().unwrap();
let chunks = main.to_markdown_chunks(14);
assert_eq!(chunks.len(), 2);
assert_eq!(chunks[0].markdown, "## Notes\n\nOne.");
assert_eq!(chunks[1].markdown, "Two.");
assert_eq!(chunks[1].heading_path, ["Notes"]);Source§impl<'a> NodeRef<'a>
impl<'a> NodeRef<'a>
Sourcepub fn to_json(&self) -> String
Available on crate feature json only.
pub fn to_json(&self) -> String
json only.Serialize just this node and its subtree to a compact JSON string,
using the same shape as Dom::to_json.
let dom = domtree::parse("<div><b>hi</b></div>");
let b = dom.find_by_tag("b").next().unwrap();
assert_eq!(b.to_json(), r#"{"type":"element","tag":"b","attrs":{},"children":[{"type":"text","value":"hi"}]}"#);Source§impl<'a> NodeRef<'a>
impl<'a> NodeRef<'a>
Sourcepub fn select<'s, S: AsSelector + ?Sized>(
&self,
selector: &'s S,
) -> impl Iterator<Item = NodeRef<'a>> + use<'a, 's, S>
Available on crate feature query only.
pub fn select<'s, S: AsSelector + ?Sized>( &self, selector: &'s S, ) -> impl Iterator<Item = NodeRef<'a>> + use<'a, 's, S>
query only.Descendants of this node (not the node itself) matching selector, in
document order.
Like the DOM’s querySelectorAll, the selector is matched against the
whole document — only the results are limited to this subtree. So
"div p" also finds a <p> whose <div> is an ancestor of this node.
Use :scope to anchor at this node: ":scope > li" is its <li>
children.
let dom = domtree::parse("<div><ul><li>a<ul><li>b</li></ul></li></ul></div>");
let ul = dom.select_first("ul").unwrap();
assert_eq!(ul.select("li").count(), 2);
assert_eq!(ul.select(":scope > li").count(), 1);
assert_eq!(ul.select("div li").count(), 2); // the <div> is outside `ul`Sourcepub fn select_first<S: AsSelector + ?Sized>(
&self,
selector: &S,
) -> Option<NodeRef<'a>>
Available on crate feature query only.
pub fn select_first<S: AsSelector + ?Sized>( &self, selector: &S, ) -> Option<NodeRef<'a>>
query only.The first descendant (in document order) matching selector; see
NodeRef::select.
let dom = domtree::parse("<table><tr><td>1</td><td>2</td></tr></table>");
let row = dom.select_first("tr").unwrap();
assert_eq!(row.select_first("td:last-child").unwrap().text_content(), "2");Sourcepub fn matches<S: AsSelector + ?Sized>(&self, selector: &S) -> bool
Available on crate feature query only.
pub fn matches<S: AsSelector + ?Sized>(&self, selector: &S) -> bool
query only.Whether this node is an element matching selector (DOM
Element.matches). An invalid selector matches nothing.
let dom = domtree::parse("<ul><li class='a'>x</li></ul>");
let li = dom.select_first("li").unwrap();
assert!(li.matches("ul > li.a"));
assert!(!li.matches("ol li"));Sourcepub fn closest<S: AsSelector + ?Sized>(
&self,
selector: &S,
) -> Option<NodeRef<'a>>
Available on crate feature query only.
pub fn closest<S: AsSelector + ?Sized>( &self, selector: &S, ) -> Option<NodeRef<'a>>
query only.The nearest element matching selector, starting with this node itself
and walking up its ancestors (DOM Element.closest).
let dom = domtree::parse("<div class='card'><p><b>hi</b></p></div>");
let b = dom.select_first("b").unwrap();
assert_eq!(b.closest(".card").unwrap().tag_name(), Some("div"));
assert_eq!(b.closest("b").unwrap().id(), b.id()); // self-inclusive
assert!(b.closest("table").is_none());