justhtml 3.6.0__tar.gz → 3.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. {justhtml-3.6.0 → justhtml-3.7.0}/CHANGELOG.md +20 -1
  2. {justhtml-3.6.0 → justhtml-3.7.0}/PKG-INFO +2 -2
  3. {justhtml-3.6.0 → justhtml-3.7.0}/benchmarks/correctness.py +1 -1
  4. {justhtml-3.6.0 → justhtml-3.7.0}/docs/comparison.md +9 -9
  5. {justhtml-3.6.0 → justhtml-3.7.0}/pyproject.toml +2 -2
  6. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/parser/engine.py +1 -1
  7. justhtml-3.7.0/tests/justhtml-tests/character_preprocessing.dat +252 -0
  8. justhtml-3.7.0/tests/justhtml-tests/document_shell_recovery.dat +366 -0
  9. {justhtml-3.6.0 → justhtml-3.7.0}/tests/justhtml-tests/foreign_content.dat +118 -0
  10. justhtml-3.7.0/tests/justhtml-tests/frameset_recovery.dat +229 -0
  11. {justhtml-3.6.0 → justhtml-3.7.0}/tests/justhtml-tests/head_noscript_recovery.dat +24 -0
  12. justhtml-3.7.0/tests/justhtml-tests/in_body_recovery.dat +175 -0
  13. justhtml-3.7.0/tests/justhtml-tests/raw_text_recovery.dat +166 -0
  14. {justhtml-3.6.0 → justhtml-3.7.0}/tests/justhtml-tests/select_recovery.dat +66 -0
  15. {justhtml-3.6.0 → justhtml-3.7.0}/tests/justhtml-tests/selectedcontent.dat +10 -0
  16. {justhtml-3.6.0 → justhtml-3.7.0}/tests/justhtml-tests/table_recovery.dat +107 -0
  17. {justhtml-3.6.0 → justhtml-3.7.0}/tests/justhtml-tests/template_recovery.dat +250 -0
  18. justhtml-3.7.0/tests/justhtml-tests/tokenizer_recovery.dat +238 -0
  19. {justhtml-3.6.0 → justhtml-3.7.0}/tests/justhtml-tests/xml_coercion.dat +24 -0
  20. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_parser_engine.py +6 -127
  21. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_parser_regressions.py +0 -37
  22. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_parser_tree.py +1 -372
  23. justhtml-3.6.0/tests/justhtml-tests/character_preprocessing.dat +0 -110
  24. justhtml-3.6.0/tests/justhtml-tests/document_shell_recovery.dat +0 -106
  25. justhtml-3.6.0/tests/justhtml-tests/frameset_recovery.dat +0 -60
  26. justhtml-3.6.0/tests/justhtml-tests/in_body_recovery.dat +0 -66
  27. {justhtml-3.6.0 → justhtml-3.7.0}/.github/copilot-instructions.md +0 -0
  28. {justhtml-3.6.0 → justhtml-3.7.0}/.github/workflows/ci.yml +0 -0
  29. {justhtml-3.6.0 → justhtml-3.7.0}/.github/workflows/publish.yml +0 -0
  30. {justhtml-3.6.0 → justhtml-3.7.0}/.gitignore +0 -0
  31. {justhtml-3.6.0 → justhtml-3.7.0}/.pre-commit-config.yaml +0 -0
  32. {justhtml-3.6.0 → justhtml-3.7.0}/AGENTS.md +0 -0
  33. {justhtml-3.6.0 → justhtml-3.7.0}/CODE_OF_CONDUCT.md +0 -0
  34. {justhtml-3.6.0 → justhtml-3.7.0}/CONTRIBUTING.md +0 -0
  35. {justhtml-3.6.0 → justhtml-3.7.0}/LICENSE +0 -0
  36. {justhtml-3.6.0 → justhtml-3.7.0}/Makefile +0 -0
  37. {justhtml-3.6.0 → justhtml-3.7.0}/PERFORMANCE.md +0 -0
  38. {justhtml-3.6.0 → justhtml-3.7.0}/README.md +0 -0
  39. {justhtml-3.6.0 → justhtml-3.7.0}/SECURITY.md +0 -0
  40. {justhtml-3.6.0 → justhtml-3.7.0}/assets/justhtml-readme-explainer.png +0 -0
  41. {justhtml-3.6.0 → justhtml-3.7.0}/benchmarks/chromium_parser_diff.py +0 -0
  42. {justhtml-3.6.0 → justhtml-3.7.0}/benchmarks/fuzz.py +0 -0
  43. {justhtml-3.6.0 → justhtml-3.7.0}/benchmarks/html5lib_engine_diff.py +0 -0
  44. {justhtml-3.6.0 → justhtml-3.7.0}/benchmarks/parser_engine_gate.py +0 -0
  45. {justhtml-3.6.0 → justhtml-3.7.0}/benchmarks/performance.py +0 -0
  46. {justhtml-3.6.0 → justhtml-3.7.0}/benchmarks/profile.py +0 -0
  47. {justhtml-3.6.0 → justhtml-3.7.0}/docs/_config.yml +0 -0
  48. {justhtml-3.6.0 → justhtml-3.7.0}/docs/_layouts/default.html +0 -0
  49. {justhtml-3.6.0 → justhtml-3.7.0}/docs/api.md +0 -0
  50. {justhtml-3.6.0 → justhtml-3.7.0}/docs/assets/search.js +0 -0
  51. {justhtml-3.6.0 → justhtml-3.7.0}/docs/bleach-migration.md +0 -0
  52. {justhtml-3.6.0 → justhtml-3.7.0}/docs/building.md +0 -0
  53. {justhtml-3.6.0 → justhtml-3.7.0}/docs/cli.md +0 -0
  54. {justhtml-3.6.0 → justhtml-3.7.0}/docs/correctness.md +0 -0
  55. {justhtml-3.6.0 → justhtml-3.7.0}/docs/encoding.md +0 -0
  56. {justhtml-3.6.0 → justhtml-3.7.0}/docs/errors.md +0 -0
  57. {justhtml-3.6.0 → justhtml-3.7.0}/docs/fragments.md +0 -0
  58. {justhtml-3.6.0 → justhtml-3.7.0}/docs/html-cleaning.md +0 -0
  59. {justhtml-3.6.0 → justhtml-3.7.0}/docs/index.md +0 -0
  60. {justhtml-3.6.0 → justhtml-3.7.0}/docs/linkify.md +0 -0
  61. {justhtml-3.6.0 → justhtml-3.7.0}/docs/migration-examples.md +0 -0
  62. {justhtml-3.6.0 → justhtml-3.7.0}/docs/playground/app.js +0 -0
  63. {justhtml-3.6.0 → justhtml-3.7.0}/docs/playground/index.html +0 -0
  64. {justhtml-3.6.0 → justhtml-3.7.0}/docs/playground/py/__init__.py +0 -0
  65. {justhtml-3.6.0 → justhtml-3.7.0}/docs/playground/py/install_latest_justhtml.py +0 -0
  66. {justhtml-3.6.0 → justhtml-3.7.0}/docs/playground/py/render.py +0 -0
  67. {justhtml-3.6.0 → justhtml-3.7.0}/docs/playground/py/use_local_repo.py +0 -0
  68. {justhtml-3.6.0 → justhtml-3.7.0}/docs/quickstart.md +0 -0
  69. {justhtml-3.6.0 → justhtml-3.7.0}/docs/sanitization.md +0 -0
  70. {justhtml-3.6.0 → justhtml-3.7.0}/docs/selectors.md +0 -0
  71. {justhtml-3.6.0 → justhtml-3.7.0}/docs/streaming.md +0 -0
  72. {justhtml-3.6.0 → justhtml-3.7.0}/docs/text.md +0 -0
  73. {justhtml-3.6.0 → justhtml-3.7.0}/docs/transforms.md +0 -0
  74. {justhtml-3.6.0 → justhtml-3.7.0}/docs/unsafe-handling.md +0 -0
  75. {justhtml-3.6.0 → justhtml-3.7.0}/docs/url-cleaning.md +0 -0
  76. {justhtml-3.6.0 → justhtml-3.7.0}/llms.txt +0 -0
  77. {justhtml-3.6.0 → justhtml-3.7.0}/project_ideas.md +0 -0
  78. {justhtml-3.6.0 → justhtml-3.7.0}/run_tests.py +0 -0
  79. {justhtml-3.6.0 → justhtml-3.7.0}/scripts/release.py +0 -0
  80. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/__init__.py +0 -0
  81. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/__main__.py +0 -0
  82. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/core/__init__.py +0 -0
  83. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/core/constants.py +0 -0
  84. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/core/doctype.py +0 -0
  85. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/core/entities.py +0 -0
  86. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/core/errors.py +0 -0
  87. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/core/rawtext.py +0 -0
  88. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/core/text.py +0 -0
  89. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/core/types.py +0 -0
  90. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/dom/__init__.py +0 -0
  91. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/dom/builder.py +0 -0
  92. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/parser/__init__.py +0 -0
  93. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/parser/context.py +0 -0
  94. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/parser/encoding.py +0 -0
  95. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/parser/options.py +0 -0
  96. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/parser/scanner.py +0 -0
  97. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/parser/stream.py +0 -0
  98. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/py.typed +0 -0
  99. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/sanitizer/__init__.py +0 -0
  100. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/sanitizer/css.py +0 -0
  101. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/sanitizer/dom.py +0 -0
  102. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/sanitizer/policy.py +0 -0
  103. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/sanitizer/policy_defaults.py +0 -0
  104. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/sanitizer/rawtext.py +0 -0
  105. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/sanitizer/url/__init__.py +0 -0
  106. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/sanitizer/url/policy.py +0 -0
  107. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/sanitizer/url/runtime.py +0 -0
  108. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/sanitizer/url/spec.py +0 -0
  109. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/selector/__init__.py +0 -0
  110. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/selector/core.py +0 -0
  111. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/serializer/__init__.py +0 -0
  112. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/serializer/html.py +0 -0
  113. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/serializer/markdown.py +0 -0
  114. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/transforms/__init__.py +0 -0
  115. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/transforms/compile.py +0 -0
  116. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/transforms/linkify.py +0 -0
  117. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/transforms/linkify_core.py +0 -0
  118. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/transforms/runtime.py +0 -0
  119. {justhtml-3.6.0 → justhtml-3.7.0}/src/justhtml/transforms/spec.py +0 -0
  120. {justhtml-3.6.0 → justhtml-3.7.0}/test-summary.txt +0 -0
  121. {justhtml-3.6.0 → justhtml-3.7.0}/tests/README.md +0 -0
  122. {justhtml-3.6.0 → justhtml-3.7.0}/tests/__init__.py +0 -0
  123. {justhtml-3.6.0 → justhtml-3.7.0}/tests/data/wikipedia.html +0 -0
  124. {justhtml-3.6.0 → justhtml-3.7.0}/tests/harness/__init__.py +0 -0
  125. {justhtml-3.6.0 → justhtml-3.7.0}/tests/harness/encoding.py +0 -0
  126. {justhtml-3.6.0 → justhtml-3.7.0}/tests/harness/regressions.py +0 -0
  127. {justhtml-3.6.0 → justhtml-3.7.0}/tests/harness/reporter.py +0 -0
  128. {justhtml-3.6.0 → justhtml-3.7.0}/tests/harness/serializer.py +0 -0
  129. {justhtml-3.6.0 → justhtml-3.7.0}/tests/harness/tree.py +0 -0
  130. {justhtml-3.6.0 → justhtml-3.7.0}/tests/justhtml-sanitize-tests/cases.json +0 -0
  131. {justhtml-3.6.0 → justhtml-3.7.0}/tests/justhtml-tests/entities.test +0 -0
  132. {justhtml-3.6.0 → justhtml-3.7.0}/tests/justhtml-tests/foreign_stack_recovery.dat +0 -0
  133. {justhtml-3.6.0 → justhtml-3.7.0}/tests/justhtml-tests/iframe_srcdoc.dat +0 -0
  134. {justhtml-3.6.0 → justhtml-3.7.0}/tests/justhtml-tests/tokenizer_state_transitions.test +0 -0
  135. {justhtml-3.6.0 → justhtml-3.7.0}/tests/justhtml-tests/tokenizer_states.test +0 -0
  136. {justhtml-3.6.0 → justhtml-3.7.0}/tests/justhtml-tests/xml_coercion_invalid_characters.test +0 -0
  137. {justhtml-3.6.0 → justhtml-3.7.0}/tests/linkify-it/LICENSE.txt +0 -0
  138. {justhtml-3.6.0 → justhtml-3.7.0}/tests/linkify-it/README.md +0 -0
  139. {justhtml-3.6.0 → justhtml-3.7.0}/tests/linkify-it/fixtures/links.txt +0 -0
  140. {justhtml-3.6.0 → justhtml-3.7.0}/tests/linkify-it/fixtures/not_links.txt +0 -0
  141. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_builder.py +0 -0
  142. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_cli.py +0 -0
  143. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_docs_examples.py +0 -0
  144. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_encoding.py +0 -0
  145. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_entities.py +0 -0
  146. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_errors.py +0 -0
  147. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_linkify_internals.py +0 -0
  148. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_linkify_it.py +0 -0
  149. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_linkify_transform.py +0 -0
  150. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_node.py +0 -0
  151. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_playground_local_repo_file_list.py +0 -0
  152. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_sanitize.py +0 -0
  153. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_sanitize_integration.py +0 -0
  154. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_sanitize_transform.py +0 -0
  155. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_selector.py +0 -0
  156. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_serialize.py +0 -0
  157. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_stream.py +0 -0
  158. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_transform_runtime.py +0 -0
  159. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_transforms.py +0 -0
  160. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_transforms_compiler.py +0 -0
  161. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_transforms_sanitize_integration.py +0 -0
  162. {justhtml-3.6.0 → justhtml-3.7.0}/tests/test_wikipedia.py +0 -0
@@ -5,7 +5,26 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [3.7.0] - 2026-07-15
9
+
10
+ ### Fixed
11
+
12
+ - Keep templates open in the head when they contain a processing instruction, matching the latest web-platform-tests tree-construction fixture.
13
+
14
+ ### Changed
15
+
16
+ - Refresh the web-platform-tests tree-construction fixtures and comparison scores.
17
+ - Make the correctness benchmark skip an optional `html5-parser` installation when its libxml2 ABI conflicts with `lxml`.
18
+
19
+ ## [3.6.1] - 2026-07-13
20
+
21
+ ### Fixed
22
+
23
+ - Keep processing instructions inside a scripting-disabled head `noscript`, and process them in body mode inside templates as required by the latest WHATWG parsing rules.
24
+
25
+ ### Changed
26
+
27
+ - Move raw parser input/output regressions from Python unit tests into the shared tree-construction fixture format.
9
28
 
10
29
  ## [3.6.0] - 2026-07-13
11
30
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: justhtml
3
- Version: 3.6.0
3
+ Version: 3.7.0
4
4
  Summary: A pure Python HTML5 parser that just works.
5
5
  Project-URL: Homepage, https://github.com/emilstenstrom/justhtml
6
6
  Project-URL: Issues, https://github.com/emilstenstrom/justhtml/issues
