wp-php-toolkit/html
HTML component for WordPress.
v0.9.0
297K downloads/mo
#2989 most downloaded on Packagist
wp-php-toolkit/html
What this package is like to depend on
Last release 3 months ago
18 May 2026
Release timing varies
gaps range from 2 weeks to 7 months
Rarely documented
notes for 9 of 40 stable releases
Nothing withdrawn
no release was ever pulled
1 years old
49 releases · first in 2025
18 releases in the last 12 months
see the full history below
Release timeline
19 releases · May 2025 to May 2026Releases
latest 49-
v0.9.018 May 2026 -
v0.8.118 May 2026 -
v0.8.0no date -
v0.7.9no date -
v0.7.818 May 2026 -
v0.7.717 May 2026Release notes
Open source →What it does
Changes the public PHP classes to progressively use the native extension
with no API-consumer burden.When the extension is installed and the native class advertises
supports_public_api(), the public PHP class resolves to the native
implementation. Otherwise it falls back to the moved PHP implementation.Implemented wiring:
Component PHP integration in this PR HTML WP_HTML_Tag_ProcessorandWP_HTML_Processorload eitherempty native adapter subclasses or the moved PHP classes. XML XMLProcessorloads eitherNativeXMLProcessordirectly orPHPXMLProcessor. The old cursor bridge is reduced to an empty nativesubclass. URL-in-text URLInTextProcessorloads either the native classthrough an empty adapter or PHPURLInTextProcessor.Data Liberation HTML Keeps the public subclass usable with the same native/PHP processor selection model instead of forcing the PHP path. The native wrapper files are intentionally tiny:
components/DataLiberation/URL/class-nativeurlintextprocessorwrapper.php:
5 linescomponents/HTML/class-wp-html-native-processor-wrapper.php: 9 linescomponents/HTML/class-wp-html-native-tag-processor-wrapper.php: 9
linescomponents/XML/class-xmlnativecursorprocessor.php: 5 lines
Rationale
The public class names should remain stable for consumers. Installing
the native extension should be a progressive upgrade, not a new API they
have to opt into.This PR keeps PHP responsible for loading and fallback only. Parser
behavior belongs to the Rust classes already merged from #276 or to the
moved PHP fallback classes.Implementation
The moved PHP implementations live under component
PHP/paths. Public
loader files choose the native class only when all of these are true:! defined( 'WP_NATIVE_APIS_DISABLE_DEFAULTS' ) || ! WP_NATIVE_APIS_DISABLE_DEFAULTS class_exists( $native_class, false ) method_exists( $native_class, 'supports_public_api' ) $native_class::supports_public_api()
There are no per-component
WP_NATIVE_APIS_ENABLE_*switches. Tests and
benchmarks can use the single global opt-out constant when they need to
force fallback behavior.The benchmark harness now tolerates unsupported PHP fallback rows
instead of aborting the run, while--require-nativestill fails if any
native implementation row is unavailable.Benchmarks
Benchmark run:
https://github.com/WordPress/php-toolkit/actions/runs/25988013187Command:
php -d extension=extensions/native-apis/target/release/libwp_native_apis.so \ bin/benchmark-native-apis.php \ --iterations=100 \ --mode=both \ --disable-native-defaults \ --require-native
Representative PHP-fallback vs native rows:
Workload PHP wall (s) Native wall (s) Speedup html-tag-processor0.240940 0.035885 6.71x html-processor1.113778 0.135704 8.21x xml-processor0.332820 0.063290 5.26x url-in-text-processor3.853847 0.030515 126.29x The same run also completed all native fused/chunked benchmark rows for
HTML, XML, and URL-in-text with--require-nativeenabled.Testing instructions
Local checks run for the benchmark harness fixes:
php -l bin/benchmark-native-apis.php
Previously run locally before the #276 rebase:
php -l components/HTML/class-wp-html-native-tag-processor-wrapper.php php -l components/HTML/class-wp-html-native-processor-wrapper.php php -l components/DataLiberation/URL/class-nativeurlintextprocessorwrapper.php php -l components/XML/class-xmlnativecursorprocessor.php php -l components/XML/class-xmlprocessor.php php -l components/HTML/class-wp-html-tag-processor.php php -l components/HTML/class-wp-html-processor.php php -l components/DataLiberation/URL/class-urlintextprocessor.php vendor/bin/phpunit components/HTML/Tests/NativeHTMLConformanceTest.php vendor/bin/phpunit components/XML/Tests/NativeXMLConformanceTest.php vendor/bin/phpunit components/DataLiberation/Tests/URLInTextProcessorTest.php components/DataLiberation/Tests/URLInTextProcessorWHATWGComplianceTest.php components/DataLiberation/Tests/WPURLTest.php vendor/bin/phpcs --standard=phpcs.xml components/DataLiberation/URL/class-nativeurlintextprocessorwrapper.php components/DataLiberation/URL/class-urlintextprocessor.php components/HTML/class-wp-html-native-processor-wrapper.php components/HTML/class-wp-html-native-tag-processor-wrapper.php components/HTML/class-wp-html-processor.php components/HTML/class-wp-html-tag-processor.php components/XML/class-xmlnativecursorprocessor.php components/XML/class-xmlprocessor.php components/HTML/Tests/NativeHTMLConformanceTest.php components/XML/Tests/NativeXMLConformanceTest.php git diff --check
GitHub Actions is the authoritative extension build/load check for this
PR because this local machine does not havephp-config. -
v0.7.616 May 2026Release notes
Open source →What it does
Moves the existing public API implementation files to
*php*sidecar
filenames with no line changes.This is intentionally only a file-move PR. It does not add public
loaders, native wrapper classes, or behavior changes; those stay in the
stacked PR #271.Rationale
This gives reviewers a clean baseline where GitHub shows the large
implementation files as renames before #271 adds the native integration
and loader changes.Implementation
Moved these files at 100% similarity:
components/HTML/class-wp-html-tag-processor.php->
components/HTML/class-wp-html-php-tag-processor.phpcomponents/HTML/class-wp-html-processor.php->
components/HTML/class-wp-html-php-processor.phpcomponents/XML/class-xmlprocessor.php->
components/XML/class-phpxmlprocessor.phpcomponents/DataLiberation/URL/class-urlintextprocessor.php->
components/DataLiberation/URL/class-phpurlintextprocessor.php
The class names are deliberately unchanged in this PR so the diff
remains move-only.Testing instructions
composer dump-autoload php -l components/HTML/class-wp-html-php-tag-processor.php components/HTML/class-wp-html-php-processor.php components/XML/class-phpxmlprocessor.php components/DataLiberation/URL/class-phpurlintextprocessor.php vendor/bin/phpunit components/XML/Tests/ vendor/bin/phpunit components/DataLiberation/Tests/URLInTextProcessorTest.php vendor/bin/phpunit components/DataLiberation/Tests/MarkupProcessorConsumerTest.php components/DataLiberation/Tests/HTMLEntityReaderTest.php vendor/bin/phpunit components/Markdown/Tests/MarkdownConsumerTest.php git diff origin/trunk...HEAD --check
-
v0.7.503 May 2026Release notes
Open source →Summary
The toolkit now documents itself using only its own runtime and its
own structured-data parsers. No Python in CI; no hand-rolled regex over
markdown or HTML.What changed
Was Now bin/_docs_components.py(200 lines, dead-code dicts)deleted bin/_load_catalog.py(370 lines: hand-rolled YAML-subset parser,regex section splitter, regex snippet extractor) replaced with proper parsers bin/build-reference.py(220 lines)bin/build-reference.phpbin/run-snippets.py(230 lines)bin/run-snippets.phpbin/serve-docs.py(50 lines)bin/serve-docs.php(usesphp -Swith router) Parsers
Surface Parser README YAML frontmatter ( slug,title,install,credit_*,see_also)Webuni\FrontMatter\FrontMatter::parse()— alreadyvendored under components/Markdown/vendor-patched/for the Markdowncomponent. Single-line, multi-line `key: `, and YAML lists all behave correctly. The same parser GitHub uses for the README's frontmatter table. Markdown body → AST League\CommonMark\Parser\MarkdownParser+walking the document. Section boundaries = Headingnodes at level 2.Snippets = HtmlBlock(<!-- snippet: -->) →FencedCode(info=php)tuples. Expected-output = HtmlBlock(<!-- expected-output -->) →FencedCodepair. Body content rendered viaHtmlRenderer::renderNodes()so raw HTML round-trips verbatim.Pitfall callouts (HtmlBlocks of the form <p>Footgun: …</p>)WP_HTML_Tag_Processor— walks tokens, confirms a<p>opener,finds the first inner #textnode, classifies, strips theFootgun:/Gotcha:prefix viaset_modifiable_text(), then slices off the outer<p>...</p>by length (no regex).Lede paragraph → inline HTML (no outer <p>)Render the lede Paragraphnode's inline children directly viaHtmlRenderer::renderNodes()instead of slicing afterward.Snippet metadata comment (`<!-- snippet: filename: x.php\nrunnable: true\n-->`) String slicing of literal <!--/-->delimiters — noregex. --updatewriting captured stdout back into a README**CommonMark AST** locates the snippet's exact line range; line-by-line splice. No regex over the README. Behavioural parity
- 87/87 snippets match their captured stdout. The PHP normalizer
mirrors the Python regex set 1:1, so existing expected-output blocks
stay valid. docs/reference/*.htmlrender correctly: 9<php-snippet>+ 9
fallback + 9 expected-output triples onhtml.html, 4 pitfall callouts
(the bold-lead pattern is preserved), see-also list intact.--updateverified end-to-end:- When an expected-output block has drifted, the rewritten content is
byte-for-byte the original. - When no expected-output exists for a snippet,
--updateinserts a
fresh block in the right place; resulting README is byte-identical to
one with the block authored manually.
Frontmatter format change:
see_alsois a proper YAML list# Before (repeated keys — not standard YAML) see_also: a | A | reason see_also: b | B | reason # After see_also: - a | A | reason - b | B | reason
Webuni\FrontMatter\FrontMattercorrectly types it as a sequence; any
frontmatter-aware tool reading the README sees the same shape. All 18
component READMEs migrated.Workflows simplified
snippet-tests.ymldrops theactions/setup-pythonstep.docs.ymlswapspython3 bin/build-reference.pyforphp bin/build-reference.php.
Remaining
preg_*calls (all on plain text, not HTML)slugify()— heading text → URL-safe slug.normalize()— scrubs noise from snippet stdout (tempfile paths, git
hashes, timestamps).- One pattern in run-snippets that matches the
require '...autoload.php';line in the snippet's PHP source to inject the
local-prelude polyfill.
These operate on plain strings, not HTML, so they're not what the "no
regex over HTML" rule was about.Test plan
-
Verify docs snippetsworkflow passes (87/87). -
Deploy docs to GitHub Pagesruns cleanly on push to trunk. - Local preview:
bash bin/build-docs-bundle.sh && php bin/serve-docs.php— http://localhost:8787 renders all reference pages
with snippets. -
php bin/run-snippets.php --update(no-op, no drift) leaves all
READMEs untouched.
- 87/87 snippets match their captured stdout. The PHP normalizer
-
v0.7.4no dateRelease notes
Open source →Summary
The toolkit now documents itself using only its own runtime and its
own structured-data parsers. No Python in CI; no hand-rolled regex over
markdown or HTML.What changed
Was Now bin/_docs_components.py(200 lines, dead-code dicts)deleted bin/_load_catalog.py(370 lines: hand-rolled YAML-subset parser,regex section splitter, regex snippet extractor) replaced with proper parsers bin/build-reference.py(220 lines)bin/build-reference.phpbin/run-snippets.py(230 lines)bin/run-snippets.phpbin/serve-docs.py(50 lines)bin/serve-docs.php(usesphp -Swith router) Parsers
Surface Parser README YAML frontmatter ( slug,title,install,credit_*,see_also)Webuni\FrontMatter\FrontMatter::parse()— alreadyvendored under components/Markdown/vendor-patched/for the Markdowncomponent. Single-line, multi-line `key: `, and YAML lists all behave correctly. The same parser GitHub uses for the README's frontmatter table. Markdown body → AST League\CommonMark\Parser\MarkdownParser+walking the document. Section boundaries = Headingnodes at level 2.Snippets = HtmlBlock(<!-- snippet: -->) →FencedCode(info=php)tuples. Expected-output = HtmlBlock(<!-- expected-output -->) →FencedCodepair. Body content rendered viaHtmlRenderer::renderNodes()so raw HTML round-trips verbatim.Pitfall callouts (HtmlBlocks of the form <p>Footgun: …</p>)WP_HTML_Tag_Processor— walks tokens, confirms a<p>opener,finds the first inner #textnode, classifies, strips theFootgun:/Gotcha:prefix viaset_modifiable_text(), then slices off the outer<p>...</p>by length (no regex).Lede paragraph → inline HTML (no outer <p>)Render the lede Paragraphnode's inline children directly viaHtmlRenderer::renderNodes()instead of slicing afterward.Snippet metadata comment (`<!-- snippet: filename: x.php\nrunnable: true\n-->`) String slicing of literal <!--/-->delimiters — noregex. --updatewriting captured stdout back into a README**CommonMark AST** locates the snippet's exact line range; line-by-line splice. No regex over the README. Behavioural parity
- 87/87 snippets match their captured stdout. The PHP normalizer
mirrors the Python regex set 1:1, so existing expected-output blocks
stay valid. docs/reference/*.htmlrender correctly: 9<php-snippet>+ 9
fallback + 9 expected-output triples onhtml.html, 4 pitfall callouts
(the bold-lead pattern is preserved), see-also list intact.--updateverified end-to-end:- When an expected-output block has drifted, the rewritten content is
byte-for-byte the original. - When no expected-output exists for a snippet,
--updateinserts a
fresh block in the right place; resulting README is byte-identical to
one with the block authored manually.
Frontmatter format change:
see_alsois a proper YAML list# Before (repeated keys — not standard YAML) see_also: a | A | reason see_also: b | B | reason # After see_also: - a | A | reason - b | B | reason
Webuni\FrontMatter\FrontMattercorrectly types it as a sequence; any
frontmatter-aware tool reading the README sees the same shape. All 18
component READMEs migrated.Workflows simplified
snippet-tests.ymldrops theactions/setup-pythonstep.docs.ymlswapspython3 bin/build-reference.pyforphp bin/build-reference.php.
Remaining
preg_*calls (all on plain text, not HTML)slugify()— heading text → URL-safe slug.normalize()— scrubs noise from snippet stdout (tempfile paths, git
hashes, timestamps).- One pattern in run-snippets that matches the
require '...autoload.php';line in the snippet's PHP source to inject the
local-prelude polyfill.
These operate on plain strings, not HTML, so they're not what the "no
regex over HTML" rule was about.Test plan
-
Verify docs snippetsworkflow passes (87/87). -
Deploy docs to GitHub Pagesruns cleanly on push to trunk. - Local preview:
bash bin/build-docs-bundle.sh && php bin/serve-docs.php— http://localhost:8787 renders all reference pages
with snippets. -
php bin/run-snippets.php --update(no-op, no drift) leaves all
READMEs untouched.
- 87/87 snippets match their captured stdout. The PHP normalizer
-
v0.7.3no dateNothing published for this version
-
v0.7.230 Apr 2026Nothing published for this version
-
v0.7.130 Apr 2026Nothing published for this version
-
v0.7.029 Apr 2026Nothing published for this version
-
v0.6.210 Apr 2026Nothing published for this version
-
v0.6.1no dateNothing published for this version
-
v0.6.0no dateNothing published for this version
-
v0.5.108 Sep 2025Nothing published for this version
-
v0.5.0no dateNothing published for this version
-
v0.4.1no dateNothing published for this version
-
v0.4.0no dateNothing published for this version
-
v0.3.1no dateNothing published for this version
-
v0.3.0no dateNothing published for this version
-
v0.2.0no dateNothing published for this version
-
v0.1.5no dateNothing published for this version
-
v0.1.4no dateNothing published for this version
-
v0.1.3no dateNothing published for this version
-
v0.1.208 Sep 2025Nothing published for this version
-
v0.1.1no dateNothing published for this version
-
v0.1.0no dateNothing published for this version
-
0.0.19no dateNothing published for this version
-
0.0.18no dateNothing published for this version
-
0.0.17no dateNothing published for this version
-
0.0.16no dateNothing published for this version
-
0.0.15no dateNothing published for this version
-
v0.0.15-alphano date pre-releaseNothing published for this version
-
0.0.14no dateNothing published for this version
-
0.0.13no dateNothing published for this version
-
0.0.12no dateNothing published for this version
-
0.0.1106 Sep 2025Nothing published for this version
-
v0.0.8-alpha08 Sep 2025 pre-releaseNothing published for this version
-
0.0.706 Sep 2025Nothing published for this version
-
v0.0.7-alpha08 Sep 2025 pre-releaseNothing published for this version
-
0.0.606 Sep 2025Nothing published for this version
-
v0.0.6-alpha08 Sep 2025 pre-releaseNothing published for this version
-
v0.0.5-alpha20 May 2025 pre-releaseNothing published for this version
-
v0.0.4-alphano date pre-releaseNothing published for this version
-
v0.0.3-alphano date pre-releaseNothing published for this version
-
v0.0.2-alphano date pre-releaseNothing published for this version
-
v0.0.1-alphano date pre-releaseNothing published for this version