Skip to main content

Dom

Struct Dom 

Source
pub struct Dom { /* private fields */ }
Expand description

A parsed HTML document: an arena of nodes, a single string buffer they reference, the top-level (root) nodes, and any recovery diagnostics.

Build one with crate::parse and traverse it with NodeRef handles from Dom::root, Dom::roots, Dom::get, or the find_by_* helpers. The editing methods (Dom::remove, Dom::set_text, Dom::append_html, Dom::wrap, …) rewrite it in place.

Implementations§

Source§

impl Dom

Source

pub fn remove(&mut self, id: NodeId)

Remove a node (and its subtree) from the document.

The node stays valid — handles to it and its descendants still work and can still be serialized, and it can be re-inserted with Dom::append_child or Dom::insert_before — but it is no longer reachable from the document: Dom::nodes, find_by_*, select, and every serializer skip it. Removing an already-removed node is a no-op.

let mut dom = domtree::parse("<p>Hi <script>track()</script>there</p>");
let script = dom.find_by_tag("script").next().unwrap().id();
dom.remove(script);
assert_eq!(dom.to_html(), "<p>Hi there</p>");
assert_eq!(dom.find_by_tag("script").count(), 0);
Source

pub fn set_attr(&mut self, id: NodeId, name: &str, value: &str)

Set attribute name (ASCII-lower-cased) to value on an element, replacing any existing value. Does nothing for non-element nodes, and nothing if name is not a valid attribute name: empty, or containing ASCII whitespace, ", ', <, >, /, = or NUL — names are written out as they are, so these would change the markup around them.