@@ -40,7 +40,7 @@ Requires-Dist: html5-parser; extra == 'benchmark'
40
40
  Requires-Dist: html5lib; extra == 'benchmark'
41
41
  Requires-Dist: lxml; extra == 'benchmark'
42
42
  Requires-Dist: markupever; extra == 'benchmark'
43
- Requires-Dist: selectolax>=0.4.8; extra == 'benchmark'
43
+ Requires-Dist: selectolax>=0.4.11; extra == 'benchmark'
44
44
  Requires-Dist: turbohtml>=1.3.1; extra == 'benchmark'
45
45
  Requires-Dist: zstandard; extra == 'benchmark'
46
46
  Provides-Extra: dev
@@ -148,7 +148,7 @@ def check_parser_available(parser_name):
148
148
  import html5_parser # noqa: F401
149
149
 
150
150
  return True
151
- except ImportError:
151
+ except (ImportError, RuntimeError):
152
152
  return False
153
153
  if parser_name == "markupever":
154
154
  try:
@@ -11,22 +11,22 @@ Use a different tool when one narrow requirement matters more than the whole pip
11
11
  | Tool | HTML5 parsing [1] | Speed | Query | Build | Sanitize | Notes |
12
12
  |------|------------------------------------------|-------|----------|-------|------------------|-------|
