Skip to main content

NodeRef

Struct NodeRef 

Source
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<'_>

Source

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>

Source

pub fn id(&self) -> NodeId

This node’s arena id.

Source

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 — &amp; 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.
  • None for nodes created by editing (Dom::create_element, Dom::set_text on an element, …), including those inserted with Dom::append_html and 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 &amp; 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 &amp; Jerry\n  ");
let text = first.first_child().unwrap();
assert_eq!(&src[text.source_range().unwrap()], "Tom &amp; Jerry\n  ");
assert_eq!(&src[dom.root().unwrap().source_range().unwrap()], src);
Source

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());
Source

pub fn kind(&self) -> NodeKind<'a>

The node’s kind (element / text / comment / doctype) as a borrowed view.

Source

pub fn is_element(&self) -> bool

Whether this node is an element.

Source

pub fn is_text(&self) -> bool

Whether this node is a text node.

Source

pub fn tag_name(&self) -> Option<&'a str>

The tag name (lower-cased), if this node is an element.

Source

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.

Source

pub fn has_attr(&self, name: &str) -> bool

Whether attribute name is present (including valueless boolean attrs).

Source

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).

Source

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.

Source

pub fn has_class(&self, class: &str) -> bool

Whether this element has the given class.

Source

pub fn text(&self) -> Option<&'a str>

This node’s own text, if it is a text node (already entity-decoded).

Source

pub fn comment(&self) -> Option<&'a str>

The body text of this comment node, if it is one.

Source

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.

Source

pub fn parent(&self) -> Option<NodeRef<'a>>

This node’s parent, if any.

Source

pub fn first_child(&self) -> Option<NodeRef<'a>>

The first child, if any.

Source

pub fn last_child(&self) -> Option<NodeRef<'a>>

The last child, if any.

Source

pub fn next_sibling(&self) -> Option<NodeRef<'a>>

The next sibling, if any.

Source

pub fn prev_sibling(&self) -> Option<NodeRef<'a>>

The previous sibling, if any.

Source

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());
Source

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");
Source

pub fn children(&self) -> impl Iterator<Item = NodeRef<'a>>

This node’s direct children, in order.

Source

pub fn child_elements(&self) -> impl Iterator<Item = NodeRef<'a>>

Child elements only (skips text/comment/doctype nodes).

Source

pub fn ancestors(&self) -> impl Iterator<Item = NodeRef<'a>>

This node’s ancestors, from parent up to the root.

Source

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>

Source

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>"#);
Source

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");
Source

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<'_>

Source

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<'_>

Source

pub fn to_markdown(&self) -> String

Available on crate feature 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<'_>

Source

pub fn to_markdown_chunks(&self, max_chars: usize) -> Vec<Chunk>

Available on crate feature 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>

Source

pub fn to_json(&self) -> String

Available on crate feature 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>

Source

pub 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.

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`
Source

pub fn select_first<S: AsSelector + ?Sized>( &self, selector: &S, ) -> Option<NodeRef<'a>>

Available on crate feature 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");
Source

pub fn matches<S: AsSelector + ?Sized>(&self, selector: &S) -> bool

Available on crate feature 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"));
Source

pub fn closest<S: AsSelector + ?Sized>( &self, selector: &S, ) -> Option<NodeRef<'a>>

Available on crate feature 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());

Trait Implementations§

Source§

impl<'a> Clone for NodeRef<'a>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<'a> Copy for NodeRef<'a>

Source§

impl<'a> Debug for NodeRef<'a>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'a> Serialize for NodeRef<'a>

Available on crate feature serde only.
Source§

fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error>

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

§

impl<'a> Freeze for NodeRef<'a>

§

impl<'a> RefUnwindSafe for NodeRef<'a>

§

impl<'a> Send for NodeRef<'a>

§

impl<'a> Sync for NodeRef<'a>

§

impl<'a> Unpin for NodeRef<'a>

§

impl<'a> UnsafeUnpin for NodeRef<'a>

§

impl<'a> UnwindSafe for NodeRef<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.