let mut dom = domtree::parse("<a href=/x>x</a>");
let a = dom.find_by_tag("a").next().unwrap().id();
dom.set_attr(a, "href", "https://example.com/x");
dom.set_attr(a, "rel", "nofollow");
dom.set_attr(a, "x><script>alert(1)</script", "refused");
assert_eq!(dom.to_html(), r#"<a href="https://example.com/x" rel="nofollow">x</a>"#);
Source

pub fn remove_attr(&mut self, id: NodeId, name: &str)

Remove attribute name (ASCII case-insensitive) from an element, if present.

let mut dom = domtree::parse(r#"<p style="color:red" class=x>hi</p>"#);
let p = dom.find_by_tag("p").next().unwrap().id();
dom.remove_attr(p, "style");
assert_eq!(dom.to_html(), r#"<p class="x">hi</p>"#);
Source

pub fn set_text(&mut self, id: NodeId, text: &str)

Replace a node’s text.

On a text node, replaces its content. On an element, removes all of its children and puts text in a single new text node (none if text is empty) — the DOM’s textContent = text. Does nothing on void elements, comments and doctypes.

text is literal, never parsed (use Dom::append_html for markup), and Dom::to_html escapes &, < and > in it — except inside <script> and <style>, whose content HTML has no escapes for. There it is written as is, apart from any </script (or </style) that would end the element early, which is written <\/script — the same string inside a JavaScript or CSS string literal — so the output still parses back to a single element.

let mut dom = domtree::parse("<h1>Old <em>title</em></h1><p>x</p>");
let h1 = dom.find_by_tag("h1").next().unwrap().id();
dom.set_text(h1, "New & <improved>");
assert_eq!(dom.to_html(), "<h1>New &amp; &lt;improved&gt;</h1><p>x</p>");

let x = dom.find_by_tag("p").next().unwrap().first_child().unwrap().id();
dom.set_text(x, "y");
assert_eq!(dom.to_html(), "<h1>New &amp; &lt;improved&gt;</h1><p>y</p>");

let mut dom = domtree::parse("<script>old()</script>");
let script = dom.root().unwrap().id();
dom.set_text(script, r#"if (a < b) log("</script>")"#);
assert_eq!(dom.to_html(), r#"<script>if (a < b) log("<\/script>")</script>"#);
Source

pub fn rename(&mut self, id: NodeId, tag: &str)

Change an element’s tag name (ASCII-lower-cased). Attributes and children are kept. Does nothing for non-elements, and nothing if tag is not a valid tag name: one that does not start with an ASCII letter, or contains ASCII whitespace, ", ', <, >, /, = or NUL.

Renaming to a void element (br, img, …) keeps any children in the tree, but Dom::to_html — like the HTML syntax — cannot express them.

let mut dom = domtree::parse("<b class=x>bold</b>");
let b = dom.root().unwrap().id();
dom.rename(b, "STRONG");
dom.rename(b, "b><img src=x onerror=alert(1)"); // refused
assert_eq!(dom.to_html(), r#"<strong class="x">bold</strong>"#);
Source

pub fn create_element(&mut self, tag: &str) -> Option<NodeId>

Create a new, empty element (tag ASCII-lower-cased).

It starts detached: not part of the document until you attach it with Dom::append_child or Dom::insert_before. Created nodes have no source_range.

Returns None (and creates nothing) if tag is not a valid tag name (see Dom::rename).

let mut dom = domtree::parse("<ul><li>a</li></ul>");
let ul = dom.root().unwrap().id();
let li = dom.create_element("li").unwrap();
let text = dom.create_text("b");
dom.append_child(li, text);
dom.set_attr(li, "class", "new");
dom.append_child(ul, li);
assert_eq!(dom.to_html(), r#"<ul><li>a</li><li class="new">b</li></ul>"#);

assert!(dom.create_element("li><img").is_none());
Source

pub fn create_text(&mut self, text: &str) -> NodeId

Create a new, detached text node holding text literally (see Dom::create_element for how detached nodes behave).

let mut dom = domtree::parse("<p></p>");
let p = dom.root().unwrap().id();
let t = dom.create_text("1 < 2");
dom.append_child(p, t);
assert_eq!(dom.to_html(), "<p>1 &lt; 2</p>");
Source

pub fn append_child(&mut self, parent: NodeId, child: NodeId) -> bool

Move child (with its subtree) to the end of parent’s children.

child may be a created node, a removed one, or a node elsewhere in the document (it is detached from its old place first). If parent is itself detached, child ends up detached with it. Returns false and changes nothing if parent cannot have children (it is not an element, or is a void element) or if parent is child or inside it — moving a node into its own subtree would make a cycle.

let mut dom = domtree::parse("<div id=a><b>x</b></div><div id=c></div>");
let b = dom.find_by_tag("b").next().unwrap().id();
let c = dom.find_by_id("c").unwrap().id();
assert!(dom.append_child(c, b)); // moves <b> from #a to #c
assert_eq!(dom.to_html(), r#"<div id="a"></div><div id="c"><b>x</b></div>"#);

assert!(!dom.append_child(b, c)); // #c contains <b>: refused, no cycle
let x = dom.get(b).unwrap().first_child().unwrap().id();
assert!(!dom.append_child(x, c)); // text nodes have no children
Source

pub fn insert_before(&mut self, reference: NodeId, node: NodeId) -> bool

Move node (with its subtree) to just before reference, as its sibling — at the top level if reference is a top-level node.

Like Dom::append_child, node may come from anywhere. Returns false and changes nothing if reference has no place to insert at (it is detached with no parent), if node is reference, or if node contains reference (a cycle).

let mut dom = domtree::parse("<li>b</li><li>c</li><li>a</li>");
let items: Vec<_> = dom.find_by_tag("li").map(|n| n.id()).collect();
assert!(dom.insert_before(items[0], items[2]));
assert_eq!(dom.to_html(), "<li>a</li><li>b</li><li>c</li>");
Source

pub fn wrap(&mut self, id: NodeId, tag: &str) -> Option<NodeId>

Wrap a node in a new tag element, which takes the node’s place. Returns the wrapper’s id, or None (changing nothing) if id is unknown, or tag is a void element or not a valid tag name (see Dom::rename). Wrapping a detached node gives a detached wrapper.

let mut dom = domtree::parse("<p>Hello <b>world</b></p>");
let b = dom.find_by_tag("b").next().unwrap().id();
let a = dom.wrap(b, "a").unwrap();
dom.set_attr(a, "href", "/world");
assert_eq!(dom.to_html(), r#"<p>Hello <a href="/world"><b>world</b></a></p>"#);
assert!(dom.wrap(b, "").is_none());
Source

pub fn unwrap(&mut self, id: NodeId) -> bool

Replace an element by its children: they move up into its place, in order, and the element itself is removed (detached, now childless). Returns false and changes nothing for non-elements and for detached elements with no parent (there is no place to move the children to).

let mut dom = domtree::parse(r#"<p>a <span class="x">b <i>c</i></span> d</p>"#);
let span = dom.find_by_tag("span").next().unwrap().id();
assert!(dom.unwrap(span));
assert_eq!(dom.to_html(), "<p>a b <i>c</i> d</p>");
Source

pub fn append_html(&mut self, parent: NodeId, html: &str) -> Vec<NodeId>

Parse html as a fragment and append its nodes to the end of parent’s children. Returns the ids of the inserted top-level nodes, in order — empty if html produced no nodes or parent cannot have children (not an element, or a void element).

The fragment is parsed on its own, with the normal parser, not in the context of parent (so <li> closes <li> inside it, but text appended to a <script> is still parsed as HTML). Its parse errors are not added to Dom::errors — those positions refer to the original input; check crate::parse_fragment(html).errors() if you need them. Inserted nodes have no source_range.

let mut dom = domtree::parse("<ul><li>a</li></ul>");
let ul = dom.root().unwrap().id();
let ids = dom.append_html(ul, "<li>b<li>c");
assert_eq!(ids.len(), 2);
assert_eq!(dom.to_html(), "<ul><li>a</li><li>b</li><li>c</li></ul>");
Source

pub fn prepend_html(&mut self, parent: NodeId, html: &str) -> Vec<NodeId>

Parse html and insert its nodes at the start of parent’s children. See Dom::append_html for how the fragment is parsed and what is returned.

let mut dom = domtree::parse("<p>world</p>");
let p = dom.root().unwrap().id();
dom.prepend_html(p, "<b>Hello</b>, ");
assert_eq!(dom.to_html(), "<p><b>Hello</b>, world</p>");
Source

pub fn insert_html_before(&mut self, id: NodeId, html: &str) -> Vec<NodeId>

Parse html and insert its nodes just before id, as its siblings (at the top level if id is a top-level node). Returns nothing inserted if id is detached with no parent. See Dom::append_html for how the fragment is parsed.

let mut dom = domtree::parse("<h1>Title</h1><p>Body</p>");
let h1 = dom.root().unwrap().id();
dom.insert_html_before(h1, "<!doctype html>");
assert_eq!(dom.to_html(), "<!doctype html><h1>Title</h1><p>Body</p>");
Source

pub fn insert_html_after(&mut self, id: NodeId, html: &str) -> Vec<NodeId>

Parse html and insert its nodes just after id, as its siblings. See Dom::insert_html_before.

let mut dom = domtree::parse("<p>See <a href=/docs>docs</a>.</p>");
let a = dom.find_by_tag("a").next().unwrap().id();
dom.insert_html_after(a, " <sup>[1]</sup>");
assert_eq!(dom.to_html(), r#"<p>See <a href="/docs">docs</a> <sup>[1]</sup>.</p>"#);
Source§

impl Dom

Source

pub fn base_url(&self, base: Option<&str>) -> Option<String>

The URL relative links in this document resolve against.

That is the href of the first <base href> element, resolved against base (typically the URL the page was fetched from); without a <base> element, base itself. None when neither is available.

As in a browser, a <base> inside a <template> does not count (a template’s content is not part of the document), and neither does one inside <svg> or <math> (it is not an HTML <base> there).

let dom = domtree::parse(r#"<head><base href="/docs/"></head>"#);
assert_eq!(
    dom.base_url(Some("https://example.com/index.html")).as_deref(),
    Some("https://example.com/docs/"),
);
assert_eq!(dom.base_url(None).as_deref(), Some("/docs/"));
assert_eq!(domtree::parse("<p>x</p>").base_url(None), None);

Every hyperlink in the document — each <a href> and <area href> — in document order, with its URL resolved and its readable text.

URLs resolve against Dom::base_url(base), so a <base href> in the page is honoured. javascript: links are skipped: they run code rather than navigate. Fragment-only (#top), mailto: and other links are kept; filter on Link::url as needed. Embedded resources (<img src>, <link href>, <script src>, …) are not links; see Dom::resources.

Link text comes from one pass over the document; in invalid nested links (<a><a>…</a></a>), the inner text belongs to the inner link only.

let dom = domtree::parse(
    r#"<nav><a href="/docs/">Docs</a> <a href="javascript:void(0)">Menu</a></nav>
       <p>See <a href="../faq.html#top">the
       FAQ</a> or <a href="https://example.org"><img src=logo.png alt="Example"></a>.</p>"#,
);
let links = dom.links(Some("https://example.com/en/guide/"));
let found: Vec<_> = links.iter().map(|l| (l.url.as_str(), l.text.as_str())).collect();
assert_eq!(found, [
    ("https://example.com/docs/", "Docs"),
    ("https://example.com/en/faq.html#top", "the FAQ"),
    ("https://example.org", "Example"),
]);
assert_eq!(links[1].href, "../faq.html#top");
Source

pub fn resources(&self, base: Option<&str>) -> Vec<Resource<'_>>

Every embedded resource URL in the document, in document order: <link href> (stylesheets, icons, feeds, …), every src (images, scripts, frames, media, …), each candidate of a srcset, poster and <object data>, resolved against Dom::base_url(base).

Empty values and javascript: URLs are skipped. For each element the attributes come in the order href, src, srcset, poster, data; if an attribute is repeated, the first one counts (as with NodeRef::attr).

let dom = domtree::parse(
    r#"<link rel=stylesheet href=/main.css>
       <img src="a.png" srcset="a.png 1x, a@2x.png 2x">
       <script src="https://cdn.example.com/x.js"></script>"#,
);
let urls: Vec<_> = dom
    .resources(Some("https://example.com/blog/"))
    .into_iter()
    .map(|r| (r.attr, r.url))
    .collect();
assert_eq!(urls, [
    ("href", "https://example.com/main.css".to_string()),
    ("src", "https://example.com/blog/a.png".to_string()),
    ("srcset", "https://example.com/blog/a.png".to_string()),
    ("srcset", "https://example.com/blog/a@2x.png".to_string()),
    ("src", "https://cdn.example.com/x.js".to_string()),
]);
Source§

impl Dom

Source

pub fn metadata(&self) -> Metadata

The page’s Metadata: title, description, canonical URL, language, OpenGraph and Twitter-card fields, raw JSON-LD blocks and feed links.

The whole document is searched (not just <head>: this parser does not move misplaced tags there), in one pass. Names and properties match ASCII case-insensitively; empty values are ignored.

let dom = domtree::parse(r#"<html lang="en"><head>
  <title>  Shipping a parser
    | Blog </title>
  <meta name="description" content="What we learned.">
  <meta property="og:image" content="https://example.com/cover.png">
  <meta name="twitter:card" content="summary_large_image">
  <link rel="canonical" href="https://example.com/p/1">
  <link rel="alternate" type="application/rss+xml" href="/feed.xml">
  <script type="application/ld+json">{"@type": "BlogPosting"}</script>
</head></html>"#);

let meta = dom.metadata();
assert_eq!(meta.title.as_deref(), Some("Shipping a parser | Blog"));
assert_eq!(meta.description.as_deref(), Some("What we learned."));
assert_eq!(meta.lang.as_deref(), Some("en"));
assert_eq!(meta.canonical.as_deref(), Some("https://example.com/p/1"));
assert_eq!(meta.image.as_deref(), Some("https://example.com/cover.png"));
assert_eq!(meta.twitter, [("twitter:card".to_string(), "summary_large_image".to_string())]);
assert_eq!(meta.json_ld, [r#"{"@type": "BlogPosting"}"#]);
assert_eq!(meta.feeds, ["/feed.xml"]);
Source§

impl Dom

Source

pub fn main_content(&self) -> Option<NodeRef<'_>>

The element holding the page’s main content — the article, post or documentation body without the navigation, sidebars, comments and footer around it. The “reader mode” step before NodeRef::to_markdown in an LLM or RAG pipeline.

A small, deterministic heuristic in the spirit of Mozilla’s Readability. Text is measured in non-whitespace characters of visible text (no <script>, <style>, hidden, aria-hidden="true" or display:none). Boilerplate means <nav>, <aside>, a <header>/<footer> that is not inside an <article>/<main>, an element with a landmark role such as navigation, complementary or contentinfo, or one whose class/id has a boilerplate word (nav, menu, header, footer, sidebar, comment, share, social, related, ad/ads/advert, promo, cookie, banner, breadcrumb, …) and no strong content word (article, content, main, body).

  1. Semantic markup first. If exactly one outermost <article> has enough text (200+ characters and at least a fifth of the page, or half the page), at most half of it in links and at most a quarter in boilerplate, and it is not itself inside boilerplate, it is the answer. Otherwise the same test runs on <main> / [role=main].
  2. Score paragraphs. Each innermost paragraph-like element (<p>, <pre>, <td>, <li>, <dd>, or a block with 25+ characters of its own text) with 25+ characters, at most half of them links, and not inside boilerplate scores 1 + commas + min(chars / 100, 3). The score goes to its parent in full, to its grandparent halved, and to three more ancestors divided by 6, 9 and 12.
  3. Pick the best container. Each scored ancestor starts from a base by tag (div/article/main +5, pre/td/blockquote +3, lists and forms −3, headings −5) plus ±25 each for content / boilerplate words in its class and in its id, adds its paragraph scores, and is scaled by 1 − link density. The highest final score wins; ties go to the earlier element. If no paragraph qualified, step 2 is retried with boilerplate allowed.
  4. Widen to the whole article. While the winner’s parent adds no boilerplate, at most as much text as the winner has, and mostly non-link text (a title, a byline, the rest of a split body), the parent becomes the winner — stopping below <body>.

Returns None when no paragraph qualifies even with boilerplate allowed (an empty, links-only or garbage page), or when the best “container” is the document itself (paragraphs at the top level with no common parent element). Use the whole document then. The chosen element is returned as is: boilerplate inside it (a share bar in an article) is not removed.

let dom = domtree::parse(r#"<body>
  <nav><a href="/">Home</a> <a href="/blog">Blog</a></nav>
  <div class="post">
    <h1>Arena trees</h1>
    <div class="entry">
      <p>An arena stores every node in one vector, and links them by index.</p>
      <p>Traversal is cheap, removal is a flag, and nothing is reference-counted.</p>
    </div>
  </div>
  <div id="comments"><p>Great post, thanks for writing it up in so much detail!</p></div>
</body>"#);

let main = dom.main_content().unwrap();
assert_eq!(main.attr("class"), Some("post"));
assert!(main.to_markdown().starts_with("# Arena trees"));
assert!(!main.to_text().contains("Great post"));
assert!(domtree::parse("<nav><a href=/>Home</a></nav>").main_content().is_none());
Source§

impl Dom

Source

pub fn len(&self) -> usize

Number of nodes in the arena, including removed and detached ones.

Source

pub fn is_empty(&self) -> bool

Whether the document contains no nodes.

Source

pub fn errors(&self) -> &[ParseError]

Recovery diagnostics collected during parsing (empty for clean input).

Source

pub fn get(&self, id: NodeId) -> Option<NodeRef<'_>>

A handle to a node by id, or None if the id is out of range.

Source

pub fn root(&self) -> Option<NodeRef<'_>>

The first top-level node (the document element for a typical page).

Source

pub fn roots(&self) -> impl Iterator<Item = NodeRef<'_>>

All top-level nodes, in document order.

Source

pub fn nodes(&self) -> impl Iterator<Item = NodeRef<'_>>

Every node in the document, in document (pre-order) order. Nodes removed with Dom::remove (and created nodes not yet attached) are skipped.

let mut dom = domtree::parse("<p>a</p><p>c</p>");
let c = dom.find_by_tag("p").nth(1).unwrap().id();
dom.insert_html_before(c, "<p>b</p>");
let text: Vec<_> = dom.nodes().filter_map(|n| n.text()).collect();
assert_eq!(text, ["a", "b", "c"]);
Source

pub fn find_by_tag<'a>( &'a self, name: &'a str, ) -> impl Iterator<Item = NodeRef<'a>> + 'a

All elements whose tag name equals name (ASCII case-insensitive).

Source

pub fn find_by_id<'a>(&'a self, id: &'a str) -> Option<NodeRef<'a>>

The first element with id="…" equal to id, if any.

Source

pub fn find_by_class<'a>( &'a self, class: &'a str, ) -> impl Iterator<Item = NodeRef<'a>> + 'a

All elements whose class attribute contains class.

Source§

impl Dom

Source

pub fn to_html(&self) -> String

Serialize the whole document back to an HTML string.

let dom = domtree::parse("<p class='x'>1 &lt; 2 &amp; 3</p>");
assert_eq!(dom.to_html(), r#"<p class="x">1 &lt; 2 &amp; 3</p>"#);
Source§

impl Dom

Source

pub fn to_text(&self) -> String

The document as readable plain text.

Unlike NodeRef::text_content, this is what a reader sees: no <head>/<script>/<style> content, whitespace collapsed, a new line for each block element, a blank line between paragraphs, <br> as a line break, tab-separated table cells and <pre> kept verbatim. The result has no trailing whitespace on lines, no runs of blank lines, and no leading or trailing blank lines.

let dom = domtree::parse(
    "<head><title>T</title></head><h1>Hello</h1>\
     <p>Some   <b>bold</b>\n text.<br>Next line.</p><script>x()</script>",
);
assert_eq!(dom.to_text(), "Hello\n\nSome bold text.\nNext line.");
Source§

impl Dom

Source

pub fn to_markdown(&self) -> String

Available on crate feature markdown only.

Convert the document to Markdown (CommonMark + GFM tables and strikethrough) — handy for LLM prompts, RAG indexes and notes.

Headings, paragraphs, emphasis, links, images, code spans, fenced code blocks (language from class="language-…"), nested lists, block quotes and tables map to their Markdown forms, and text is escaped so it renders as itself. Hidden elements (<head>, <script>, …) are skipped as in Dom::to_text. Blocks are separated by one blank line, <br> becomes a backslash hard break, and the output has no leading or trailing newline.

let dom = domtree::parse(
    "<h1>Title</h1><p>Some <b>bold</b> and a <a href='/x'>link</a>.</p>\
     <ul><li>one</li><li>two</li></ul>",
);
assert_eq!(
    dom.to_markdown(),
    "# Title\n\nSome **bold** and a [link](/x).\n\n- one\n- two",
);
Source§

impl Dom

Source

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

Available on crate feature markdown only.

The document’s Markdown (Dom::to_markdown) split into chunks of at most max_chars characters (Unicode scalar values) for retrieval (RAG), each carrying its heading path.

  • Sections. A chunk never spans two sections: each top-level heading (# … ######) starts a new chunk, and its plain-text title joins the Chunk::heading_path (replacing any heading of the same or a deeper level). A heading directly followed by a deeper one stays in the same chunk, so # Guide / ## Install / text is one chunk. Titles in the path are capped at 256 characters (cut, then …).
  • Packing. Within a section, whole blocks (paragraphs, lists, quotes, tables, fenced code) are packed greedily, separated by a blank line, while the chunk stays within max_chars. Small sections are not merged with their neighbours.
  • Oversized blocks. A block that alone exceeds max_chars is split at line boundaries. A split code block is re-fenced and a split table repeats its header rows in every piece, so each piece is valid Markdown on its own (when the fence or header leaves room for a line; otherwise lines are split plainly).

Every chunk is at most max_chars long, except one made of a single line that is itself longer — lines are never cut (a long paragraph is one line). A section heading that cannot share a chunk with the first block under it becomes a chunk of its own. No content is lost or reordered: concatenating the chunks gives back the Markdown, plus any repeated fences and table headers. max_chars == 0 is allowed (every line becomes its own chunk). Blank chunks are never returned.

Work and output are linear in the size of the Markdown: every chunk holds at least one line of it, and its heading path at most six capped titles.

let dom = domtree::parse(
    "<h1>Guide</h1><p>Welcome.</p>\
     <h2>Install</h2><p>Run the installer.</p><pre>cargo add dom-tree-rs</pre>\
     <h2>Usage</h2><p>Parse, then query.</p>",
);
let chunks = dom.to_markdown_chunks(500);
let parts: Vec<_> = chunks.iter().map(|c| (c.context(), c.markdown.as_str())).collect();
assert_eq!(parts, [
    ("Guide".to_string(), "# Guide\n\nWelcome."),
    ("Guide > Install".to_string(), "## Install\n\nRun the installer.\n\n```\ncargo add dom-tree-rs\n```"),
    ("Guide > Usage".to_string(), "## Usage\n\nParse, then query."),
]);
Source§

impl Dom

Source

pub fn to_json(&self) -> String

Available on crate feature json only.

Serialize the document to a compact JSON string.

The shape is self-describing: every node is an object with a "type" of element / text / comment / doctype, elements carry tag, attrs (an object) and children (an array), and the whole document is wrapped in { "type": "document", "errors": N, "children": [...] }.

let dom = domtree::parse("<a href=\"/x\">hi</a>");
let json = dom.to_json();
assert!(json.contains("\"tag\":\"a\""));
assert!(json.contains("\"href\":\"/x\""));
assert!(json.contains("\"value\":\"hi\""));
Source§

impl Dom

Source

pub fn select<'a, S: AsSelector + ?Sized>( &'a self, selector: &S, ) -> impl Iterator<Item = NodeRef<'a>>

Available on crate feature query only.

Every element matching a CSS selector, in document order.

selector may be a &str, a &String or a pre-parsed &Selector (see AsSelector). It is parsed once per call; an invalid selector string matches nothing (use Selector::parse to see the error).

let dom = domtree::parse(
    "<div><ul><li class='x'>a</li><li>b</li></ul><ol><li>c</li></ol></div>",
);
assert_eq!(dom.select("ul li.x").count(), 1); // descendant + class
assert_eq!(dom.select("ol > li").count(), 1); // child combinator
assert_eq!(dom.select("ul, ol").count(), 2); // selector list
assert_eq!(dom.select("li:nth-child(2)").count(), 1);
assert_eq!(dom.select("ul:has(.x) + ol").count(), 1);
Source

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

Available on crate feature query only.

The first element (in document order) matching a CSS selector.

let dom = domtree::parse("<p>a</p><p class='x'>b</p><p class='x'>c</p>");
let p = dom.select_first("p.x").unwrap();
assert_eq!(p.text_content(), "b");
assert!(dom.select_first("table").is_none());

Trait Implementations§

Source§

impl Clone for Dom

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 Debug for Dom

Source§

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

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

impl Serialize for Dom

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 Freeze for Dom

§

impl RefUnwindSafe for Dom

§

impl Send for Dom

§

impl Sync for Dom

§

impl Unpin for Dom

§

impl UnsafeUnpin for Dom

§

impl UnwindSafe for Dom

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.