WordPress HTML API(6.2 起引入,后续持续扩展)的正确用法是:不要再用正则改 HTML 。用 WP_HTML_Tag_Processor 定位标签、读写属性、增删 class 、替换文本,最后 get_updated_html() 拿结果。它是按 HTML5 规范实现的解析器,能正确处理注释、 script/textarea 里的伪标签、单引号属性、布尔属性这些正则必踩的坑。
正则为什么一定会出错
最经典的例子是给图片加 loading="lazy"。第一版正则 ~<img(.*)>~ 会因为贪婪匹配把属性加到最后一个标签上;改成非贪婪 .*? 后,遇到 <img title="bears > tigers"> 这种属性值里带尖括号的写法就会把标记切烂;继续修,又会漏掉单引号属性、无值属性、以及已经存在 loading 属性的情况。修到第四版,正则已经没人看得懂,边界仍然不全。
更麻烦的是语义:<textarea> 里的 <img> 是纯文本不是标签,<script> 里的字符串同理。正则永远分不清这个区别,解析器可以。
同样的需求用 HTML API 一次到位:
$processor = new WP_HTML_Tag_Processor( $html );
if ( $processor->next_tag( 'img' ) ) {
$processor->set_attribute( 'loading', 'lazy' );
}
return $processor->get_updated_html();
三步走:创建 → 查找 → 改
1. 查找标签
next_tag() 移动内部游标到下一个符合条件的标签,支持多种查询写法:
$tags = new WP_HTML_Tag_Processor( $html );
$tags->next_tag(); // 下一个任意标签
$tags->next_tag( 'img' ); // 下一个 img
$tags->next_tag( array( 'class_name' => 'fullwidth' ) ); // 带指定 class 的标签
$tags->next_tag( array( 'tag_name' => 'img', 'class_name' => 'fullwidth' ) );
两条重要的行为规则:
- 返回
false时游标已走到文档末尾,不能回退,想重新扫描必须新建实例——唯一的例外是书签。 - 查询语法不够用时,可以用
get_tag()/get_attribute()在循环里自行过滤,做自定义查询。
2. 读写属性
set_attribute() 会自动完成所有 HTML 编码,你传未转义的原始值即可:传 'Eggs & Milk' 浏览器看到的就是 Eggs & Milk,不会二次转义成 &。
// 布尔属性:true 只加属性名,false 移除该属性
$processor->set_attribute( 'selected', true );
$processor->set_attribute( 'selected', false );
// class 有专用方法,别直接 set_attribute( 'class', ... )
$processor->add_class( 'is-active' );
$processor->remove_class( 'old-class' );
get_attribute() 的返回值要分清三种情况:属性不存在返回 null,布尔属性返回 true,属性存在但值为空返回空字符串 ''。判断时别只用 empty(),否则会把布尔属性和空值混为一谈。
3. 取回结果
所有修改都是先入队(lexical updates),最后统一由 get_updated_html() 输出。中间不产生字符串拷贝,性能也更好。
书签:唯一能「回头」的机制
默认不能回退,但可以先 set_bookmark() 再 seek() 跳回来。典型场景是「改完子元素后给父容器加属性」:
$processor = new WP_HTML_Tag_Processor( $block_content );
if ( ! $processor->next_tag() ) {
return $block_content;
}
$processor->set_bookmark( 'parent' );
while ( $processor->next_tag( array( 'tag_name' => 'span', 'class_name' => 'word-switcher' ) ) ) {
$processor->set_attribute( 'data-wp-text', 'state.currentWord' );
}
$processor->seek( 'parent' );
$processor->set_attribute( 'data-wp-interactive', 'my-plugin/word-switcher' );
$processor->set_attribute( 'data-wp-context', wp_json_encode( array( 'index' => 0 ) ) );
return $processor->get_updated_html();
这也是 HTML API 与 Interactivity API 配合的标准姿势:用解析器给已有标记注入 data-wp-* 指令,无需手写正则替换。
改文本内容:set_modifiable_text()
除了属性,还能改节点的文本内容。next_token() 配合 get_modifiable_text() / set_modifiable_text() 可以遍历文本节点:
while ( $processor->next_token() ) {
if ( '#text' !== $processor->get_token_name() ) {
continue;
}
$chunk = $processor->get_modifiable_text();
if ( ! str_contains( $chunk, ':)' ) ) {
continue;
}
$processor->set_modifiable_text( str_replace( ':)', '🙂', $chunk ) );
}
它同样负责编码,并且对 SCRIPT 、 STYLE 、 TEXTAREA 这类特殊元素有保护逻辑:不允许写入会提前闭合标签的内容,防止结构被破坏。写失败时返回 false,务必接住返回值。
能力边界:Tag Processor 改不了什么
WP_HTML_Tag_Processor 处理的是「标签与属性」这一层:它不理解 DOM 树结构,不能插入新标签、不能删除标签、不能包裹元素。需要改结构请用更高层的 WP_HTML_Processor,它维护完整的树信息,支持在特定深度插入/删除节点。
选择建议:只改属性、 class 、文本 → Tag Processor,够用且更快;要动结构 → HTML Processor 。
典型落地场景
- 批量加性能优化属性:给图片加
loading="lazy"、给外链加rel="noopener"、给 iframe 加懒加载。 - 在
render_block过滤器里改输出:这是最推荐的接入点,比过滤the_content精准得多,也不会误伤短代码输出。 - 给核心区块注入交互指令:配合 Interactivity API,为已有区块加上前端行为而不复制其渲染逻辑。
- 内容迁移与清洗:批量规范化旧文章的标记属性。










评论 (0)