{"id":19592308,"url":"https://github.com/smikhalevski/tag-soup","last_synced_at":"2025-10-29T11:01:54.262Z","repository":{"id":41965302,"uuid":"268283090","full_name":"smikhalevski/tag-soup","owner":"smikhalevski","description":"🍜 The fastest pure JS SAX/DOM XML/HTML parser with streaming support.","archived":false,"fork":false,"pushed_at":"2023-03-05T03:22:38.000Z","size":1970,"stargazers_count":7,"open_issues_count":4,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-11T21:01:30.173Z","etag":null,"topics":["dom","html","javascript","parser","sax","xml"],"latest_commit_sha":null,"homepage":"https://smikhalevski.github.io/tag-soup","language":"HTML","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/smikhalevski.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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}},"created_at":"2020-05-31T13:12:24.000Z","updated_at":"2024-09-15T19:58:44.000Z","dependencies_parsed_at":"2024-06-19T23:51:49.419Z","dependency_job_id":"607e67f1-cb4b-419b-92ed-bdc002c8427f","html_url":"https://github.com/smikhalevski/tag-soup","commit_stats":{"total_commits":21,"total_committers":2,"mean_commits":10.5,"dds":0.2857142857142857,"last_synced_commit":"b37fec049ce20679d7837873a49deed5593d9ef2"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/smikhalevski/tag-soup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smikhalevski%2Ftag-soup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smikhalevski%2Ftag-soup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smikhalevski%2Ftag-soup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smikhalevski%2Ftag-soup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/smikhalevski","download_url":"https://codeload.github.com/smikhalevski/tag-soup/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smikhalevski%2Ftag-soup/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262723811,"owners_count":23354118,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["dom","html","javascript","parser","sax","xml"],"created_at":"2024-11-11T08:34:35.379Z","updated_at":"2025-10-29T11:01:54.251Z","avatar_url":"https://github.com/smikhalevski.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"#readme\"\u003e\u003cimg alt=\"TagSoup\" src=\"./assets/logo.png\" width=\"250\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nTagSoup is [the fastest](#performance) pure JS SAX/DOM XML/HTML parser and serializer.\n\n- Extremely low memory consumption.\n- Tolerant of malformed tag nesting, missing end tags, etc.\n- Recognizes CDATA sections, processing instructions, and DOCTYPE declarations.\n- Supports both strict XML and forgiving HTML parsing modes.\n- [20 kB gzipped](https://bundlephobia.com/result?p=tag-soup), including dependencies.\n- Check out TagSoup dependencies: [Speedy Entities](https://github.com/smikhalevski/speedy-entities#readme)\n  and [Flyweight DOM](https://github.com/smikhalevski/flyweight-dom#readme).\n\n```sh\nnpm install --save-prod tag-soup\n```\n\n- [API docs](https://smikhalevski.github.io/tag-soup/)\n- [DOM parsing](#dom-parsing)\n- [SAX parsing](#sax-parsing)\n- [Tokenization](#tokenization)\n- [Serialization](#serialization)\n- [Performance](#performance)\n- [Limitations](#limitations)\n\n# DOM parsing\n\nTagSoup exports preconfigured [`HTMLDOMParser`](https://smikhalevski.github.io/tag-soup/variables/HTMLDOMParser.html)\nwhich parses HTML markup as a DOM node. This parser never throws errors during parsing and forgives malformed markup:\n\n```ts\nimport { HTMLDOMParser, toHTML } from 'tag-soup';\n\nconst fragment = HTMLDOMParser.parseFragment('\u003cp\u003ehello\u003cp\u003ecool\u003c/br\u003e');\n// ⮕ DocumentFragment\n\ntoHTML(fragment);\n// ⮕ '\u003cp\u003ehello\u003c/p\u003e\u003cp\u003ecool\u003cbr\u003e\u003c/p\u003e'\n```\n\n`HTMLDOMParser` decodes both HTML entities and numeric character references with\n[`decodeHTML`](https://smikhalevski.github.io/speedy-entities/variables/decodeHTML.html).\n\n[`XMLDOMParser`](https://smikhalevski.github.io/tag-soup/variables/XMLDOMParser.html)\nparses XML markup as a DOM node. It throws\n[`ParserError`](https://smikhalevski.github.io/tag-soup/classes/ParserError.html) if markup doesn't\nsatisfy XML spec:\n\n```ts\nimport { XMLDOMParser, toXML } from 'tag-soup';\n\nXMLDOMParser.parseFragment('\u003cp\u003ehello\u003c/br\u003e');\n// ❌ ParserError: Unexpected end tag.\n\nconst fragment = XMLDOMParser.parseFragment('\u003cp\u003ehello\u003cbr/\u003e\u003c/p\u003e');\n// ⮕ DocumentFragment\n\ntoXML(fragment);\n// ⮕ '\u003cp\u003ehello\u003cbr/\u003e\u003c/p\u003e\n```\n\n`XMLDOMParser` decodes both XML entities and numeric character references with\n[`decodeXML`](https://smikhalevski.github.io/speedy-entities/variables/decodeXML.html).\n\nTagSoup uses [Flyweight DOM](https://github.com/smikhalevski/flyweight-dom#readme) nodes, which provide many standard\nDOM manipulation features:\n\n```ts\nconst document = HTMLDOMParser.parseDocument('\u003c!DOCTYPE html\u003e\u003chtml\u003ehello\u003c/html\u003e');\n\ndocument.doctype.name;\n// ⮕ 'html'\n\ndocument.textContent;\n// ⮕ 'hello'\n```\n\nFor example, you can use `TreeWalker` to traverse DOM nodes:\n\n```ts\nimport { TreeWalker, NodeFilter } from 'flyweight-dom';\n\nconst fragment = XMLDOMParser.parseFragment('\u003cp\u003ehello\u003cbr/\u003e\u003c/p\u003e');\n\nconst treeWalker = new TreeWalker(fragment, NodeFilter.SHOW_TEXT);\n\ntreeWalker.nextNode();\n// ⮕ Text { 'hello' }\n```\n\nCreate a custom DOM parser using\n[`createDOMParser`](https://smikhalevski.github.io/tag-soup/functions/createDOMParser.html):\n\n```ts\nimport { createDOMParser } from 'tag-soup';\n\nconst myParser = createDOMParser({\n  voidTags: ['br'],\n});\n\nmyParser.parseFragment('\u003cp\u003e\u003cbr\u003e\u003c/p\u003e');\n// ⮕ DocumentFragment\n```\n\n# SAX parsing\n\nTagSoup exports preconfigured [`HTMLSAXParser`](https://smikhalevski.github.io/tag-soup/variables/HTMLSAXParser.html)\nwhich parses HTML markup and calls handler methods when a token is read. This parser never throws errors during parsing\nand forgives malformed markup:\n\n```ts\nimport { HTMLSAXParser } from 'tag-soup';\n\nHTMLSAXParser.parseFragment('\u003cp\u003ehello\u003cp\u003ecool\u003c/br\u003e', {\n  onStartTagOpening(tagName) {\n    // Called with 'p', 'p', and 'br'\n  },\n  onText(text) {\n    // Called with 'hello' and 'cool'\n  },\n});\n```\n\n[`XMLSAXParser`](https://smikhalevski.github.io/tag-soup/variables/XMLSAXParser.html) parses XML markup and calls\nhandler methods when a token is read. It throws\n[`ParserError`](https://smikhalevski.github.io/tag-soup/classes/ParserError.html) if markup doesn't satisfy XML spec:\n\n```ts\nimport { XMLSAXParser } from 'tag-soup';\n\nXMLSAXParser.parseFragment('\u003cp\u003ehello\u003c/br\u003e', {});\n// ❌ ParserError: Unexpected end tag.\n\nXMLSAXParser.parseFragment('\u003cp\u003ehello\u003cbr/\u003e\u003c/p\u003e', {\n  onEndTag(tagName) {\n    // Called with 'br' and 'p'\n  },\n});\n```\n\nCreate a custom SAX parser using\n[`createSAXParser`](https://smikhalevski.github.io/tag-soup/functions/createSAXParser.html):\n\n```ts\nimport { createSAXParser } from 'tag-soup';\n\nconst myParser = createSAXParser({\n  voidTags: ['br'],\n});\n\nmyParser.parseFragment('\u003cp\u003e\u003cbr\u003e\u003c/p\u003e', {\n  onStartTagOpening(tagName) {\n    // Called with 'p' and 'br'\n  },\n});\n```\n\n# Tokenization\n\nTagSoup exports preconfigured\n[`HTMLTokenizer`](https://smikhalevski.github.io/tag-soup/variables/HTMLSAXParser.html) which parses HTML markup and\ninvokes a callback when a token is read. This tokenizer never throws errors during tokenization and forgives malformed\nmarkup:\n\n```ts\nimport { HTMLTokenizer } from 'tag-soup';\n\nHTMLTokenizer.tokenizeFragment('\u003cp\u003ehello\u003cp\u003ecool\u003c/br\u003e', (token, startIndex, endIndex) =\u003e {\n  // Handle token\n});\n```\n\n[`XMLTokenizer`](https://smikhalevski.github.io/tag-soup/variables/XMLTokenizer.html) parses XML markup and invokes\na callback when a token is read. It throws\n[`ParserError`](https://smikhalevski.github.io/tag-soup/classes/ParserError.html) if markup doesn't satisfy XML spec:\n\n```ts\nimport { XMLTokenizer } from 'tag-soup';\n\nXMLTokenizer.tokenizeFragment('\u003cp\u003ehello\u003c/br\u003e', (token, startIndex, endIndex) =\u003e {});\n// ❌ ParserError: Unexpected end tag.\n\nXMLTokenizer.tokenizeFragment('\u003cp\u003ehello\u003cbr/\u003e\u003c/p\u003e', (token, startIndex, endIndex) =\u003e {\n  // Handle token\n});\n```\n\nCreate a custom tokenizer using\n[`createTokenizer`](https://smikhalevski.github.io/tag-soup/functions/createTokenizer.html):\n\n```ts\nimport { createTokenizer } from 'tag-soup';\n\nconst myTokenizer = createTokenizer({\n  voidTags: ['br'],\n});\n\nmyTokenizer.tokenizeFragment('\u003cp\u003e\u003cbr\u003e\u003c/p\u003e', (token, startIndex, endIndex) =\u003e {\n  // Handle token\n});\n```\n\n# Serialization\n\nTagSoup exports two preconfigured serializers:\n[`toHTML`](https://smikhalevski.github.io/tag-soup/variables/toHTML.html) and\n[`toXML`](https://smikhalevski.github.io/tag-soup/variables/toXML.html).\n\n```ts\nimport { HTMLDOMParser, toHTML } from 'tag-soup';\n\nconst fragment = HTMLDOMParser.parseFragment('\u003cp\u003ehello\u003cp\u003ecool\u003c/br\u003e');\n// ⮕ DocumentFragment\n\ntoHTML(fragment);\n// ⮕ '\u003cp\u003ehello\u003c/p\u003e\u003cp\u003ecool\u003cbr\u003e\u003c/p\u003e'\n```\n\nCreate a custom serializer using\n[`createSerializer`](https://smikhalevski.github.io/tag-soup/functions/createSerializer.html):\n\n```ts\nimport { HTMLDOMParser, createSerializer } from 'tag-soup';\n\nconst mySerializer = createSerializer({\n  voidTags: ['br'],\n});\n\nconst fragment = HTMLDOMParser.parseFragment('\u003cp\u003ehello\u003c/br\u003e');\n// ⮕ DocumentFragment\n\nmySerializer(fragment);\n// ⮕ '\u003cp\u003ehello\u003cbr\u003e\u003c/p\u003e'\n```\n\n# Performance\n\nExecution performance is measured in operations per second (± 5%), the higher number is better.\nMemory consumption (RAM) is measured in bytes, the lower number is better.\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003cth align=\"right\" valign=\"top\" rowspan=\"2\"\u003eLibrary\u003c/th\u003e\n\u003cth align=\"right\" valign=\"top\" rowspan=\"2\"\u003eLibrary size\u003c/th\u003e\n\u003cth align=\"center\" colspan=\"2\"\u003eDOM parsing\u003c/th\u003e\n\u003cth align=\"center\" colspan=\"2\"\u003eSAX parsing\u003c/th\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd align=\"right\"\u003eOps/sec\u003c/td\u003e\n\u003ctd align=\"right\"\u003eRAM\u003c/td\u003e\n\u003ctd align=\"right\"\u003eOps/sec\u003c/td\u003e\n\u003ctd align=\"right\"\u003eRAM\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd align=\"right\"\u003etag-soup\u0026#x200B;@3.0.0\u003c/td\u003e\n\u003ctd align=\"right\"\u003e\n\u003ca href=\"https://bundlephobia.com/package/tag-soup@3.0.0\"\u003e20 kB\u003c/a\u003e\n\u003c/td\u003e\n\u003ctd align=\"right\"\u003e\u003cstrong\u003e26 Hz\u003c/strong\u003e\u003c/td\u003e\n\u003ctd align=\"right\"\u003e\u003cstrong\u003e22 MB\u003c/strong\u003e\u003c/td\u003e\n\u003ctd align=\"right\"\u003e\u003cstrong\u003e58 Hz\u003c/strong\u003e\u003c/td\u003e\n\u003ctd align=\"right\"\u003e\u003cstrong\u003e22 kB\u003c/strong\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd align=\"right\"\u003e\n\u003ca href=\"https://github.com/fb55/htmlparser2\"\u003ehtmlparser2\u003c/a\u003e\u0026#x200B;@10.0.0\n\u003c/td\u003e\n\u003ctd align=\"right\"\u003e\n\u003ca href=\"https://bundlephobia.com/package/htmlparser2@10.0.0\"\u003e58 kB\u003c/a\u003e\n\u003c/td\u003e\n\u003ctd align=\"right\"\u003e19 Hz\u003c/td\u003e\n\u003ctd align=\"right\"\u003e23 MB\u003c/td\u003e\n\u003ctd align=\"right\"\u003e31 Hz\u003c/td\u003e\n\u003ctd align=\"right\"\u003e10 MB\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd align=\"right\"\u003e\n\u003ca href=\"https://github.com/inikulin/parse5\"\u003eparse5\u003c/a\u003e\u0026#x200B;@8.0.0\n\u003c/td\u003e\n\u003ctd align=\"right\"\u003e\n\u003ca href=\"https://bundlephobia.com/package/parse5@8.0.0\"\u003e45 kB\u003c/a\u003e\n\u003c/td\u003e\n\u003ctd align=\"right\"\u003e7 Hz\u003c/td\u003e\n\u003ctd align=\"right\"\u003e105 MB\u003c/td\u003e\n\u003ctd align=\"right\"\u003e12 Hz\u003c/td\u003e\n\u003ctd align=\"right\"\u003e10 MB\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003c/table\u003e\n\nPerformance was measured when parsing [the 3.8 MB HTML file](./src/test/test.html).\n\nTests were conducted using [TooFast](https://github.com/smikhalevski/toofast#readme) on Apple M1 with Node.js v23.11.1.\n\nTo reproduce [the performance test suite](./src/test/perf/overall.perf.js) results, clone this repo and run:\n\n```shell\nnpm ci\nnpm run build\nnpm run perf\n```\n\n# Limitations\n\nTagSoup doesn't resolve some quirky element structures that malformed HTML may cause.\n\nAssume the following markup:\n\n\u003c!-- prettier-ignore --\u003e\n```html\n\u003cp\u003e\u003cstrong\u003eokay\n\u003cp\u003enope\n```\n\nWith [`DOMParser`](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser) this markup would be transformed to:\n\n```html\n\u003cp\u003e\u003cstrong\u003eokay\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003enope\u003c/strong\u003e\u003c/p\u003e\n```\n\nTagSoup doesn't insert the second `strong` tag:\n\n```html\n\u003cp\u003e\u003cstrong\u003eokay\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003enope\u003c/p\u003e\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmikhalevski%2Ftag-soup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmikhalevski%2Ftag-soup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmikhalevski%2Ftag-soup/lists"}