{"id":27635705,"url":"https://github.com/thednp/domparser","last_synced_at":"2026-04-01T18:31:57.507Z","repository":{"id":275142501,"uuid":"925200910","full_name":"thednp/domparser","owner":"thednp","description":"🍝 Super light HTML parser for isomorphic applications","archived":false,"fork":false,"pushed_at":"2026-03-19T07:58:41.000Z","size":990,"stargazers_count":14,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-03-20T00:55:46.828Z","etag":null,"topics":["domparser"],"latest_commit_sha":null,"homepage":"https://stackblitz.com/fork/github/thednp/domparser/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thednp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":["thednp"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"thanks_dev":null,"custom":null}},"created_at":"2025-01-31T12:31:25.000Z","updated_at":"2026-03-19T07:56:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"15cd48e3-aa41-43f0-8838-28e6d677922a","html_url":"https://github.com/thednp/domparser","commit_stats":null,"previous_names":["thednp/domparser"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/thednp/domparser","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thednp%2Fdomparser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thednp%2Fdomparser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thednp%2Fdomparser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thednp%2Fdomparser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thednp","download_url":"https://codeload.github.com/thednp/domparser/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thednp%2Fdomparser/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290878,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["domparser"],"created_at":"2025-04-23T20:11:25.499Z","updated_at":"2026-04-01T18:31:57.499Z","avatar_url":"https://github.com/thednp.png","language":"TypeScript","funding_links":["https://github.com/sponsors/thednp"],"categories":[],"sub_categories":[],"readme":"## DomParser\r\n[![Coverage Status](https://coveralls.io/repos/github/thednp/domparser/badge.svg)](https://coveralls.io/github/thednp/domparser) \r\n[![NPM Version](https://img.shields.io/npm/v/@thednp/domparser.svg)](https://www.npmjs.com/package/@thednp/domparser)\r\n[![ci](https://github.com/thednp/domparser/actions/workflows/ci.yml/badge.svg)](https://github.com/thednp/domparser/actions/workflows/ci.yml)\r\n\r\n\r\nA TypeScript-based [HTML parser](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser) available in two versions: a lightweight **Parser** focused on speed and memory efficiency (using 64kB chunks), and a feature-rich **DomParser** that provides a DOM-like API with additional capabilities like tag validation.\r\n\r\nAt just ~1.5kB gzipped, the core parser is perfect for both server and client-side applications especially where bundle size matters. The more comprehensive version is ideal for development environments where markup validation and DOM manipulation are needed. Both parsers rely on a versatile tokenizer which implements a chunking strategy to avoid memory overload and prevent a wide range of issues.\r\n\r\nWhile not a direct replacement for the browser's native DOMParser, its modular architecture makes it versatile for various use cases. The library also includes a powerful DOM creation API that improves upon the native `Document` interface, offering a more intuitive and efficient way to build DOM trees programmatically.\r\n\r\nUnlike alternatives such as [jsdom](https://github.com/jsdom/jsdom) or [cheerio](https://cheerio.js.org) that attempt to replicate the entire DOM specification, this library focuses on essential DOM features, resulting in significantly better performance and memory efficiency. In the [benchmark.ts](https://github.com/thednp/domparser/blob/master/demo/benchmark.ts) file we're comparing **Parser** and **DomParser** against **jsdom**, here are some results:\r\n\r\n### Parsing Benchmarks\r\n```\r\nHTML Parsing Performance (5 runs average) (HTML 2551 characters)\r\n\r\nParser     █ 1ms\r\nDomParser  ██ 2ms\r\njsdom      █████████████████████████████████████████████ 54ms\r\n\r\n0ms                    25ms                    50ms\r\n\r\nGenerated on 2025-05-23 10:24:00 UTC\r\n```\r\n\r\n### Query Benchmarks\r\n```\r\nQuery Performance (5 runs average) (query 13 paragraphs)\r\n\r\nDomParser  ████ 1ms\r\njsdom      ████████████████████████ 6ms\r\n\r\n0ms                3ms                6ms\r\n\r\nGenerated on 2025-02-21 10:43:00 UTC\r\n```\r\n\u003e ℹ️ **Note**: _these results come from a desktop PC with NodeJS v23.5.0, your results may vary._\r\n\r\n\r\n### Features\r\n* **Minimal Size with Maximum Flexibility** (~1.5kB core parser, ~4.1kB parser with DOM API, ~2.6kB DOM API)\r\n* **Modern Tree-Shaking Friendly Architecture** (both versions packaged in separate bundles)\r\n* **Isomorphic by Design** (Works in Node.js, Deno, Bun, browsers; No DOM dependencies)\r\n* **High Performance** (Sub-millisecond parsing for typical HTML templates; very fast `match` based queries)\r\n* **TypeScript Support** (First-class TypeScript support with full types).\r\n* **Tested with Vitest** (full 100% code coverage).\r\n\r\n\r\n### Main Components\r\n* **Parser** - the core parser which creates a basic DOM tree very fast and very memory efficient;\r\n* **DomParser** - everything the basic **Parser** comes with, but also allows you to generate a DOM tree that can be manipulated and queried; it can even validate open and closing tags;\r\n* **DOM** - a separate module that allows you to create a `Document` like object with similar API.\r\n\r\n\r\n## Which apps can use it\r\n* plugins that transform SVG files/markup to components for UI frameworks like [React](https://github.com/thednp/vite-react-svg), [Solid](https://github.com/thednp/vite-solid-svg), [VanJS](https://github.com/thednp/vite-vanjs-svg);\r\n* plugins that manage a website's metadata;\r\n* plugins that implement unit testing in a virtual/isolated environment;\r\n* apps that perform web research and/or web-scrapping;\r\n* apps that support server side rendering (SSR);\r\n* apps that require HTML markup validation;\r\n* generally all apps that rely on a very fast runtime.\r\n\r\n\r\n## Installation\r\n```bash\r\nnpm install @thednp/domparser\r\n```\r\n\r\n```bash\r\npnpm add @thednp/domparser\r\n```\r\n\r\n```bash\r\ndeno add npm:@thednp/domparser\r\n```\r\n\r\n```bash\r\nbun add @thednp/domparser\r\n```\r\n\r\n\r\n## Parser Basic Usage\r\n\r\n### Source markup\r\n\r\nLet's take a sample HTML source for this example. We want to showcase all the capabilities and especially how the **Parser** handles special tags, comment and text nodes.\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```html\r\n\u003c!doctype html\u003e\r\n\u003chtml\u003e\r\n    \u003chead\u003e\r\n        \u003cmeta charset=\"UTF-8\"\u003e\r\n        \u003ctitle\u003eExample\u003c/title\u003e\r\n    \u003c/head\u003e\r\n    \u003cbody\u003e\r\n        \u003ch1\u003eHello World!\u003c/h1\u003e\r\n        \u003cp class=\"example\" aria-hidden=\"true\"\u003eThis is an example.\u003c/p\u003e\r\n        \u003ccustom-element /\u003e\r\n        \u003c!-- some comment --\u003e\r\n         \u003c![CDATA[\r\n          /*\r\n            This content is treated as a #text node and\r\n            could contain unescaped chars like \u003c or \u0026\r\n          */\r\n        ]]\u003e\r\n        Some text node.\r\n        \u003cCounter count=\"0\" /\u003e\r\n    \u003c/body\u003e\r\n\u003c/html\u003e\r\n```\r\n\r\n\u003e ℹ️ **Notes**\r\n\u003e * the `\u003c!doctype html\u003e` tag will not be included in the resulting DOM tree, but **DomParser** will add it to the `root.doctype` property;\r\n\u003e * the `charset` value of the `\u003cmeta\u003e` tag will also be added to the `root.charset` property;\r\n\u003e * if attributes of a node aren't valid (missing opening or closing quotes), they are completely removed, this is to prevent crashes or invalid tree structure;\r\n\u003e * both comment and CDATA nodes are registered as `#comment` nodes. \r\n\u003c/details\u003e\r\n\r\n\r\n### Initialize Parser\r\n\r\nFirst let's import and initialize the **Parser** and designate the source to be parsed:\r\n```ts\r\nimport { Parser } from '@thednp/domparser';\r\n\r\n// initialize\r\nconst parser = Parser();\r\n\r\n// source\r\nconst html = source.trim();\r\n/* \r\n// or dynamically import it on your server side\r\nconst html = (await fs.readFile(\"/path-to/index.html\", \"utf-8\")).trim();\r\n*/\r\n```\r\n\r\nNext let's parse the source: \r\n\r\n```ts\r\n// parse the source\r\nconst { components, tags, root } =  parser.parseFromString(html);\r\n```\r\n\r\n\r\n### Parse Results - tags and components\r\n\r\nFirst let's talk about the `components`. All tags with a special pattern will be added to the components results: \r\n```ts\r\n// list all components\r\nconsole.log(components);\r\n/*\r\n[\r\n  // this looks like a CustomElement,\r\n  // you can get it via customElements.get('custom-element')\r\n  \"custom-element\",\r\n  // this looks like a UI framework component\r\n  // handle it accordingly\r\n  \"Counter\"\r\n]\r\n*/\r\n```\r\n\r\nNext let's talk about the `tags`. Basically all valid elements found in the given HTML markup, in order of appearence:\r\n```ts\r\n// list all tags\r\nconsole.log(tags);\r\n// ['html',  'head', 'meta',  'title', 'body',  'h1', 'p']\r\n```\r\n\r\n\r\n### Parse Results - DOM tree\r\nLastly and most importantly, we can finally talk about the real result of the parser, the DOM tree:\r\n```ts\r\n// work with the root\r\nconsole.log(root);\r\n\r\n/*\r\n{\r\n  \"nodeName\": \"#document\",\r\n  \"children\": []\r\n}\r\n*/\r\n```\r\n\r\nBelow we have a near complete representation of the given HTML markup, keep in mind that the contents of the `children` property is not included to shorten the DOM tree.\r\n\r\n\u003e ℹ️ **IMPORTANT** - The light **Parser** will not distinguish nodes like `Element`, `SVGElement` from `TextNode` or `CommentNode` nodes, they are all included in the `children` property.\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\n// the DOM tree output\r\n/*\r\n{\r\n  \"nodeName\": \"#document\",\r\n  \"children\": [\r\n    {\r\n      \"tagName\": \"html\",\r\n      \"nodeName\": \"HTML\",\r\n      \"attributes\": {},\r\n      \"children\": [\r\n        {\r\n          \"tagName\": \"head\",\r\n          \"nodeName\": \"HEAD\",\r\n          \"attributes\": {},\r\n          \"children\": [\r\n            {\r\n              \"tagName\": \"meta\",\r\n              \"nodeName\": \"META\",\r\n              \"attributes\": {\r\n                \"charset\": \"UTF-8\"\r\n              },\r\n              \"children\": [],\r\n            },\r\n            {\r\n              \"tagName\": \"title\",\r\n              \"nodeName\": \"TITLE\",\r\n              \"attributes\": {},\r\n              \"children\": [\r\n                {\r\n                  \"nodeName\": \"#text\",\r\n                  \"nodeValue\": \"Example\"\r\n                }\r\n              ]\r\n            }\r\n          ]\r\n        },\r\n        {\r\n          \"tagName\": \"body\",\r\n          \"nodeName\": \"BODY\",\r\n          \"attributes\": {},\r\n          \"children\": [\r\n            {\r\n              \"tagName\": \"h1\",\r\n              \"nodeName\": \"H1\",\r\n              \"attributes\": {},\r\n              \"children\": [],\r\n              \"children\": [\r\n                {\r\n                  \"nodeName\": \"#text\",\r\n                  \"nodeValue\": \"Hello World!\"\r\n                }\r\n              ]\r\n            },\r\n            {\r\n              \"tagName\": \"p\",\r\n              \"nodeName\": \"P\",\r\n              \"attributes\": {\r\n                \"class\": \"example\",\r\n                \"aria-hidden\": \"true\"\r\n              },\r\n              \"children\": [\r\n                {\r\n                  \"nodeName\": \"#text\",\r\n                  \"nodeValue\": \"This is an example.\"\r\n                }\r\n              ],\r\n            },\r\n            {\r\n              \"tagName\": \"custom-element\",\r\n              \"nodeName\": \"CUSTOM-ELEMENT\",\r\n              \"attributes\": {},\r\n              \"children\": []\r\n            },\r\n            {\r\n              \"nodeName\": \"#comment\",\r\n              \"nodeValue\": \"\u003c!-- some comment --\u003e\"\r\n            },\r\n            {\r\n              \"nodeName\": \"#comment\",\r\n              \"nodeValue\": \"\u003c![CDATA[...]]\u003e\"\r\n            },\r\n            {\r\n              \"nodeName\": \"#text\",\r\n              \"nodeValue\": \"Some text node.\"\r\n            },\r\n            {\r\n              \"tagName\": \"Counter\",\r\n              \"nodeName\": \"COUNTER\",\r\n              \"attributes\": {\r\n                \"count\": \"0\"\r\n              },\r\n              \"children\": []\r\n            }\r\n          ]\r\n        }\r\n      ]\r\n    }\r\n  ]\r\n}\r\n*/\r\n```\r\n\u003c/details\u003e\r\n\r\n\r\n## DomParser Usage\r\nThe **DomParser** returns a similar result as the basic **Parser**, however it also allows you to manipulate the DOM tree or create one similar to how `Document` API works, if no starting HTML markup is provided, you only have a basic `Document` like you can manipulate.\r\n\r\n\u003e ℹ️ Unlike the lighter **Parser** this version _will_ distinguish nodes like `Element`, `SVGElement` from `TextNode` or `CommentNode` nodes, which means that the `children` property contains `Element` and `SVGElement` nodes while the `childNodes` property contains all types of nodes.\r\n\r\n### Initialize DomParser\r\nFirst let's import and initialize **DomParser** and get to build a DOM tree from scratch:\r\n```ts\r\nimport { DomParser } from '@thednp/domparser';\r\n\r\n// initialize\r\nconst doc = DomParser().parseFromString().root;\r\n```\r\n\r\nNow we can use `Document` like methods to create a DOM tree structure:\r\n\r\n```ts\r\nconst html = doc.createElement(\r\n  \"html\",                           // tagName\r\n  { class: \"html-class\" },          // attributes\r\n  doc.createElement(\"head\"),        // childNodes\r\n  // ...other child nodes\r\n);\r\n```\r\n\r\n\r\n### DomParser - Selector Engine\r\n\r\nThe API allows you to perform various queries: `getElementById` (exclusive to the root node), `getElementsByClassName` or `querySelector`. The selector engine uses cache to store common `match` based selectors and prevent re-processing of selectors and drastically increase performance.\r\n\r\n```ts\r\n// exclusive to the root node\r\nconsole.log(doc.getElementById('my-id'));\r\n```\r\nCheck below for more examples.\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { DomParser } from \"@thednp/domparser/dom-parser\";\r\n\r\nconst { root: doc } = DomParser().parseFromString();\r\n\r\n// exclusive to the root node\r\nconsole.log(doc.getElementById('my-id'));\r\n// returns node with id=\"my-id\" or null otherwise\r\n\r\nconsole.log(doc.getElementsByTagName('*'));\r\n// returns nodes with all tag names\r\n\r\nconsole.log(doc.getElementsByTagName('head'));\r\n// returns an array with only \u003chead\u003e node in this case\r\n\r\nconsole.log(doc.getElementsByClassName('html-head'));\r\n// returns an array with only \u003chead\u003e node in this case\r\n\r\nconsole.log(doc.body.querySelectorAll('h1, p'));\r\n// handles multiple selectors and returns all Heading1 and Paragraphs\r\n// found in the children of the body \r\n\r\n// parent-child relationship\r\nconsole.log(doc.body.contains(svg));\r\n// returns true\r\n\r\nconsole.log(doc.head.contains(svg));\r\n// returns false\r\n\r\nconsole.log(svg.closest(\"#my-body\"));\r\n// returns the `body` object\r\n```\r\n\u003e ℹ️ **Note** - direct-child selectors and other pseudo-selectors are not supported.\r\n\u003c/details\u003e\r\n\r\n\r\n### DomParser - Create DOM from HTML\r\n\r\n**DomParser** has its own logic for handling the parsed tokens, a logic that can be configured to remove potentially harmful tags and/or attributes. Here's a quick example:\r\n\r\n```ts\r\nimport { DomParser } from \"@thednp/domparser/dom-parser\";\r\n\r\nconst doc = DomParser({ filterTags: [\"script\"] })\r\n  .parseFromString(`\u003chtml\u003e\u003cscript src=\"some-url\"\u003e\u003c/script\u003e\u003c/html\u003e`).root;\r\n```\r\n\r\nCheck below a more detailed example:\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { DomParser } from \"@thednp/domparser/dom-parser\";\r\n\r\nconst parserOptions = {\r\n  // sets a callback to call on every new node\r\n  onNodeCallback: (node, parent, root) =\u003e {\r\n    // apply any validation, sanitization to your node\r\n    // and return the SAME node reference\r\n    doSomeFunctionWith(node, parent, root);\r\n    return node;\r\n  },\r\n\r\n  // Common dangerous tags that could lead to XSS attacks\r\n  filterTags: [\r\n    \"script\", \"style\", \"iframe\", \"object\", \"embed\", \"base\", \"form\",\r\n    \"input\", \"button\", \"textarea\", \"select\", \"option\"\r\n  ],\r\n\r\n  // Unsafe attributes that could lead to XSS attacks\r\n  filterAttrs: [\r\n    \"onerror\", \"onload\", \"onunload\", \"onclick\", \"ondblclick\", \"onmousedown\",\r\n    \"onmouseup\", \"onmouseover\", \"onmousemove\", \"onmouseout\", \"onkeydown\",\r\n    \"onkeypress\", \"onkeyup\", \"onchange\", \"onsubmit\", \"onreset\", \"onselect\",\r\n    \"onblur\", \"onfocus\", \"formaction\", \"href\", \"xlink:href\", \"action\"\r\n  ]\r\n}\r\n\r\nconst { root: doc } = DomParser(parserOptions).parseFromString(\r\n  \"\u003c!doctype html\u003e\u003chtml\u003e\u003cbody\u003eThis is a basic body\u003c/body\u003e\u003c/html\u003e\",\r\n);\r\n// \u003e the doctype will be added to `doc` as a property;\r\n// \u003e all configured tags and attributes will be removed\r\n// from the resulted parseResult.root object tree.\r\n```\r\n\u003c/details\u003e\r\n\r\n\r\n### Some caveats\r\n* Methods like `createElement`, `getElementById`, etc., are designed to be called on the root node instance (`doc.createElement(...)`) and rely on `this` being bound to the root node object. Destructuring (e.g., `const { createElement } = DomParser.parseFromString()`) will detach these methods from their intended `this` context and cause errors; a workaround would be `createElement.call(yourRootNode, ...arguments)` but that would be detrimental to the readability of the code.\r\n* If you call `DomParser` with an invalid HTML parameter or invalid parser options, it will throw a specific error.\r\n\r\nExamples:\r\n```ts\r\nDomParser(\"invalid\");\r\n// \u003e DomParserError: 1st parameter is not an object\r\n\r\nDomParser.parserFromString({});\r\n// \u003e DomParserError: 1st parameter is not a string \r\n```\r\n\r\n\r\n## API Reference \r\n\r\n### Document API\r\nWhen you create a new **DomParser** instance, you immediately get access to a [Document-like API](https://developer.mozilla.org/en-US/docs/Web/API/Document), but only the essentials. On that note, you are provided with methods to create and remove nodes, check if nodes are added to the DOM tree, or apply queries.\r\n\r\n#### Document - Create Root Node\r\nCurrently there are 2 methods to create a `Document` like root node:\r\n* by invoking `DomParser(options).parseFromString(\"with starting html markup\")` or with no arguments at all;\r\n* by invoking `createDocument()`.\r\n\r\nExample with first method:\r\n```ts\r\nimport { DomParser } from \"@thednp/domparser/dom-parser\";\r\n\r\n// create a root node\r\nconst doc = DomParser.parseFromString().root;\r\n```\r\n\r\nExample with second method:\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// create a root node\r\nconst doc = createDocument();\r\n```\r\n\r\n#### Document - Create Element / Node\r\n\r\nThe root node exposes all known `Document` like methods for creating new nodes, specifically `createElement`, `createElementNS`, `createComment` and `createTextNode` however the `\u003c!doctype html\u003e` node is not supported.\r\n\r\nIn most cases you'll be using something like this:\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// create a root node\r\nconst doc = createDocument();\r\n\r\n// create an Element like node\r\nconst html = doc.createElement(\"html\");\r\n```\r\nCheck the example below for a more detailed workflow.\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// create a root document\r\nconst doc = createDocument();\r\n\r\n// create `Element` like nodes\r\nconst html = doc.createElement(\"html\",\r\n  // define an attributes object as second parameter\r\n  // and/or define multiple child nodes as additional parameters\r\n);\r\n// =\u003e \u003chtml\u003e\r\n\r\n// create `SVGElement` like nodes or namespace\r\nconst svg = doc.createElementNS(\r\n  \"http://www.w3.org/2000/svg\", // namespace\r\n  \"svg\",                        // tagName\r\n  {                             // attributes\r\n    xmlns: \"http://www.w3.org/2000/svg\",\r\n    viewBox: \"0 0 24 24\",\r\n  },\r\n  // define multiple child nodes as additional parameters\r\n);\r\n// =\u003e \u003csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"\u003e\r\n```\r\n\u003c/details\u003e\r\n\r\n---\r\n\r\n#### Document - Create Text and Comment Nodes\r\n\r\nThe API provides methods for creating text nodes and comment nodes, similar to the standard DOM:\r\n\r\n* `createTextNode(text: string)` - creates a new text node with the given text content.\r\n* `createComment(text: string)` - creates a new comment node with the given text content.\r\n\r\nExamples:\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// Create a root document\r\nconst doc = createDocument();\r\n\r\n// Create a text node\r\nconst textNode = doc.createTextNode(\"This is some text.\");\r\n\r\n// Create a comment node\r\nconst commentNode = doc.createComment(\"This is a comment.\");\r\n\r\n// Create an element to hold the nodes\r\nconst paragraph = doc.createElement(\"p\");\r\n\r\n// Append the text and comment nodes to the element\r\nparagraph.append(textNode, commentNode);\r\n\r\n// Append the element to the document body\r\ndoc.body.append(paragraph);\r\n\r\n// Access the child nodes, including the text and comment\r\nconsole.log(paragraph.childNodes);\r\n// Output:\r\n/*\r\n[\r\n  { nodeName: '#text', nodeValue: 'This is some text.' },\r\n  { nodeName: '#comment', nodeValue: '\u003c!-- This is a comment. --\u003e' }\r\n]\r\n*/\r\n```\r\n\r\n\u003c/details\u003e\r\n\r\nYou can also create text and comment nodes by simply providing a string as a child for the `createElement` method, which will handle it by converting it to a text or comment node accordingly.\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// Create a root document.\r\nconst doc = createDocument();\r\n\r\n// Create a new paragraph element with text and comment nodes.\r\nconst paragraph = doc.createElement(\"p\",\r\n\t\"This is text content.\",       // Automatically creates a text node.\r\n\t\"\u003c!-- This is a comment. --\u003e\", // Automatically creates a comment node\r\n);\r\n\r\nconsole.log(paragraph.childNodes);\r\n// Output:\r\n/*\r\n[\r\n  { nodeName: '#text', nodeValue: 'This is text content.' },\r\n  { nodeName: '#comment', nodeValue: '\u003c!-- This is a comment. --\u003e' }\r\n]\r\n*/\r\n```\r\n\u003c/details\u003e\r\n\r\n---\r\n\r\n#### Document - Append Child Nodes\r\nThe Document API provides 2 methods to append nodes to the root node: `append`, which allows adding multiple nodes and `appendChild` which only adds a single node.\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// Create a root document.\r\nconst doc = createDocument();\r\n\r\n// Create a element and append new nodes to it\r\nconst html = doc.createElement(\"html\");\r\nhtml.append(\r\n  doc.createElement(\"head\"),\r\n  doc.createElement(\"body\")\r\n);\r\n\r\n// Append the new element to the root node\r\ndoc.appendChild(html);\r\n```\r\n\u003c/details\u003e\r\n\r\n---\r\n\r\n#### One syntax DOM tree\r\n\r\nAs showcased in the above example, this API is a different from the native API to greatly improve your workflow. In that sense that you can create an entire DOM tree with a single call, with node attributes and children relationships, without having to define a variable for each node, append each node to another.\r\n\r\nAlso you might not need to use `DomParser` in this case because we don't need a starting HTML markup, you can just import and use `createDocument` and get to create the DOM tree:\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// create a root document\r\nconst doc = createDocument();\r\n\r\n// create the entire page if you like\r\ndoc.createElement(\"html\",            // tagName\r\n  { class: \"html-class\" },           // attributes\r\n  doc.createElement(\"head\",          // childNodes\r\n    // the following syntax call will automatically create a `#text` node\r\n    doc.createElement(\"title\", \"This is a title\"),\r\n    doc.createElement(\"meta\", {\r\n      name: \"description\",\r\n      content: \"Some description\",\r\n    }),\r\n  ),\r\n  doc.createElement(\"body\",\r\n    // attributes can be optional\r\n    doc.createElement(\"h1\", \"This is a heading\"),\r\n    doc.createElement(\"p\", \"This is a paragraph\"),\r\n  ),\r\n);\r\n```\r\n\u003c/details\u003e\r\n\r\n---\r\n\r\n\r\n#### Document - Ancestor Relationship\r\n\r\nThe API allows you to `append` nodes to the root node and later you can check if your nodes are present within the DOM tree via the `contains` method.\r\n\r\nThe `append` method is important for some reasons:\r\n* we shouldn't be able to just splice or push into the stored objects, it needs to be consistent in keeping track of which node contains which;\r\n* it's the only method available to add nodes to the DOM tree, which is a recommended practice to make sure selectors and other methods or properties work properly.\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nconst doc = createDocument();\r\n\r\n// create a target node\r\nconst childNode = document.createElement(\"div\");\r\n\r\n// check if root node contains a specific node\r\ndoc.contains(childNode);\r\n// in this case returns `false` because it hasn't been appended\r\n// to any existing node in the DOM tree\r\n\r\n// if we append the node\r\ndoc.append(childNode);\r\ndoc.contains(childNode);\r\n// this now returns `true`\r\n```\r\n\u003c/details\u003e\r\n\r\n---\r\n\r\n#### Document - Children Relationship\r\nThe API exposes `Node` like `readonly` properties to access child nodes:\r\n* `children` - all `Element` like nodes;\r\n* `childNodes` - all nodes including `#text` and `#comment` nodes.\r\n\r\nAlong with these properties, the root node also provides accessors for `documentElement`, `head` and `body`, however the root node comes with an exclusive accessor for `all` which lists all existing `Element` like nodes in the DOM tree.\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nconst doc = createDocument();\r\n\r\n// create a target node\r\nconst childNode = document.createElement(\"html\");\r\n\r\n// now we append the node\r\ndoc.append(childNode);\r\n\r\n// children accessor\r\nconsole.log(doc.children);\r\n// =\u003e [html]\r\n\r\n// childNodes accessor\r\nconsole.log(doc.childNodes);\r\n// =\u003e [html]\r\n\r\n// all accessor\r\nconsole.log(doc.all);\r\n// =\u003e [html]\r\n\r\n// documentElement accessor\r\nconsole.log(doc.documentElement);\r\n// =\u003e html\r\n\r\n// similarly we would have document.head and document.body\r\n// if these nodes have been created and appended\r\n```\r\n\u003c/details\u003e\r\n\r\n---\r\n\r\n#### Document - Remove / Replace Child Nodes\r\nThe Node like API also allows you to remove or replace one or more child nodes via `removeChild` and `replaceChildren` methods. Check examples below for more details.\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// create a root document\r\nconst doc = createDocument();\r\n\r\n// create a child node\r\nconst html = doc.createElement('html');\r\n\r\n// append a child to the root node\r\ndoc.append(html);\r\n\r\n// remove a child from the root node\r\ndoc.removeChild(html);\r\n\r\n// remove all children of the root node\r\ndoc.replaceChildren();\r\n\r\n// replace children of the root node\r\ndoc.replaceChildren(\r\n  doc.createElement('html',\r\n    doc.createElement('head'),\r\n    doc.createElement('body'),\r\n  ),\r\n);\r\n\r\n/* root only methods, internally used */\r\nconst html = doc.createElement('html');\r\n```\r\n\u003e ℹ️ **Note** - `register` and `deregister` are internally used when you call `node.append` and `node.removeChild` respectively. Generally you don't need to use these methods unless you want to override the entire prototype.\r\n\u003c/details\u003e\r\n\r\n---\r\n\r\n#### Document - Selector Engine\r\n\r\nThe Document API exposes all known methods to query the DOM tree, namely `getElementById`, `querySelector`, `querySelectorAll` ,`getElementsByClassName` and `getElementsByTagName`. Unlike the real DOM, instead of `NodeList` or live collections `HTMLCollection`, the results here are `Array` where applicable.\r\n\r\nIt should support multiple selectors comma separated and attribute selectors, however direct selectors and pseudo-selectors are not implemented. Also it caches up to 100 most used matching functions to prevent over processing of the selectors to push performance further.\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nconst doc = createDocument();\r\n\r\ndoc.append(doc.createElement(\"div\", {\r\n  id: \"my-div\", class: \"target\", \"data-visible\": \"true\"\r\n}));\r\n\r\n// find node by ID attribute\r\ndoc.getElementById(\"my-div\");\r\n// =\u003e div#my-div.target\r\n\r\n// find element by CSS selector\r\ndoc.querySelector('.target');\r\n// =\u003e div#my-div.target\r\n\r\n// find elements by attribute CSS selector\r\ndoc.querySelectorAll(\"[data-visible]\");\r\n// =\u003e [div#my-div.target]\r\n\r\n// find elements by multiple selectors\r\ndoc.querySelectorAll(\"p, ul\");\r\n// =\u003e []\r\n\r\n// find elements by class name\r\ndoc.getElementsByClassName('target');\r\n// [div#my-div.target]\r\n\r\n// find elements by tag name\r\ndoc.getElementsByTagName(\"div\");\r\n// =\u003e [div#my-div.target]\r\n\r\n// find elements by ANY tag name\r\ndoc.getElementsByTagName(\"*\");\r\n// =\u003e [div#my-div.target]\r\n```\r\n\u003c/details\u003e\r\n\r\n\r\n### Node \u0026 Element API\r\n\r\nA partial implementation of the [Element API](https://developer.mozilla.org/en-US/docs/Web/API/Element) and [Node API](https://developer.mozilla.org/en-US/docs/Web/API/Node) but only with the essentials. On that note events, animations, box model properties and other `Window` related API (e.g.: `getComputedStyle`, `customElements`, etc) are not available.\r\n\r\nAs a rule of thumb, most properties are `readonly` accessors (getters) for consistency and other reasons some might consider security related.\r\n\r\n#### Node - Append Child Nodes\r\nThe Node API provides 4 methods to append nodes to other nodes:\r\n* `append`, which allows adding multiple nodes;\r\n* `appendChild` which only adds a single node;\r\n* `before` which allows adding multiple nodes **before** the target node;\r\n* `after` which allows adding multiple nodes **after** the target node;\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// Create a root document.\r\nconst doc = createDocument();\r\n\r\n// Create elements and append new nodes\r\nconst html = doc.createElement(\"html\");\r\nconst body =  doc.createElement(\"body\");\r\nconst head =  doc.createElement(\"head\");\r\n\r\n// Append a single node\r\nhtml.append(head);\r\n// Append one or more nodes after the target node\r\nhead.after(body);\r\n\r\n// Append the new element to the root node\r\ndoc.appendChild(html);\r\n```\r\n\u003c/details\u003e\r\n\r\n---\r\n\r\n#### Node - Ancestor Relationship\r\n\r\nThe `Node` API exposes `parentNode`, `ownerDocument` (getters) and `contains` (method):\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nconst doc = createDocument();\r\n\r\nconst html = doc.createElement(\"html\");\r\nconst head = doc.createElement(\"head\");\r\nconst title = doc.createElement(\"title\", \"My App Title\");\r\n\r\n// append the html node to the root\r\ndoc.append(html);\r\n\r\n// append the head node to the html node\r\nhtml.append(head);\r\n// after the above you can also call\r\n// doc.documentElement.append(head)\r\n\r\n// append the title node to the head node\r\nhead.append(title);\r\n\r\n// check parentNode / parentElement\r\nconsole.log(title.parentNode);\r\n// OR\r\nconsole.log(title.parentElement);\r\n// =\u003e head\r\n\r\n// check ownerDocument\r\nconsole.log(title.ownerDocument);\r\n// =\u003e doc\r\n\r\n// check if title is appended\r\nconsole.log(head.contains(title));\r\n// =\u003e true\r\n\r\n// check if title and head nodes are appended\r\nconsole.log(html.contains(title));\r\n\r\n// check if title, head and html nodes are appended\r\nconsole.log(doc.contains(title));\r\n// =\u003e true\r\n```\r\n\u003c/details\u003e\r\n\r\n---\r\n\r\n#### Node - Children Relationship\r\n\r\nThe `Node` prototype will only expose `readonly` properties (getters) to access `children` and `childNodes` for any node instance present in the DOM tree. The rule of thumb is that if a node isn't appended to a parent, it should _not_ be present in the output of these accessors.\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// create a root document\r\nconst doc = createDocument();\r\n\r\n// create nodes\r\nconst html = doc.createElement('html');\r\nconst head = doc.createElement('head');\r\nconst body = doc.createElement('body');\r\nconst comment = doc.createComment('This is a comment');\r\n\r\n// append nodes to DOM tree\r\ndoc.append(html);\r\nhtml.append(head, body, comment);\r\n\r\n// children\r\nconsole.log(html.children);\r\n// =\u003e [head, body]\r\n\r\n// childNodes should also list other type of nodes\r\n// such as #text or #comment nodes \r\nconsole.log(html.childNodes);\r\n// =\u003e [head, body, comment]\r\n```\r\n\u003c/details\u003e\r\n\r\n---\r\n\r\n#### Element - Remove / Replace Child Nodes\r\n\r\nFor consistency the `Element` prototype doesn't have a way to directly add or remove child nodes into the DOM tree, you need to use the provided API.\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// create a root document\r\nconst doc = createDocument();\r\n\r\n// create nodes\r\nconst html = doc.createElement('html');\r\nconst body = doc.createElement('body');\r\n\r\n// append a child to a parent node\r\nhtml.append(body);\r\n\r\n// remove a child from the parent node\r\nhtml.removeChild(body);\r\n\r\n// remove all children of a given node\r\nhtml.replaceChildren();\r\n\r\n// replace children of a given node\r\nhtml.replaceChildren(\r\n  doc.createElement('body',\r\n    doc.createElement('header'),\r\n    doc.createElement('main'),\r\n    doc.createElement('footer'),\r\n  ),\r\n);\r\n\r\n// any node in the DOM tree\r\n// can just remove itself\r\nhtml.remove();\r\n```\r\n\u003c/details\u003e\r\n\r\n---\r\n\r\n#### Element - Attributes\r\n\r\nNodes enhanced with DOM methods and properties don't allow direct access to manipulate the attributes of an `Element` like node, you must use the following API:\r\n\r\n##### Non-namespace Attributes\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// create a root document\r\nconst doc = createDocument();\r\n\r\n// create a non-namespace node\r\nconst html = doc.createElement(\"html\",\r\n  // alternatively you could add an attributes object here\r\n);\r\n\r\n// set attribute\r\nhtml.setAttribute(\"id\", \"app-head\");\r\n\r\n// check attribute\r\nhtml.hasAttribute(\"id\");\r\n// =\u003e true\r\n\r\n// get attribute\r\nhtml.getAttribute(\"id\");\r\n\r\n// common attribute\r\nhtml.id;\r\nhtml.className;\r\n```\r\n\u003c/details\u003e\r\n\r\n\r\n##### Namespace Attributes\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// create a root document\r\nconst doc = createDocument();\r\n\r\n// namespaced attributes\r\nconst svg = doc.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\",\r\n  // alternativelly add an attributes object here\r\n)\r\n\r\n// set attributes\r\nsvg.setAttributeNS(\"http://www.w3.org/2000/svg\", \"xmlns\", \"http://www.w3.org/2000/svg\");\r\nsvg.setAttributeNS(\"http://www.w3.org/2000/svg\", \"viewBox\", \"0 0 24 24\");\r\n\r\n// check attribute\r\nsvg.hasAttributeNS(\"http://www.w3.org/2000/svg\", \"viewBox\");\r\n// =\u003e true\r\n\r\n// get attribute\r\nsvg.getAttributeNS(\"http://www.w3.org/2000/svg\", \"xmlns\");\r\n// =\u003e \"http://www.w3.org/2000/svg\"\r\n\r\n```\r\n\u003c/details\u003e\r\n\r\n---\r\n\r\n#### Element - Selector Engine\r\nThe Element API exposes all known methods to query the DOM tree except `getElementById` which is exclusive to the root node. Same caveats apply as for the Document API.\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// create a root document\r\nconst doc = createDocument();\r\n\r\n// create a DOM tree\r\ndoc.append(\r\n  doc.createElement(\"html\",\r\n    doc.createElement(\"head\",\r\n      doc.createElement(\"title\", \"Page title\")\r\n    ),\r\n    doc.createElement(\"body\",\r\n      doc.createElement(\"main\", { class: \"container\" },\r\n        doc.createElement(\"h1\", \"Page title\"),\r\n        doc.createElement(\"p\", { class: \"lead\" }, \"Lead paragraph\"),\r\n        doc.createElement(\"p\", \"Second paragraph\"),\r\n        doc.createElement(\"button\", { \"data-toggle\": \"popover\" }, \"Read more\"),\r\n      )\r\n    )\r\n  )\r\n);\r\n\r\nconst paragraphs = doc.body.getElementsByTagName(\"p\");\r\n// =\u003e [p, p]\r\n\r\nconst contentItems = doc.body.querySelectorAll(\"h1, p\");\r\n// =\u003e [h1, p, p]\r\n\r\nconst heading = doc.body.querySelector(\"h1\");\r\n// =\u003e h1\r\n\r\nconst main = heading.closest(\"main\");\r\n// =\u003e main\r\n\r\nconst allContents = main.getElementsByTagName(\"*\");\r\n// =\u003e [h1, p.lead, p, button[data-toggle]]\r\n\r\ndoc.head.contains(heading);\r\n// =\u003e false\r\n\r\ndoc.contains(heading);\r\n// =\u003e true\r\n\r\nconst lead = doc.documentElement.getElementsByClassName(\"lead\");\r\n// =\u003e [p.lead]\r\n\r\nconst button = main.children.find(child =\u003e child.matches(\"[data-toggle]\"))\r\n// =\u003e button[data-toggle=\"popover\"]\r\n```\r\n\u003e ℹ️ **Note** - remember that the selector engine doesn't support CSS pseudo-selectors.\r\n\u003c/details\u003e\r\n\r\n\r\n#### Node \u0026 Element - Content Exports\r\n\r\nThe API provides properties for accessing the content of elements:\r\n\r\n* **`innerHTML`**\r\n  **Getter:** - returns the HTML markup contained *within* the element. This includes all child nodes (`Element` like nodes and text nodes), serialized to a formatted HTML string.\r\n* **`outerHTML`**\r\n  **Getter:** returns the complete HTML markup of the element, including the element itself and its contents.\r\n* **`textContent`**\r\n  **Getter:** returns the concatenated text content of the element and all its descendants. This is the text that would be visible if the HTML were rendered, with all tags stripped out.\r\n  **Setter:** replaces the `nodeValue` of a `TextNode` and for a `DOMNode` it will remove all its childnodes and replaced them with a `TextNode` having the specified `string` value.\r\n\r\n\r\nExamples:\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\nconst doc = createDocument();\r\ndoc.append(doc.createElement(\"body\"));\r\n\r\n// Create some elements\r\nconst div = doc.createElement(\"div\", { id: \"myDiv\" });\r\nconst p1 = doc.createElement(\"p\", \"Paragraph 1\");\r\nconst p2 = doc.createElement(\"p\", \"Paragraph 2\");\r\ndiv.append(p1, p2);\r\ndoc.body.append(div);\r\n\r\n// innerHTML\r\nconsole.log(div.innerHTML);\r\n// =\u003e \r\n`\r\n\u003cp\u003eParagraph 1\u003c/p\u003e\r\n\u003cp\u003eParagraph 2\u003c/p\u003e\r\n`\r\n\r\n// outerHTML\r\nconsole.log(div.outerHTML);\r\n// =\u003e \r\n`\r\n\u003cdiv id=\"myDiv\"\u003e\r\n  \u003cp\u003eParagraph 1\u003c/p\u003e\r\n  \u003cp\u003eParagraph 2\u003c/p\u003e\r\n\u003c/div\u003e\r\n`\r\n\r\n// get() textContent\r\nconsole.log(div.textContent);\r\n// =\u003e \r\n`\r\nParagraph 1\r\nParagraph 2\r\n`\r\n\r\n// set() textContent\r\ndiv.textContent = \"A new Node\";\r\n\r\nconsole.log(div.textContent);\r\n\r\n// =\u003e \r\n`\r\nA new Node\r\n`\r\n```\r\n\u003c/details\u003e\r\n\r\n\r\n## Other tools\r\nThe library exports every of its tools you can use in your own library or app, attributes parser or tokenizer.\r\n\r\n### createDocument\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\n/**\r\n * Creates a new `Document` like root node.\r\n *\r\n * @returns a new root node\r\n */\r\nconst createDocument = () =\u003e RootNode;\r\n```\r\n\r\nQuick usage:\r\n```ts\r\nimport { createDocument } from \"@thednp/domparser/dom\";\r\n\r\n// create a root node\r\nconst doc = createDocument();\r\n\r\n// use the available methods\r\nconst html = doc.createElement(\"html\");\r\n```\r\n\u003c/details\u003e\r\n\r\n---\r\n\r\n### tokenize\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\n/**\r\n * Tokenizes an HTML string into an array of HTML tokens.\r\n * These tokens represent opening tags, closing tags, text contents, and comments.\r\n * \r\n * ```ts\r\n * type HTMLToken {\r\n *   type: \"tag\" | \"comment\" | \"text\";\r\n *   value: string;\r\n *   isSC: boolean; // short for (tag) isSelfClosing\r\n * }\r\n * ```\r\n * @param html The HTML string to tokenize.\r\n * @returns An array of `HTMLToken` objects.\r\n */\r\nconst tokenize = (html: string) =\u003e HTMLToken[];\r\n```\r\n\r\nQuick usage:\r\n```ts\r\nimport { tokenize } from \"@thednp/domparser\";\r\n\r\n// use the tokenizer methods\r\nconst html = tokenize(\"\u003chtml\u003e\u003c/html\u003e\");\r\n/*\r\n[\r\n  { type: \"tag\", isSC: false, value: \"html\"},\r\n  { type: \"tag\", isSC: false, value: \"/html\"}\r\n]\r\n*/\r\n// isSC is short for isSelfClosing, e.g.: \u003cpath /\u003e\r\n```\r\n\u003c/details\u003e\r\n\r\n---\r\n\r\n### getBasicAttributes\r\n\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\n/**\r\n * Parse a string token and return an object\r\n * where the keys are the names of the attributes and the values\r\n * are the values of the attributes.\r\n *\r\n * @param tagStr the tring token\r\n * @param config an optional set of options for unsafe attributes\r\n * @returns the attributes object\r\n */\r\nconst getBasicAttributes: (tagStr: string, options) =\u003e Record\u003cstring, string\u003e;\r\n```\r\n\r\nQuick usage:\r\n```ts\r\nimport { getBasicAttributes } from \"@thednp/domparser\";\r\n\r\n// define options\r\nconst options = {\r\n  unsafeAttrs: new Set([\"data-url\"]),\r\n}\r\n\r\n// use the tokenizer methods\r\nconst attributes = getBasicAttributes(\r\n  // the target string\r\n  `html id=\"html\" class=\"html\" data-url=\"https://example.com/api\"`,\r\n  // the options\r\n  options,\r\n);\r\n// the results\r\n/*\r\n{\r\n  id: \"html\",\r\n  class: \"html\",\r\n}\r\n*/\r\n```\r\n\u003c/details\u003e\r\n\r\n**Other tools you might need to use**:\r\n* `isRoot: (node: unknown) =\u003e boolean` - check if an object is a `RootLike` or `RootNode`;\r\n* `isNode: (node: unknown) =\u003e boolean` - check if an object is any kind of node: root, element, text node, etc.\r\n* `isTag: (node: unknown) =\u003e boolean` - check if an object is an `Element` like node;\r\n* `isPrimitive: (node: unknown) =\u003e boolean` - check if value is either `string` or `number`.\r\n\r\n\r\n## Tree-shaking\r\nThis library exports its components as separate modules so you can save even more space and allow for a more flexible sourcing of the code. This is to make sure that even if your setup isn't perfectly configured to handle tree-shaking, you are still bundling only what's actually used.\r\n\u003cdetails\u003e\r\n\u003csummary\u003eClick to expand\u003c/summary\u003e\r\n\r\n```ts\r\n// import Parser only\r\nimport { Parser } from \"@thednp/domparser/parser\"\r\n\r\n// import DomParser only\r\nimport { DomParser } from \"@thednp/domparser/dom-parser\"\r\n\r\n// import createDocument only\r\nimport { createDocument } from \"@thednp/domparser/dom\"\r\n```\r\n\u003c/details\u003e\r\n\r\n\r\n## TypeScript Support\r\n\r\n`@thednp/domparser` is fully typed with TypeScript and type definitions are included in the package for a smooth development experience.\r\n\r\nTo provide a lightweight and performant DOM representation, the **Parser** creates \"Node\" like objects only with essential properties: `nodeName`, `tagName`, [`attributes`, `children`, `childNodes`] for `Element` like nodes and `nodeValue` for basic nodes like `#text`.\r\n\r\nThis is why it's important to distinguish from native browser DOM API, the types have been simplified to set the right expectations and avoid accessing unsuported properties or methods. These are the main nodes in the results of the parser:\r\n* `RootLike` - is for the `Document` node;\r\n* `NodeLike` - is an `Element` like node;\r\n* `CommentLike` - is a `#comment` node;\r\n* `TextLike` - is a `#text` node;\r\n* `ChildLike` - is either `NodeLike`, `TextLike` or `CommentLike`.\r\n\r\nThen we have **DomParser** which overrides some properties and enhance the node prototype with ancestor accessors, selector engine and attributes API. Here are the types for the enhanced nodes:\r\n* `RootNode` - extends `RootLike`;\r\n* `DOMNode` extends `NodeLike`;\r\n* `CommentNode` extends `CommentLike`;\r\n* `TextNode` extends `TextLike`;\r\n* `ChildNode` is either `TextNode`, `CommentNode` or `DOMNode`.\r\n\r\n\r\n## Error Handling\r\n\r\nBoth the **Parser** and the **DomParser** will attempt to parse even malformed HTML, however invalid tags or attributes might be ignored or handled in a specific way (depending on the `filterTags` and `filterAttrs` options).\r\n\r\nFor critical errors (tag open/closing mismatch), **DomParser** throws a specific Error. It does *not* throw exceptions for typical parsing errors.\r\n\r\nExample:\r\n```ts\r\nDomParser().parseFromString(\"\u003chtml\u003e\u003cp\u003e\u003cspan\u003e\u003c/p\u003e\u003c/html\u003e\");\r\n//=\u003e \"DomParserError: Mismatched closing tag: \u003c/p\u003e. Expected closing tag for \u003cspan\u003e.\"\r\n```\r\n\r\n\r\n## Technical Notes\r\n* an audit of the parser reveals a number of _very strong advantages_: usage of character codes, minimal string operations, no nested loops or lookBacks and single pass processing;\r\n* **DomParser** will throw a specific error when an unmatched open/closing tag is detected;\r\n* both parser versions will handle self-closing tags and some cases of invalid markup such as `\u003cpath /\u003e` versus `\u003cpath\u003e\u003c/path\u003e` (cases where both are valid) and `\u003cmeta name=\"..\" /\u003e` vs `\u003cmeta name=\"..\"\u003e` (only the second case is valid);\r\n* parsing HTML markup can lead to heavy memory usage so the tokenizer used by both parsers implements a chunking strategy to avoid memory overload, a default chunk size of 64kB and 128kB maximum token size which, when exceeded, the entire token will be skipped, check the next section for details;\r\n* the tokenizer will handle other cases of invalid markup like missing single/double quote which will result in stripping **all** attributes, while this sounds harsh, it's actually important to prevent breaking the tree structure; (e.g.: `\u003chtml lang=\"en\u003e` becomes `\u003chtml\u003e`);\r\n* both parser versions should be capable to handle HTML comments `\u003c!-- comment --\u003e` and `\u003c!CDATA\u003e` even if they have other valid tags inside, but considering that nested comments aren't supported in the current HTML5 draft; the comment's usual structure is `{ nodeName: \"#comment\", nodeValue: \"\u003c!-- comment --\u003e\" }`; the tokenizer can also handle framework specific hydration comments (e.g.: `\u003c!--[!--\u003e`);\r\n* another note is that `\u003c!doctype\u003e` tag is always stripped, but **DomParser** will add it to the root node in its `doctype` property, which is similar to the native browser API;\r\n* if the current DOM tree contains a `\u003cmeta charset=\"utf-8\"\u003e` **DomParser** will use the `charset` value for the root property `charset`;\r\n* similar to the native browser DOMParser, this script returns a document like tree structure where the root element is a \"root\" property of the output; what's different is that our script will also export a list of tags and a list of components;\r\n* the script properly handles `CustomElement`s, UI Library components, and even camelCase tags like `clipPath` or attributes like `preserveAspectRatio`;\r\n* if you encounter any issue, please report it [here](https://github.com/thednp/domparser/issues), thanks!\r\n\r\n\r\n### Script and Style Content Handling\r\n\r\nThis parser uses an efficient chunking strategy to handle large HTML documents:\r\n- Processes HTML in 64kB chunks to optimize memory usage\r\n- Limits inline `\u003cscript\u003e` and `\u003cstyle\u003e` content to 128kB\r\n- Skips content beyond the limit while maintaining valid HTML structure\r\n- Perfect for most web documents while preventing memory issues\r\n\r\nFor larger scripts and styles, consider using external files with `src` or `href` attributes.\r\n\r\n\r\n## Backstory\r\nI've created some tools to generate SVG components for [VanJS](https://github.com/thednp/vite-plugin-vanjs-svg) and other tools, and I noticed my \"hello world\" app bundle was 102kB and looking into the dependencies, I found that an entire parser and tooling was all bundled in my app client side code and I thought: that's not good. Then I immediately started to work on this thing.\r\n\r\nThe result: bundle size 10kB, render time significantly faster, basically microseconds.\r\n\r\n\r\n## License\r\n**DomParser** is [MIT Licensed](https://github.com/thednp/domparser/blob/master/LICENSE).\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthednp%2Fdomparser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthednp%2Fdomparser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthednp%2Fdomparser/lists"}