13
13
  | **JustHTML**<br>Pure Python | ✅ 100% | ⚡ Fast | ✅ CSS selectors | ✅ `element()` | ✅ Built-in | Correct, secure, easy to install, and fast enough. |
14
- | **`selectolax`**<br>Python wrapper of C-based Lexbor | 🟡 95.4% | 🚀 Very Fast | ✅ CSS selectors | ✅ `create_node()` | ❌ Needs sanitization | Very fast. |
15
- | **Chromium**<br>browser engine | 🟡 95.1% [2] | 🚀 Very Fast | — | — | — | Current browser-harness result. |
16
- | **`turbohtml`**<br>Python wrapper of a C core | 🟡 94.3% | 🚀 Very Fast | ✅ CSS selectors, XPath | ✅ `E.*` builder | ✅ Built-in | Broad, compiled alternative with parsing, querying, and sanitization. |
17
- | **WebKit**<br>browser engine | 🟡 94.0% [2] | 🚀 Very Fast | — | — | — | Current browser-harness result. |
18
- | **Firefox**<br>browser engine | 🟡 93.2% [2] | 🚀 Very Fast | — | — | — | Current browser-harness result. |
14
+ | **`selectolax`**<br>Python wrapper of C-based Lexbor | ✅ 100% | 🚀 Very Fast | ✅ CSS selectors | ✅ `create_node()` | ❌ Needs sanitization | Very fast. |
15
+ | **Chromium**<br>browser engine | 🟡 95.0% [2] | 🚀 Very Fast | — | — | — | Current browser-harness result. |
16
+ | **`turbohtml`**<br>Python wrapper of a C core | 🟡 94.2% | 🚀 Very Fast | ✅ CSS selectors, XPath | ✅ `E.*` builder | ✅ Built-in | Broad, compiled alternative with parsing, querying, and sanitization. |
17
+ | **WebKit**<br>browser engine | 🟡 93.9% [2] | 🚀 Very Fast | — | — | — | Current browser-harness result. |
18
+ | **Firefox**<br>browser engine | 🟡 93.1% [2] | 🚀 Very Fast | — | — | — | Current browser-harness result. |
19
19
  | **`html5lib`**<br>Pure Python | 🟡 82.3% | 🐢 Slow | 🟡 XPath (lxml) | 🟡 Tree API | 🔴 [Deprecated](https://github.com/html5lib/html5lib-python/issues/443) | Unmaintained reference implementation; incomplete coverage of the tree-construction fixtures. |
20
- | **`markupever`**<br>Python wrapper of Rust-based html5ever | 🟡 79.4% | 🚀 Very Fast | ✅ CSS selectors | ✅ `TreeDom .create_*()` | ❌ Needs sanitization | Fast, but 105 fixture cases abort its current parser process. |
20
+ | **`markupever`**<br>Python wrapper of Rust-based html5ever | 🟡 79.4% | 🚀 Very Fast | ✅ CSS selectors | ✅ `TreeDom .create_*()` | ❌ Needs sanitization | Fast, but many fixtures cases abort its current parser process. |
21
21
  | **`html5_parser`**<br>Python wrapper of C-based Gumbo | 🔴 47.8% | 🚀 Very Fast | 🟡 XPath (lxml) | 🟡 `etree` (lxml) | ❌ Needs sanitization | Fast, but its public tree API loses information needed by many fixtures. |
22
22
  | **`BeautifulSoup`**<br>Pure Python | 🔴 0.3% (default) | 🐢 Slow | 🟡 Custom API | ✅ `new_tag()` API | ❌ Needs sanitization | Wraps `html.parser` (default). Can use lxml or html5lib. |
23
23
  | **`html.parser`**<br>Python stdlib | 🔴 0.3% | ⚡ Fast | ❌ None | ❌ None | ❌ Needs sanitization | Standard library. Chokes on malformed HTML. |
24
- | **`lxml`**<br>Python wrapper of C-based libxml2 | 🔴 0.3% | 🚀 Very Fast | 🟡 XPath | ✅ `etree` / E-factory | ❌ Needs sanitization | Fast but not HTML5 compliant. Context-fragment cases are skipped; supported cases still perform poorly. Don't use the old lxml.html.clean module! |
24
+ | **`lxml`**<br>Python wrapper of C-based libxml2 | 🔴 0.9% | 🚀 Very Fast | 🟡 XPath | ✅ `etree` / E-factory | ❌ Needs sanitization | Fast but not HTML5 compliant. Context-fragment cases are skipped; supported cases still perform poorly. Don't use the old lxml.html.clean module! |
25
25
 
26
- [1]: Correctness scores are counted against the [web-platform-tests tree-construction tests](https://github.com/web-platform-tests/wpt/tree/master/html/syntax/parsing/resources). They were run locally at [2026-07-11](https://github.com/web-platform-tests/wpt/commit/ba458f52c8a848bbd9c525ac98fb1f7b30e41dc2).
26
+ [1]: Library correctness scores are counted against the [web-platform-tests tree-construction tests](https://github.com/web-platform-tests/wpt/tree/master/html/syntax/parsing/resources). They were run locally at [2026-07-15](https://github.com/web-platform-tests/wpt/commit/4830edb033cb486fd0cd6f85b5e937cfc718704d) and cover 1,880 cases after excluding scripting-directed fixtures. JustHTML and Selectolax 0.4.11 passed all 1,880 cases; MarkupEver's score counts its 103 process-aborting cases as errors.
27
27
 
28
28
 
29
- [2]: Current local rerun with [`justhtml-html5lib-tests-bench`](https://github.com/EmilStenstrom/justhtml-html5lib-tests-bench) against WPT commit [`ba458f52`](https://github.com/web-platform-tests/wpt/commit/ba458f52c8a848bbd9c525ac98fb1f7b30e41dc2). The browser harness skips 12 `#script-on` cases but includes `#script-off` cases, so these scores are not directly comparable to the 1,879-case Python-parser scores above.
29
+ [2]: Current local rerun with [`justhtml-html5lib-tests-bench`](https://github.com/EmilStenstrom/justhtml-html5lib-tests-bench) against WPT commit [`4830edb`](https://github.com/web-platform-tests/wpt/commit/4830edb033cb486fd0cd6f85b5e937cfc718704d). Chromium 143.0.7499.4, WebKit 26.0, and Firefox 144.0.2 were compared against 1,908 cases; the harness skips 12 `#script-on` cases but includes `#script-off` cases, so these scores are not directly comparable to the 1,880-case Python-parser scores above.
30
30
 
31
31
  ## Why JustHTML
32
32
 
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "justhtml"
3
3
  authors = [{ name = "Emil Stenström", email = "emil@emilstenstrom.se" }]
4
- version = "3.6.0"
4
+ version = "3.7.0"
5
5
  description = "A pure Python HTML5 parser that just works."
6
6
  readme = "README.md"
7
7
  license = { file = "LICENSE" }
@@ -18,7 +18,7 @@ benchmark = [
18
18
  "html5lib",
19
19
  "lxml",
20
20
  "beautifulsoup4",
21
- "selectolax>=0.4.8",
21
+ "selectolax>=0.4.11",
22
22
  "html5-parser",
23
23
  "markupever",
24
24
  "turbohtml>=1.3.1",
@@ -1632,7 +1632,7 @@ class ParseEngine:
1632
1632
  append_text(html[lt:end] if gt == -1 else html[lt : gt + 1], lt)
1633
1633
  pos = end if gt == -1 else gt + 1
1634
1634
  continue
1635
- if self._raw_mode and len(self._stack) > 1 and self._stack[-1].name in _RAWTEXT_ELEMENT_TAGS:
1635
+ if self._raw_mode and len(self._stack) > 1 and self._stack[-1].name in self._rawtext_element_tags:
1636
1636
  comment_end = end if gt == -1 else gt
1637
1637
  append_text(html[lt:comment_end] if gt == -1 else html[lt : gt + 1], lt)
1638
1638
  pos = end if gt == -1 else gt + 1
@@ -0,0 +1,252 @@
1
+ #data
2
+ <math><mi>\x00</mi></math>
3
+ #errors
4
+ (1,6): expected-doctype-but-got-start-tag
5
+ (1,11): unexpected-null-character
6
+
7
+ #document
8
+ | <html>
9
+ | <head>
10
+ | <body>
11
+ | <math math>
12
+ | <math mi>
13
+
14
+ #data
15
+ <math><mi>\x00\x0c</mi></math>
16
+ #errors
17
+ (1,6): expected-doctype-but-got-start-tag
18
+ (1,11): unexpected-null-character
19
+
20
+ #document
21
+ | <html>
22
+ | <head>
23
+ | <body>
24
+ | <math math>
25
+ | <math mi>
26
+ | " "
27
+
28
+ #data
29
+ <math><mi> </mi></math>
30
+ #errors
31
+ (1,1): expected-doctype-but-got-start-tag
32
+
33
+ #document
34
+ | <html>
35
+ | <head>
36
+ | <body>
37
+ | <math math>
38
+ | <math mi>
39
+ | " "
40
+
41
+ #data
42
+ <math><mi>\x0ctext</mi></math>
43
+ #errors
44
+ (1,1): expected-doctype-but-got-start-tag
45
+
46
+ #document
47
+ | <html>
48
+ | <head>
49
+ | <body>
50
+ | <math math>
51
+ | <math mi>
52
+ | " text"
53
+
54
+ #data
55
+ <select>\x0ctext</select>
56
+ #errors
57
+ (1,1): expected-doctype-but-got-start-tag
58
+
59
+ #document
60
+ | <html>
61
+ | <head>
62
+ | <body>
63
+ | <select>
64
+ | " text"
65
+
66
+ #data
67
+ <table>\x00</table>
68
+ #errors
69
+ (1,7): expected-doctype-but-got-start-tag
70
+ (1,8): unexpected-null-character
71
+
72
+ #document
73
+ | <html>
74
+ | <head>
75
+ | <body>
76
+ | <table>
77
+
78
+ #data
79
+ <head></head>\x00
80
+ #errors
81
+ (1,6): expected-doctype-but-got-start-tag
82
+ (1,14): unexpected-null-character
83
+
84
+ #document
85
+ | <html>
86
+ | <head>
87
+ | <body>
88
+
89
+ #data
90
+ <table>\x0c</table>
91
+ #errors
92
+ (1,1): expected-doctype-but-got-start-tag
93
+
94
+ #document
95
+ | <html>
96
+ | <head>
97
+ | <body>
98
+ | <table>
99
+ | " "
100
+
101
+ #data
102
+ <!doctype html>\x00
103
+ #errors
104
+ (1,16): unexpected-null-character
105
+
106
+ #document
107
+ | <!DOCTYPE html>
108
+ | <html>
109
+ | <head>
110
+ | <body>
111
+
112
+
113
+ #data
114
+ <!--a\x00b-->
115
+ #errors
116
+ (1,1): expected-doctype-but-got-chars
117
+ (1,6): unexpected-null-character
118
+ #document
119
+ | <!-- a�b -->
120
+ | <html>
121
+ | <head>
122
+ | <body>
123
+
124
+ #data
125
+ <frameset>\x0d\x0a
126
+ #errors
127
+ #document
128
+ | <html>
129
+ | <head>
130
+ | <frameset>
131
+ | "
132
+ "
133
+ #data
134
+ <!--a-
135
+ #errors
136
+ (1,1): expected-doctype-but-got-chars
137
+ (1,6): eof-in-comment
138
+ #document
139
+ | <!-- a -->
140
+ | <html>
141
+ | <head>
142
+ | <body>
143
+
144
+ #data
145
+ <!x\x0d\x0ay>
146
+ #errors
147
+ (1,1): expected-doctype-but-got-chars
148
+ #document
149
+ | <!-- x
150
+ y -->
151
+ | <html>
152
+ | <head>
153
+ | <body>
154
+
155
+ #data
156
+ <\x00div>
157
+ #errors
158
+ (1,1): expected-doctype-but-got-chars
159
+ (1,2): unexpected-null-character
160
+ #document
161
+ | <html>
162
+ | <head>
163
+ | <body>
164
+ | "<�div>"
165
+
166
+ #data
167
+ </>
168
+ #errors
169
+ (1,1): expected-doctype-but-got-chars
170
+ #document
171
+ | <html>
172
+ | <head>
173
+ | <body>
174
+
175
+ #data
176
+ \x00\x0cA
177
+ #errors
178
+ (1,1): unexpected-null-character
179
+ (1,1): expected-doctype-but-got-chars
180
+ #document
181
+ | <html>
182
+ | <head>
183
+ | <body>
184
+ | "A"
185
+
186
+ #data
187
+ <!DOCTYPE\x00html>
188
+ #errors
189
+ (1,10): unexpected-null-character
190
+ (1,15): unknown-doctype
191
+ #document
192
+ | <!DOCTYPE �html>
193
+ | <html>
194
+ | <head>
195
+ | <body>
196
+
197
+ #data
198
+ &#x;
199
+ #errors
200
+ (1,1): expected-doctype-but-got-chars
201
+ #document
202
+ | <html>
203
+ | <head>
204
+ | <body>
205
+ | "&#x;"
206
+
207
+ #data
208
+ <!--first--><!DOCTYPE\x0d\x0ahtml>
209
+ #errors
210
+ (1,1): expected-doctype-but-got-chars
211
+ #document
212
+ | <!-- first -->
213
+ | <!DOCTYPE html>
214
+ | <html>
215
+ | <head>
216
+ | <body>
217
+
218
+ #data
219
+ <o\x0ce="\x0d">
220
+ #errors
221
+ (1,9): expected-doctype-but-got-start-tag
222
+ #document
223
+ | <html>
224
+ | <head>
225
+ | <body>
226
+ | <o>
227
+ | e="
228
+ "
229
+
230
+ #data
231
+ <d 2="\x0d">
232
+ #errors
233
+ (1,9): expected-doctype-but-got-start-tag
234
+ #document
235
+ | <html>
236
+ | <head>
237
+ | <body>
238
+ | <d>
239
+ | 2="
240
+ "
241
+
242
+ #data
243
+ <a 8=&#13\x0d>
244
+ #errors
245
+ (1,11): expected-doctype-but-got-start-tag
246
+ #document
247
+ | <html>
248
+ | <head>
249
+ | <body>
250
+ | <a>
251
+ | 8="
252
+ "