NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Packagist
HTML component for WordPress.
Last release 2 months ago
07 Aug 2026
Release timing varies
gaps range from 2 weeks to 7 months
Some releases are documented
notes for 12 of 43 stable releases
Nothing withdrawn
no release was ever pulled
1 years old
52 releases · first in 2025
One column per month.
Renames the four relocated PHP* implementation classes to their public names and removes the temporary loaders introduced by #304.
Renames the four relocated PHP* implementation classes to their public
names and removes the temporary loaders introduced by #304.
With #303 merged, Files changed contains only the class declaration
cleanup and temporary-loader removal. The resulting tree contains
neither the retired PHP* class names nor the PHP/ and loaders/
directories.
The Data Liberation, XML, and Markdown suites pass. PHPCS and syntax
checks pass. A root-autoload check confirms the public classes load, the
retired PHP* classes do not, and WP_HTML_Processor remains a
WP_HTML_Tag_Processor subclass.
Renames the four relocated PHP* implementation classes to their public names and removes the temporary loaders introduced by #304.
Renames the four relocated PHP* implementation classes to their public
names and removes the temporary loaders introduced by #304.
With #303 merged, Files changed contains only the class declaration
cleanup and temporary-loader removal. The resulting tree contains
neither the retired PHP* class names nor the PHP/ and loaders/
directories.
The Data Liberation, XML, and Markdown suites pass. PHPCS and syntax
checks pass. A root-autoload check confirms the public classes load, the
retired PHP* classes do not, and WP_HTML_Processor remains a
WP_HTML_Tag_Processor subclass.
Renames the four relocated PHP* implementation classes to their public names and removes the temporary loaders introduced by #304.
Renames the four relocated PHP* implementation classes to their public
names and removes the temporary loaders introduced by #304.
With #303 merged, Files changed contains only the class declaration
cleanup and temporary-loader removal. The resulting tree contains
neither the retired PHP* class names nor the PHP/ and loaders/
directories.
The Data Liberation, XML, and Markdown suites pass. PHPCS and syntax
checks pass. A root-autoload check confirms the public classes load, the
retired PHP* classes do not, and WP_HTML_Processor remains a
WP_HTML_Tag_Processor subclass.
Expose PHP fallback APIs from native wrappers
Expose PHP fallback APIs from native wrappers
Expose PHP fallback APIs from native wrappers
Expose PHP fallback APIs from native wrappers
Expose PHP fallback APIs from native wrappers
Expose PHP fallback APIs from native wrappers
Expose PHP fallback APIs from native wrappers
Expose PHP fallback APIs from native wrappers
Fix native wrapper autoload without extension
Fix native wrapper autoload without extension
Changes the public PHP classes to progressively use the native extension with no API-consumer burden.
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_Processor and WP_HTML_Processor load either |
| empty native adapter subclasses or the moved PHP classes. | |
| XML | XMLProcessor loads either NativeXMLProcessor directly or |
PHPXMLProcessor. The old cursor bridge is reduced to an empty native |
|
| subclass. | |
| URL-in-text | URLInTextProcessor loads either the native class |
through 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 lines
components/HTML/class-wp-html-native-processor-wrapper.php: 9 linescomponents/HTML/class-wp-html-native-tag-processor-wrapper.php: 9components/XML/class-xmlnativecursorprocessor.php: 5 linesThe 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.
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-native still fails if any
native implementation row is unavailable.
Benchmark run:
https://github.com/WordPress/php-toolkit/actions/runs/25988013187
Command:
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-nativeRepresentative PHP-fallback vs native rows:
| Workload | PHP wall (s) | Native wall (s) | Speedup |
|---|---|---|---|
html-tag-processor |
0.240940 | 0.035885 | 6.71x |
html-processor |
1.113778 | 0.135704 | 8.21x |
xml-processor |
0.332820 | 0.063290 | 5.26x |
url-in-text-processor |
3.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-native enabled.
Local checks run for the benchmark harness fixes:
php -l bin/benchmark-native-apis.phpPreviously 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 --checkGitHub Actions is the authoritative extension build/load check for this
PR because this local machine does not have php-config.
Moves the existing public API implementation files to *php* sidecar filenames with no line changes.
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.
This gives reviewers a clean baseline where GitHub shows the large
implementation files as renames before #271 adds the native integration
and loader changes.
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.phpThe class names are deliberately unchanged in this PR so the diff
remains move-only.
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 --checkThe 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 o
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.
| 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.php |
bin/run-snippets.py (230 lines) |
bin/run-snippets.php |
bin/serve-docs.py (50 lines) |
bin/serve-docs.php (uses php -S |
| with router) |
| Surface | Parser |
|---|---|
README YAML frontmatter (slug, title, install, credit_*, |
|
see_also) |
Webuni\FrontMatter\FrontMatter::parse() — already |
vendored under components/Markdown/vendor-patched/ for the Markdown |
|
| component. 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 = Heading nodes at level 2. |
|
Snippets = HtmlBlock (<!-- snippet: -->) → FencedCode (info=php) |
|
tuples. Expected-output = HtmlBlock (<!-- expected-output -->) → |
|
FencedCode pair. Body content rendered via |
|
HtmlRenderer::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 #text node, classifies, strips the Footgun: / |
|
Gotcha: prefix via set_modifiable_text(), then slices off the outer |
|
<p>...</p> by length (no regex). |
|
Lede paragraph → inline HTML (no outer <p>) |
Render the lede |
Paragraph node's inline children directly via |
|
HtmlRenderer::renderNodes() instead of slicing afterward. |
|
| Snippet metadata comment (`<!-- snippet: filename: x.php\nrunnable: | |
| true\n-->`) | String slicing of literal <!-- / --> delimiters — no |
| regex. | |
--update writing captured stdout back into a README |
**CommonMark |
| AST** locates the snippet's exact line range; line-by-line splice. No | |
| regex over the README. |
docs/reference/*.html render correctly: 9 <php-snippet> + 9html.html, 4 pitfall callouts--update verified end-to-end:--update inserts asee_also is 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 | reasonWebuni\FrontMatter\FrontMatter correctly types it as a sequence; any
frontmatter-aware tool reading the README sees the same shape. All 18
component READMEs migrated.
snippet-tests.yml drops the actions/setup-python step.docs.yml swaps python3 bin/build-reference.py for php bin/build-reference.php.preg_* calls (all on plain text, not HTML)slugify() — heading text → URL-safe slug.normalize() — scrubs noise from snippet stdout (tempfile paths, gitrequire '...autoload.php'; line in the snippet's PHP source to inject theThese operate on plain strings, not HTML, so they're not what the "no
regex over HTML" rule was about.
Verify docs snippets workflow passes (87/87).Deploy docs to GitHub Pages runs cleanly on push to trunk.bash bin/build-docs-bundle.sh && php bin/serve-docs.php — http://localhost:8787 renders all reference pagesphp bin/run-snippets.php --update (no-op, no drift) leaves allThe 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 o
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.
| 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.php |
bin/run-snippets.py (230 lines) |
bin/run-snippets.php |
bin/serve-docs.py (50 lines) |
bin/serve-docs.php (uses php -S |
| with router) |
| Surface | Parser |
|---|---|
README YAML frontmatter (slug, title, install, credit_*, |
|
see_also) |
Webuni\FrontMatter\FrontMatter::parse() — already |
vendored under components/Markdown/vendor-patched/ for the Markdown |
|
| component. 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 = Heading nodes at level 2. |
|
Snippets = HtmlBlock (<!-- snippet: -->) → FencedCode (info=php) |
|
tuples. Expected-output = HtmlBlock (<!-- expected-output -->) → |
|
FencedCode pair. Body content rendered via |
|
HtmlRenderer::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 #text node, classifies, strips the Footgun: / |
|
Gotcha: prefix via set_modifiable_text(), then slices off the outer |
|
<p>...</p> by length (no regex). |
|
Lede paragraph → inline HTML (no outer <p>) |
Render the lede |
Paragraph node's inline children directly via |
|
HtmlRenderer::renderNodes() instead of slicing afterward. |
|
| Snippet metadata comment (`<!-- snippet: filename: x.php\nrunnable: | |
| true\n-->`) | String slicing of literal <!-- / --> delimiters — no |
| regex. | |
--update writing captured stdout back into a README |
**CommonMark |
| AST** locates the snippet's exact line range; line-by-line splice. No | |
| regex over the README. |
docs/reference/*.html render correctly: 9 <php-snippet> + 9html.html, 4 pitfall callouts--update verified end-to-end:--update inserts asee_also is 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 | reasonWebuni\FrontMatter\FrontMatter correctly types it as a sequence; any
frontmatter-aware tool reading the README sees the same shape. All 18
component READMEs migrated.
snippet-tests.yml drops the actions/setup-python step.docs.yml swaps python3 bin/build-reference.py for php bin/build-reference.php.preg_* calls (all on plain text, not HTML)slugify() — heading text → URL-safe slug.normalize() — scrubs noise from snippet stdout (tempfile paths, gitrequire '...autoload.php'; line in the snippet's PHP source to inject theThese operate on plain strings, not HTML, so they're not what the "no
regex over HTML" rule was about.
Verify docs snippets workflow passes (87/87).Deploy docs to GitHub Pages runs cleanly on push to trunk.bash bin/build-docs-bundle.sh && php bin/serve-docs.php — http://localhost:8787 renders all reference pagesphp bin/run-snippets.php --update (no-op, no drift) leaves allNothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →