{"id":33934707,"url":"https://github.com/niklak/dom_query","last_synced_at":"2026-04-09T03:31:47.496Z","repository":{"id":213677668,"uuid":"734642713","full_name":"niklak/dom_query","owner":"niklak","description":"A Flexible Rust Crate for DOM Querying and Manipulation","archived":false,"fork":false,"pushed_at":"2026-04-06T09:40:10.000Z","size":1250,"stargazers_count":84,"open_issues_count":3,"forks_count":10,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-06T11:26:30.913Z","etag":null,"topics":["css-selectors","html","html5ever","parser","scraping","selectors","web-scraping"],"latest_commit_sha":null,"homepage":"https://docs.rs/dom_query","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/niklak.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-12-22T08:09:20.000Z","updated_at":"2026-04-06T09:40:17.000Z","dependencies_parsed_at":"2024-11-28T19:19:00.991Z","dependency_job_id":"1667c0f8-cc5d-48e9-8dd0-834fa0941386","html_url":"https://github.com/niklak/dom_query","commit_stats":null,"previous_names":["niklak/dom_query"],"tags_count":47,"template":false,"template_full_name":null,"purl":"pkg:github/niklak/dom_query","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niklak%2Fdom_query","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niklak%2Fdom_query/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niklak%2Fdom_query/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niklak%2Fdom_query/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/niklak","download_url":"https://codeload.github.com/niklak/dom_query/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niklak%2Fdom_query/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31584568,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T14:31:17.711Z","status":"online","status_checked_at":"2026-04-09T02:00:06.848Z","response_time":112,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css-selectors","html","html5ever","parser","scraping","selectors","web-scraping"],"created_at":"2025-12-12T13:53:50.840Z","updated_at":"2026-04-09T03:31:47.489Z","avatar_url":"https://github.com/niklak.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# DOM_QUERY: A Flexible Rust Crate for DOM Querying and Manipulation\n\n[![Crates.io version](https://img.shields.io/crates/v/dom_query.svg?style=flat)](https://crates.io/crates/dom_query)\n[![Download](https://img.shields.io/crates/d/dom_query.svg?style=flat)](https://crates.io/crates/dom_query)\n[![docs.rs docs](https://img.shields.io/badge/docs-latest-blue.svg?style=flat)](https://docs.rs/dom_query)\n[![codecov](https://codecov.io/gh/niklak/dom_query/graph/badge.svg?token=CFAVOIE61O)](https://codecov.io/gh/niklak/dom_query)\n\n[![Build Status](https://github.com/niklak/dom_query/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/niklak/dom_query/actions/workflows/rust.yml)\n[![Rust CI ARM64](https://github.com/niklak/dom_query/actions/workflows/rust-arm64.yml/badge.svg)](https://github.com/niklak/dom_query/actions/workflows/rust-arm64.yml)\n[![wasm ci](https://github.com/niklak/dom_query/actions/workflows/wasm.yml/badge.svg)](https://github.com/niklak/dom_query/actions/workflows/wasm.yml)\n\n\nDOM_QUERY is a flexible Rust crate that simplifies HTML parsing, DOM querying and manipulation by providing a high-level jQuery-like API. It uses the `html5ever` crate for HTML parsing and the `selectors` crate for efficient DOM traversal and element selection.\n\n## Features\n\n- Parse HTML documents and fragments\n- Query DOM elements using CSS selectors\n- Traverse the DOM tree (ancestors, parents, children, siblings)\n- Manipulate elements and their attributes:\n  - Add/remove/modify attributes\n  - Change element content\n  - Add/remove elements\n  - Rename elements\n  - Move elements within the DOM tree\n\n\u003e [!NOTE]\n\u003e This crate is a significantly enhanced fork of [nipper](https://crates.io/crates/nipper),\n\u003e featuring expanded CSS selector support, enhanced DOM traversal  and improved DOM manipulation capabilities.\n\n\n## Examples\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eParsing a document\u003c/b\u003e\u003c/summary\u003e\n\n```rust\nuse dom_query::Document;\nuse tendril::StrTendril;\n// Document may consume \u0026str, String, StrTendril\nlet contents_str = r#\"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\u003chead\u003e\u003ctitle\u003eTest Page\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\u003c/body\u003e\u003c/html\u003e\"#;\nlet doc = Document::from(contents_str);\n\nlet contents_string = contents_str.to_string();\nlet doc = Document::from(contents_string);\n\nlet contents_tendril = StrTendril::from(contents_str);\nlet doc = Document::from(contents_tendril);\n\n// The root element for the `Document` is a Document\nassert!(doc.root().is_document());\n\n// if the source has DocType, then the Document will also have one\n// as a first child.\nassert!(doc.root().first_child().unwrap().is_doctype());\n\n//both of them are not elements.\n```\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eParsing a fragment\u003c/b\u003e\u003c/summary\u003e\n\n```rust\nuse dom_query::Document;\nuse tendril::StrTendril;\n// fragment can be created with Document::fragment(), which accepts \u0026str, String, StrTendril\nlet contents_str = r#\"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\u003chead\u003e\u003ctitle\u003eTest Page\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\u003c/body\u003e\u003c/html\u003e\"#;\nlet fragment = Document::fragment(contents_str);\n\nlet contents_string = contents_str.to_string();\nlet fragment = Document::fragment(contents_string);\n\nlet contents_tendril = StrTendril::from(contents_str);\nlet fragment = Document::fragment(contents_tendril);\n\n// The root element for the  fragment is not a Document but a Fragment\nassert!(!fragment.root().is_document());\nassert!(fragment.root().is_fragment());\n\n// and when it parses a fragment, it drops Doctype\nassert!(!fragment.root().first_child().unwrap().is_doctype());\n```\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eSelecting elements\u003c/b\u003e\u003c/summary\u003e\n\n```rust\nuse dom_query::Document;\nlet html = r#\"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\n        \u003cmeta charset=\"utf-8\"\u003e\n        \u003ctitle\u003eTest Page\u003c/title\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003ch1\u003eTest Page\u003c/h1\u003e\n        \u003cul\u003e\n            \u003cli\u003eOne\u003c/li\u003e\n            \u003cli\u003e\u003ca href=\"/2\"\u003eTwo\u003c/a\u003e\u003c/li\u003e\n            \u003cli\u003e\u003ca href=\"/3\"\u003eThree\u003c/a\u003e\u003c/li\u003e\n        \u003c/ul\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\"#;\nlet document = Document::from(html);\n// select a single element\nlet a = document.select(\"ul li:nth-child(2)\");\nlet text = a.text().to_string();\nassert!(text == \"Two\");\n// selecting multiple elements\ndocument.select(\"ul \u003e li:has(a)\").iter().for_each(|el| {\n    assert!(el.is(\"li\"));\n})\n\n// there is also `try_select` which returns an Option\nlet no_sel = document.try_select(\"p\");\nassert!(no_sel.is_none());\n\n```\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eSelecting a single match and multiple matches\u003c/b\u003e\u003c/summary\u003e\n\n```rust\nuse dom_query::Document;\nlet doc: Document = r#\"\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\u003c/head\u003e\n\u003cbody\u003e\n    \u003cul class=\"list\"\u003e\n        \u003cli\u003e1\u003c/li\u003e\u003cli\u003e2\u003c/li\u003e\u003cli\u003e3\u003c/li\u003e\n    \u003c/ul\u003e\n    \u003cul class=\"list\"\u003e\n        \u003cli\u003e4\u003c/li\u003e\u003cli\u003e5\u003c/li\u003e\u003cli\u003e6\u003c/li\u003e\n    \u003c/ul\u003e\n\u003c/body\u003e\n\u003c/html\u003e\"#\n    .into();\n// if you need to select only the first, single match, you can use following:\nlet single_selection = doc.select_single(\".list\");\n\n// access is only for the first matching:\nassert_eq!(single_selection.length(), 1);\nassert_eq!(single_selection.inner_html().to_string().trim(), \"\u003cli\u003e1\u003c/li\u003e\u003cli\u003e2\u003c/li\u003e\u003cli\u003e3\u003c/li\u003e\");\n\n// simple selection contains all matches:\nlet selection = doc.select(\".list\");\nassert_eq!(selection.length(), 2);\n\n// but if you call inner_html() on it, you will get the inner_html of the first match:\nassert_eq!(selection.inner_html().to_string().trim(), \"\u003cli\u003e1\u003c/li\u003e\u003cli\u003e2\u003c/li\u003e\u003cli\u003e3\u003c/li\u003e\");\n\n//this approach is using the first node from nodes vec and `select_single` consumes one iteration instead.\nlet first_selection = doc.select(\".list\").first();\nassert_eq!(first_selection.length(), 1);\nassert_eq!(first_selection.inner_html().to_string().trim(), \"\u003cli\u003e1\u003c/li\u003e\u003cli\u003e2\u003c/li\u003e\u003cli\u003e3\u003c/li\u003e\");\n\n// this approach is consuming all nodes into vec at first, and then you can call `iter().next()` to get the first one.\nlet next_selection = doc.select(\".list\").iter().next().unwrap();\nassert_eq!(next_selection.length(), 1);\nassert_eq!(next_selection.inner_html().to_string().trim(), \"\u003cli\u003e1\u003c/li\u003e\u003cli\u003e2\u003c/li\u003e\u003cli\u003e3\u003c/li\u003e\");\n\n// currently, to get data from all matches you need to iterate over them, either:\nlet all_matched: String = selection.iter().map(|s| s.inner_html().trim().to_string()).collect();\nassert_eq!(\nall_matched,\n\"\u003cli\u003e1\u003c/li\u003e\u003cli\u003e2\u003c/li\u003e\u003cli\u003e3\u003c/li\u003e\u003cli\u003e4\u003c/li\u003e\u003cli\u003e5\u003c/li\u003e\u003cli\u003e6\u003c/li\u003e\"\n);\n\n// or:\nlet all_matched: String = selection.nodes().iter().map(|s| s.inner_html().trim().to_string()).collect();\n// which is more efficient.\nassert_eq!(\nall_matched,\n\"\u003cli\u003e1\u003c/li\u003e\u003cli\u003e2\u003c/li\u003e\u003cli\u003e3\u003c/li\u003e\u003cli\u003e4\u003c/li\u003e\u003cli\u003e5\u003c/li\u003e\u003cli\u003e6\u003c/li\u003e\"\n);\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eSelecting descendent elements\u003c/b\u003e\u003c/summary\u003e\n\n```rust\n use dom_query::Document;\n\n let html = r#\"\u003c!DOCTYPE html\u003e\n \u003chtml\u003e\n     \u003chead\u003e\n         \u003cmeta charset=\"utf-8\"\u003e\n         \u003ctitle\u003eTest Page\u003c/title\u003e\n     \u003c/head\u003e\n     \u003cbody\u003e\n         \u003ch1\u003eTest Page\u003c/h1\u003e\n         \u003cul class=\"list-a\"\u003e\n             \u003cli\u003eOne\u003c/li\u003e\n             \u003cli\u003e\u003ca href=\"/2\"\u003eTwo\u003c/a\u003e\u003c/li\u003e\n             \u003cli\u003e\u003ca href=\"/3\"\u003eThree\u003c/a\u003e\u003c/li\u003e\n         \u003c/ul\u003e\n         \u003cul class=\"list-b\"\u003e\n             \u003cli\u003e\u003ca href=\"/4\"\u003eFour\u003c/a\u003e\u003c/li\u003e\n         \u003c/ul\u003e\n     \u003c/body\u003e\n \u003c/html\u003e\"#;\n let document = Document::from(html);\n // select a parent element\n let ul = document.select(\"ul\");\n\n // selecting multiple elements\n ul.select(\"li\").iter().for_each(|el| {\n     assert!(el.is(\"li\"));\n });\n\n // also descendant selector may be specified starting from the parent elements\n let el = ul.select(\"body ul.list-b li\").first();\n let text = el.text();\n assert_eq!(\"Four\", text.to_string());\n\n```\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cb\u003eSelecting ancestors\u003c/b\u003e\u003c/summary\u003e\n\n\n```rust\nuse dom_query::Document;\n\nlet doc: Document = r#\"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003eTest\u003c/head\u003e\n    \u003cbody\u003e\n        \u003cdiv id=\"great-ancestor\"\u003e\n            \u003cdiv id=\"grand-parent\"\u003e\n                \u003cdiv id=\"parent\"\u003e\n                    \u003cdiv id=\"child\"\u003eChild\u003c/div\u003e\n                \u003c/div\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n\"#.into();\n\n// selecting an element\nlet child_sel = doc.select(\"#child\");\nassert!(child_sel.exists());\n\nlet child_node = child_sel.nodes().first().unwrap();\n\n// getting all ancestors\nlet ancestors = child_node.ancestors(None);\n\nlet ancestor_sel = Selection::from(ancestors);\n\n// or just: let ancestor_sel = child_sel.ancestors(None);\n\n// in this case ancestors includes all ancestral nodes including html\n\n// the root html element is presented in the ancestor selection\nassert!(ancestor_sel.is(\"html\"));\n\n// also the direct parent of our starting node is presented\nassert!(ancestor_sel.is(\"#parent\"));\n\n// `Selection::is` matches only the current selection without descending down the tree,\n// so it won't match the #child node.\nassert!(!ancestor_sel.is(\"#child\"));\n\n\n// if you don't require all ancestors, you can specify a number of ancestors you need -- `max_limit`\nlet ancestors = child_node.ancestors(Some(2));\nlet ancestor_sel = Selection::from(ancestors);\n\n// in this case ancestors includes only two ancestral nodes: #grand-parent and #parent\nassert!(ancestor_sel.is(\"#grand-parent #parent\"));\n\nassert!(!ancestor_sel.is(\"#great-ancestor\"));\n\n```\n\u003c/details\u003e\n\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eSelecting with precompiled matchers (for reuse)\u003c/b\u003e\u003c/summary\u003e\n\n```rust\nuse dom_query::{Document, Matcher};\nlet html1 = r#\"\u003c!DOCTYPE html\u003e\u003chtml\u003e\u003chead\u003e\u003ctitle\u003eTest Page 1\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\u003c/body\u003e\u003c/html\u003e\"#;\nlet html2 = r#\"\u003c!DOCTYPE html\u003e\u003chtml\u003e\u003chead\u003e\u003ctitle\u003eTest Page 2\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\u003c/body\u003e\u003c/html\u003e\"#;\n\nlet doc1 = Document::from(html1);\nlet doc2 = Document::from(html2);\n// create a matcher once, reuse on different documents\nlet title_matcher = Matcher::new(\"title\").unwrap();\n\nlet title_el1 = doc1.select_matcher(\u0026title_matcher);\nassert_eq!(\u0026title_el1.text(), \"Test Page 1\");\n\nlet title_el2 = doc2.select_matcher(\u0026title_matcher);\nassert_eq!(\u0026title_el2.text(), \"Test Page 2\");\n// selecting a single match\nlet title_single = doc1.select_single_matcher(\u0026title_matcher);\nassert_eq!(\u0026title_single.text(), \"Test Page 1\");\n```\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eSelecting with pseudo-classes (:has, :has-text, :contains, :only-text)\u003c/b\u003e\u003c/summary\u003e\n\n```rust\nuse dom_query::Document;\n\nlet html = include_str!(\"../test-pages/rustwiki_2024.html\");\nlet doc = Document::from(html);\n\n// searching list items inside a `tr` element which has a `a` element\n// with title=\"Programming paradigm\"\nlet paradigm_selection =\n    doc.select(\n        r#\"table tr:has(a[title=\"Programming paradigm\"]) td.infobox-data ul \u003e li\"#\n    );\n\nprintln!(\"Rust programming paradigms:\");\nfor item in paradigm_selection.iter() {\n    println!(\" {}\", item.text());\n}\nprintln!(\"{:-\u003c50}\", \"\");\n\n//since `th` contains text \"Paradigms\" without sibling tags, we can use `:has-text` pseudo class\nlet influenced_by_selection =\n    doc.select(r#\"table tr:has-text(\"Influenced by\") + tr td  ul \u003e li \u003e a\"#);\n\nprintln!(\"Rust influenced by:\");\nfor item in influenced_by_selection.iter() {\n    println!(\" {}\", item.text());\n}\nprintln!(\"{:-\u003c50}\", \"\");\n\n// Extract all links from the block that contains certain text.\n// Since `foreign function interface` located in its own tag,\n// we have to use `:contains` pseudo class\nlet links_selection =\n    doc.select(\n        r#\"p:contains(\"Rust has a foreign function interface\") a[href^=\"/\"]\"#\n    );\n\nprintln!(\"Links in the FFI block:\");\nfor item in links_selection.iter() {\n    println!(\" {}\", item.attr(\"href\").unwrap());\n}\nprintln!(\"{:-\u003c50}\", \"\");\n\n// :only-text selects an element that contains only a single text node,\n// with no child elements.\n// It can be combined with other pseudo-classes to achieve more specific selections.\n// For example, to select a \u003cdiv\u003e inside an \u003ca\u003e\n//that has no siblings and no child elements other than text.\nprintln!(\"Single \u003cdiv\u003e inside an \u003ca\u003e with text only:\");\nfor el in doc.select(\"a div:only-text:only-child\").iter() {\n    println!(\"{}\", el.text().trim());\n}\n```\n\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eSerializing to HTML\u003c/b\u003e\u003c/summary\u003e\n\n```rust\nuse dom_query::Document;\nlet html = r#\"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\u003ctitle\u003eTest\u003c/title\u003e\u003c/head\u003e\n    \u003cbody\u003e\u003cdiv class=\"content\"\u003e\u003ch1\u003eTest Page\u003c/h1\u003e\u003c/div\u003e\u003c/body\u003e\n\u003c/html\u003e\"#;\nlet doc = Document::from(html);\nlet heading_selector = doc.select(\"div.content\");\n// serializing including the outer html tag\nlet content = heading_selector.html();\nassert_eq!(content.to_string(), r#\"\u003cdiv class=\"content\"\u003e\u003ch1\u003eTest Page\u003c/h1\u003e\u003c/div\u003e\"#);\n// serializing without the outer html tag\nlet inner_content = heading_selector.inner_html();\nassert_eq!(inner_content.to_string(), \"\u003ch1\u003eTest Page\u003c/h1\u003e\");\n\n// there is also `try_html()` method, which returns an `Option\u003cStrTendril\u003e`,\n// and if there is no matching selection it returns None\nlet opt_no_content = doc.select(\"div.no-content\").try_html();\nassert_eq!(opt_no_content, None);\n\n//`html()` method will return an empty `StrTendril` if there is no matching selection\nlet no_content = doc.select(\"div.no-content\").html();\nassert_eq!(\u0026no_content, \"\");\n\n//Same things works for `inner_html()` and `try_inner_html()` method.\nassert_eq!(doc.select(\"div.no-content\").try_inner_html(), None);\nassert_eq!(\u0026doc.select(\"div.no-content\").inner_html(), \"\");\n```\n\u003c/details\u003e\n\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eAccessing descendent text\u003c/b\u003e\u003c/summary\u003e\n\n```rust\nuse dom_query::Document;\n\nlet html = r#\"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\u003ctitle\u003eTest\u003c/title\u003e\u003c/head\u003e\n    \u003cbody\u003e\u003cdiv\u003e\u003ch1\u003eTest \u003cspan\u003ePage\u003c/span\u003e\u003c/h1\u003e\u003c/div\u003e\u003c/body\u003e\n\u003c/html\u003e\"#;\nlet doc = Document::from(html);\nlet body_selection = doc.select(\"body div\").first();\nlet text = body_selection.text();\nassert_eq!(text.to_string(), \"Test Page\");\n```\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eAccessing immediate text\u003c/b\u003e\u003c/summary\u003e\n\n```rust\nuse dom_query::Document;\n\nlet html = r#\"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\u003ctitle\u003eTest\u003c/title\u003e\u003c/head\u003e\n    \u003cbody\u003e\u003cdiv\u003e\u003ch1\u003eTest \u003cspan\u003ePage\u003c/span\u003e\u003c/h1\u003e\u003c/div\u003e\u003c/body\u003e\n\u003c/html\u003e\"#;\n\nlet doc = Document::from(html);\n\nlet body_selection = doc.select(\"body div h1\").first();\n// accessing immediate text without descendants\nlet text = body_selection.immediate_text();\nassert_eq!(text.to_string(), \"Test \");\n\n```\n\n\u003c/details\u003e\n\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eManipulating the attribute of an HTML element\u003c/b\u003e\u003c/summary\u003e\n\n```rust\nuse dom_query::Document;\nlet html = r#\"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\u003ctitle\u003eTest\u003c/title\u003e\u003c/head\u003e\n    \u003cbody\u003e\u003cinput hidden=\"\" id=\"k\" class=\"important\" type=\"hidden\" name=\"k\" data-k=\"100\"\u003e\u003c/body\u003e\n\u003c/html\u003e\"#;\n\nlet doc = Document::from(html);\nlet mut input_selection = doc.select(\"input[name=k]\");\n\n// get the value of attribute \"data-k\"\nlet val = input_selection.attr(\"data-k\").unwrap();\nassert_eq!(val.to_string(), \"100\");\n\n// remove the attribute \"data-k\" from the element\ninput_selection.remove_attr(\"data-k\");\n\n// get the value of attribute \"data-k\", if missing, return default value\nlet val_or = input_selection.attr_or(\"data-k\", \"0\");\nassert_eq!(val_or.to_string(), \"0\");\n\n// remove a list of attributes from the element\ninput_selection.remove_attrs(\u0026[\"id\", \"class\"]);\n// set a attribute \"data-k\" with value \"200\"\ninput_selection.set_attr(\"data-k\", \"200\");\n\nassert_eq!(\u0026input_selection.html(), r#\"\u003cinput hidden=\"\" type=\"hidden\" name=\"k\" data-k=\"200\"\u003e\"#);\n\n// check if attribute \"hidden\" exists on the element\nlet is_hidden = input_selection.has_attr(\"hidden\");\nassert!(is_hidden);\nlet has_title = input_selection.has_attr(\"title\");\nassert!(!has_title);\n\n\n// remove all attributes from the element\ninput_selection.remove_all_attrs();\nassert_eq!(\u0026input_selection.html(), r#\"\u003cinput\u003e\"#);\n\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cb\u003eManipulating the DOM\u003c/b\u003e\u003c/summary\u003e\n\n```rust\nuse dom_query::Document;\nlet html_contents = r#\"\u003c!DOCTYPE html\u003e\n    \u003chtml\u003e\n        \u003chead\u003e\u003ctitle\u003eTest\u003c/title\u003e\u003c/head\u003e\n        \u003cbody\u003e\n            \u003cdiv class=\"content\"\u003e\n            \u003c/div\u003e\n            \u003cdiv class=\"remove-it\"\u003e\n                Remove me\n            \u003c/div\u003e\n            \u003cdiv class=\"replace-it\"\u003e\n                \u003cdiv\u003eReplace me\u003c/div\u003e\n            \u003c/div\u003e\n        \u003c/body\u003e\n    \u003c/html\u003e\"#;;\n\nlet doc = Document::from(html_contents);\n\nlet mut content_selection = doc.select(\"body .content\");\n// append a new html node to the selection\ncontent_selection.append_html(r#\"\u003cdiv class=\"inner\"\u003einner block\u003c/div\u003e\"#);\nassert!(doc.select(\"body .content .inner\").exists());\n\n// set a new content to the selection, replacing existing content\nlet mut set_selection = doc.select(\".inner\");\nset_selection.set_html(r#\"\u003cp\u003e1,2,3\u003c/p\u003e\"#);\nassert_eq!(\u0026doc.select(\".inner\").html(), r#\"\u003cdiv class=\"inner\"\u003e\u003cp\u003e1,2,3\u003c/p\u003e\u003c/div\u003e\"#);\n\n// remove the selection\ndoc.select(\".remove-it\").remove();\nassert!(!doc.select(\".remove-it\").exists());\n\n// replace the selection with a new html, current selection will not change.\nlet mut replace_selection = doc.select(\".replace-it\");\nreplace_selection.replace_with_html(r#\"\u003cdiv class=\"replaced\"\u003eReplaced\u003c/div\u003e\"#);\nassert_eq!(replace_selection.text().trim(), \"Replace me\");\n\n//but the document will change\nassert_eq!(\u0026doc.select(\".replaced\").text(),\"Replaced\");\n\n//instead of appending content, you can prepend it\nlet mut content_selection = doc.select_single(\"body .content\");\n// you can prepend one element or,\ncontent_selection.prepend_html(r#\"\u003cp class=\"third\"\u003e3\u003c/p\u003e\"#);\n// more:\ncontent_selection.prepend_html(r#\"\u003cp class=\"first\"\u003e2\u003c/p\u003e\u003cp class=\"second\"\u003e2\u003c/p\u003e\"#);\n\n// Also you can insert html before selection:\nlet first = content_selection.select(\".first\");\nfirst.before_html(r#\"\u003cp class=\"none\"\u003eNone\u003c/p\u003e\"#);\n// or after:\nlet third = content_selection.select(\".third\");\nthird.after_html(r#\"\u003cp class=\"fourth\"\u003e4\u003c/p\u003e\"#);\n\n// now the added paragraphs standing in front of `div`\nassert!(doc.select(r#\".content \u003e .none + .first + .second + .third + .fourth + div:has-text(\"1,2,3\")\"#).exists());\n\n// to set a text to the selection you can use `set_html` but `set_text` is preferable:\nlet p_sel = content_selection.select(\"p\");\nlet total_p = p_sel.length();\np_sel.set_text(\"test content\");\nassert_eq!(doc.select(r#\"p:has-text(\"test content\")\"#).length(), total_p);\n\n```\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cb\u003eNode manipulations: Creating an empty element, adding a single element to a single node\u003c/b\u003e\u003c/summary\u003e\n\n```rust\nuse dom_query::Document;\n\nlet doc: Document = r#\"\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\u003c/head\u003e\n\u003cbody\u003e\n    \u003cdiv id=\"main\"\u003e\n        \u003cp id=\"first\"\u003eIt's\u003c/p\u003e\n    \u003c/div\u003e\n\u003c/body\u003e\n\u003c/html\u003e\"#.into();\n\n// selecting a node we want to attach a new element\nlet main_sel = doc.select_single(\"#main\");\nlet main_node = main_sel.nodes().first().unwrap();\n\n// if you need just to create a simple element, then you can use the following:\nlet el = doc.tree.new_element(\"p\");\n// you still able to deal with element's attributes:\nel.set_attr(\"id\", \"second\");\n// and set text\nel.set_text(\"test\");\nmain_node.append_child(\u0026el);\n// also main_node.append_child(\u0026el);\nassert!(doc.select(r#\"#main #second:has-text(\"test\")\"#).exists());\n// because this method doesn't parse anything it is much more cheaper than following approaches.\n\n// if you need to add a more complex element, you can use `node.append_html`,\n// which is much more convenient, then previous approach:\n\nmain_node.append_html(r#\"\u003cp id=\"third\"\u003eWonderful\u003c/p\u003e\"#);\nassert_eq!(doc.select(\"#main #third\").text().as_ref(), \"Wonderful\");\n// There is also a `prepend_child` and `prepend_html` methods which allows\n// to insert content to the begging of the node.\nmain_node.prepend_html(r#\"\u003cp id=\"minus-one\"\u003e-1\u003c/p\u003e\u003cp id=\"zero\"\u003e0\u003c/p\u003e\"#);\nassert!(doc.select(\"#main \u003e #minus-one + #zero + #first + #second + #third\").exists());\n\n// if we need to replace existing element content inside a node with a new one, then use `node.set_html`.\n// It changes the inner html contents of the node.\nmain_node.set_html(r#\"\u003cp id=\"the-only\"\u003eWonderful\u003c/p\u003e\"#);\nassert_eq!(doc.select(\"#main #the-only\").text().as_ref(), \"Wonderful\");\nassert!(!doc.select(\"#first\").exists());\n\n// To completely replace contents of the node,\n// including itself use `node.replace_with_html`.\n// Also we can specify more than one element in the string for methods\n// like `replace_with_html`, `set_html` and `append_html`.\nmain_node.replace_with_html(r#\"\u003cspan\u003eTweedledum\u003c/span\u003e and \u003cspan\u003eTweedledee\u003c/span\u003e\"#);\nassert!(!doc.select(\"#main\").exists());\nassert_eq!(doc.select(\"span + span\").text().as_ref(), \"Tweedledee\");\n```\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cb\u003eRenaming selected elements without changing the contents\u003c/b\u003e\u003c/summary\u003e\n\n\n```rust\nuse dom_query::Document;\n\nlet doc: Document = r#\"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\u003ctitle\u003eTest\u003c/title\u003e\u003c/head\u003e\n\u003cbody\u003e\n    \u003cdiv class=\"content\"\u003e\n        \u003cdiv\u003e1\u003c/div\u003e\n        \u003cdiv\u003e2\u003c/div\u003e\n        \u003cdiv\u003e3\u003c/div\u003e\n        \u003cspan\u003e4\u003c/span\u003e\n    \u003c/div\u003e\n\u003cbody\u003e\n\u003c/html\u003e\"#\n.into();\nlet mut sel = doc.select(\"div.content \u003e div, div.content \u003e span\");\n// before renaming, there are 3 `div` and 1 `span`\nassert_eq!(sel.length(), 4);\n\nsel.rename(\"p\");\n\n// after renaming, there are no `div` and `span` elements\nassert_eq!(doc.select(\"div.content \u003e div, div.content \u003e span\").length(), 0);\n// but there are four `p` elements\nassert_eq!(doc.select(\"div.content \u003e p\").length(), 4);\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cb\u003eRetrieving The Base URI\u003c/b\u003e\u003c/summary\u003e\n\n\n```rust\nuse dom_query::Document;\n\nlet contents: \u0026str = r#\"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\n        \u003cbase href=\"https://www.example.com/\"/\u003e\n        \u003ctitle\u003eTest\u003c/title\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003cdiv id=\"main\"\u003e\u003c/div\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\"#;\nlet doc = Document::from(contents);\n// This method is a much faster alternative to\n// `doc.select(\"html \u003e head \u003e base\").attr(\"href\")`.\n// Currently, it does not cache the result, so each time you call it,\n// it will traverse the tree again.\n// The reason it is not cached is to keep `Document` implementing the `Send` trait.\n\n// It may be called from the document level.\nlet base_uri = doc.base_uri().unwrap();\nassert_eq!(base_uri.as_ref(), \"https://www.example.com/\");\n\nlet sel = doc.select_single(\"#main\");\nlet node = sel.nodes().first().unwrap();\n\n// Also it is accessible from any node of the tree.\nlet base_uri = node.base_uri().unwrap();\nassert_eq!(base_uri.as_ref(), \"https://www.example.com/\");\n```\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cb\u003eVerifying Selection and Node Matches\u003c/b\u003e\u003c/summary\u003e\n\n\n```rust\nuse dom_query::Document;\n\nlet contents: \u0026str = r#\"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\n        \u003ctitle\u003eTest\u003c/title\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003cdiv id=\"main\" dir=\"ltr\"\u003e\u003c/div\u003e\n        \u003cdiv id=\"extra\"\u003e\u003c/div\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\"#;\nlet doc = Document::from(contents);\n\nlet main_sel = doc.select_single(\"#main\");\nlet extra_sel = doc.select_single(\"#extra\");\n\n// The `is()` method is available for `Selection` and `NodeRef`.\n// For `Selection`, it verifies that at least one of the nodes in the selection\n// matches the selector.\nassert!(main_sel.is(\"div#main\"));\nassert!(!extra_sel.is(\"div#main\"));\n\n// For `NodeRef`, the `is` method verifies that the node matches the selector.\n// This method is useful if you need to combine several checks into one expression.\n// It can check for having a certain position in the DOM tree,\n// having a certain attribute, or a certain element name all at once.\nlet main_node = main_sel.nodes().first().unwrap();\nlet extra_node = extra_sel.nodes().first().unwrap();\n\nassert!(main_node.is(\"html \u003e body \u003e div#main[dir=ltr]\"));\nassert!(extra_node.is(\"html \u003e body \u003e div#main + div\"));\n```\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cb\u003eFast Finding Child Elements\u003c/b\u003e\u003c/summary\u003e\n\n\n```rust\nuse dom_query::Document;\n\nlet doc: Document = r#\"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\u003ctitle\u003eTest\u003c/title\u003e\u003c/head\u003e\n    \u003cbody\u003e\n        \u003cdiv id=\"main\"\u003e\u003c/div\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\"#.into();\n\nlet main_sel = doc.select_single(\"#main\");\nlet main_node = main_sel.nodes().first().unwrap();\n\n// create 10 child blocks with links\nlet total_links = 10usize;\nfor i in 0..total_links {\n    let content = format!(r#\"\u003cdiv\u003e\u003ca href=\"/{0}\"\u003e{0} link\u003c/a\u003e\u003c/div\u003e\"#, i);\n    main_node.append_html(content);\n}\nlet selected_count = doc.select(\"html body a\").nodes().len();\nassert_eq!(selected_count, total_links);\n\n// `find` currently can deal only with paths that start after the current node.\n// In the following example, `\u0026[\"html\", \"body\", \"div\", \"a\"]` will fail,\n// while `\u0026[\"a\"]` or `\u0026[\"div\", \"a\"]` are okay.\nlet found_count = main_node.find(\u0026[\"div\", \"a\"]).len();\nassert_eq!(found_count, total_links);\n```\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cb\u003eSerializing a document to Markdown\u003c/b\u003e\u003c/summary\u003e\n\n*This example requires `markdown` feature.*\n\n```rust\nuse dom_query::Document;\n\nlet contents = \"\n\u003cstyle\u003ep {color: blue;}\u003c/style\u003e\n\u003cp\u003eI really like using \u003cb\u003eMarkdown\u003c/b\u003e.\u003c/p\u003e\n\n\u003cp\u003eI think I'll use it to format all of my documents from now on.\u003c/p\u003e\";\n\nlet expected = \"I really like using **Markdown**\\\\.\\n\\n\\\nI think I'll use it to format all of my documents from now on\\\\.\";\n\nlet doc = Document::from(contents);\n// Passing `None` into md allows to use default skip tags, which are:\n// `[\"script\", \"style\", \"meta\", \"head\"]`.\nlet got = doc.md(None);\nassert_eq!(got.as_ref(), expected);\n\n// If you need the full text content of the elements, pass `Some(\u0026vec![])` to `md`.\n// If you pass content like the example below to `Document::from`,\n// `html5ever` will create a `\u003chead\u003e` element and place your `\u003cstyle\u003e` element inside it.\n// To preserve the original order, use `Document::fragment`.\n\nlet contents = \"\u003cstyle\u003ep {color: blue;}\u003c/style\u003e\\\n\u003cdiv\u003e\u003ch1\u003eContent Heading\u003ch1\u003e\u003c/div\u003e\\\n\u003cp\u003eI really like using Markdown.\u003c/p\u003e\\\n\u003cp\u003eI think I'll use it to format all of my documents from now on.\u003c/p\u003e\";\n\nlet expected = \"p \\\\{color: blue;\\\\}\\n\\\nI really like using Markdown\\\\.\\n\\n\\\nI think I'll use it to format all of my documents from now on\\\\.\";\n\nlet doc = Document::fragment(contents);\nlet got = doc.md(Some(\u0026[\"div\"]));\nassert_eq!(got.as_ref(), expected);\n\n```\n\n\u003c/details\u003e\n\n- **[more examples](./examples/)**\n- **[dom_query by example](https://niklak.github.io/dom_query_by_example/)**\n\n\n## Crate features\n\n- `hashbrown` — optional,replaces standard hashmaps and hashsets with `hashbrown` hashmaps and hashsets.\n- `atomic` — optional, switches `NodeData` from using `StrTendril` to `Tendril\u003ctendril::fmt::UTF8, tendril::Atomic\u003e`.\nThis allows `NodeData` and all ascending structures, including `Document`, to implement the `Send` trait;\n- `markdown` — optional, enables the `Document::md` and `NodeRef::md` methods, allowing serialization of a document or node to `Markdown` text.\n- `mini_selector` — optional, provides a lightweight and faster alternative for element matching with limited CSS selector support.\n  This includes additional `NodeRef` methods: `find_descendants`, `try_find_descendants`, `mini_is`, and `mini_match`.\n  *This is an experimental feature that may change in future releases.*\n\n## Possible issues\n* [wasm32 compilation](https://niklak.github.io/dom_query_by_example/WASM32-compilation.html)\n\n\n## Changelog\n[Changelog](./CHANGELOG.md)\n\n## License\n\nLicensed under MIT ([LICENSE](LICENSE) or http://opensource.org/licenses/MIT)\n\n\n## Contribution\n\nAny contribution intentionally submitted for inclusion in the work by you, shall be\nlicensed with MIT license, without any additional terms or conditions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniklak%2Fdom_query","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fniklak%2Fdom_query","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniklak%2Fdom_query/lists"}