PHP 修复未正常闭合 HTML 标签的函数实现

Aria
Aria
发布时间:2026-10-16 | 5人浏览过
原创

在使用 PHP 处理或生成 HTML 内容时,常因拼接逻辑疏漏导致标签未正确闭合。本文提供一个实用的 PHP 函数解决方案,支持嵌套和就近两种闭合策略,自动修复如 <p>... 等缺失结束标签的问题。

功能目标与模式说明

该函数的核心目标是确保传入的 HTML 字符串中所有非自闭合标签均拥有匹配的结束标签。为解决复杂嵌套场景下的不确定性,提供了两种主要的修复逻辑:

  1. 嵌套闭合模式(NEST):这是默认行为。它遵循标准的 DOM 树逻辑,当遇到未闭合的开始标签时,会在最后统一补全对应层级结构的结束标签。例如:<body><div>你好</div></body>。
  2. 就近闭合模式(CLOSE):该模式会将每一个独立的开始标签立即配对闭合。例如:对于 <p>你好<p>为什么没有...</p>,它会将其转换为 <p>你好</p><p>为什么没有...</p>,适用于段落频繁中断且无需深层嵌套的场景。

准备工作与安装

无需额外安装扩展,直接将该函数复制到你项目的公共函数库或相关类文件中即可使用。函数依赖基础的 PHP 字符串处理和正则表达式功能,兼容大多数 PHP 版本。

操作步骤

第1步:定义配置参数

调用函数前,需要构建一个包含必要参数的关联数组。主要参数包括待处理的 HTML 源码以及可选的行为选项。

  • $html:必须提供。类型为字符串,即需要进行标签修复的 HTML 内容。
  • $options['tagArray']:可选。在嵌套模式下,指定哪些标签需要触发“就近闭合”逻辑。其余标签仍按标准嵌套规则处理。
  • $options['type']:设置全局模式。值可以是 'NEST'(默认)或 'CLOSE'。若设为 'CLOSE',将忽略 tagArray,对所有标签执行就近闭合。
  • $options['length']:可选。整数类型,用于限制输出结果的字符长度。
  • $options['lowerTag']:布尔值,默认为 true。是否将所有标签名统一转换为小写,以符合标准 HTML/XHTML 规范。
  • $options['XHtmlFix']:布尔值,默认为 true。是否对单标签进行 XHTML 风格修复,即在 <br> 后添加空格和斜杠变为 <br />。

第2步:执行修复并获取结果

将构建好的参数数组传递给 fixHtmlTag() 函数。函数内部会利用正则表达式解析 HTML 片段,维护一个标签栈来判断当前标签状态,并根据设定的模式推入或弹出标签,最终返回修复后的完整字符串。

复制代码 代码如下:
<?php
/**
 * fixHtmlTag
 *
 * HTML标签修复函数,此函数可以修复未正确闭合的 HTML 标签
 *
 * 由于不确定性因素太多,暂时提供两种模式“嵌套闭合模式”和
 * “就近闭合模式”,应该够用了。
 *
 * 这两种模式是我为了解释清楚此函数的实现而创造的两个名词,
 * 只需明白什么意思就行。
 * 1,嵌套闭合模式,NEST,为默认的闭合方式。即 "<body><div>你好"
 * 这样的 html 代码会被修改为 "<body><div>你好</div></body>"
 * 2,就近闭合模式,CLOSE,这种模式会将形如 "<p>你好<p>为什么没有
 * 闭合呢" 的代码修改为 "<p>你好</p><p>为什么没有闭合呢</p>"
 *
 * 在嵌套闭合模式(默认,无需特殊传参)下,可以传入需要就近闭合的
 * 标签名,通过这种方式将类似 "<body><p>你好</p><p>我也好" 转换为
 * "<body><p>你好</p><p>我也好</p></body>"的形式。
 * 传参时索引需要按照如下方式写,不需要修改的设置可以省略
 *
 * $param = array(
 * 'html' => '', //必填
 * 'options' => array(
 * 'tagArray' => array();
 * 'type' => 'NEST',
 * 'length' => null,
 * 'lowerTag' => TRUE,
 * 'XHtmlFix' => TRUE,
 * )
 * );
 * fixHtmlTag($param);
 *
 * 上面索引对应的值含义如下
 * string $html 需要修改的 html 代码
 * array $tagArray 当为嵌套模式时,需要就近闭合的标签数组
 * string $type 模式名,目前支持 NEST 和 CLOSE 两种模式,如果设置为 CLOSE,将会忽略参数 $tagArray 的设置,而全部就近闭合所有标签
 * ini $length 如果希望截断一定长度,可以在此赋值,此长度指的是字符串长度
 * bool $lowerTag 是否将代码中的标签全部转换为小写,默认为 TRUE
 * bool $XHtmlFix 是否处理不符合 XHTML 规范的标签,即将 <br> 转换为 <br />
 *
 * @author IT不倒翁 <itbudaoweng@gmail.com>
 * @version 0.2
 * @link http://yungbo.com IT不倒翁
 * @link http://enenba.com/?post=19 某某
 * @param array $param 数组参数,需要赋予特定的索引
 * @return string $result 经过处理后的 html 代码
 * @since 2012-04-14
*/
function fixHtmlTag($param = array()) {
//参数的默认值
$html = '';
$tagArray = array();
$type = 'NEST';
$length = null;
$lowerTag = TRUE;
$XHtmlFix = TRUE;

//首先获取一维数组,即 $html 和 $options (如果提供了参数)
extract($param);

//如果存在 options,提取相关变量
if (isset($options)) {
extract($options);
}

$result = ''; //最终要返回的 html 代码
$tagStack = array(); //标签栈,用 array_push() 和 array_pop() 模拟实现
$contents = array(); //用来存放 html 标签
$len = 0; //字符串的初始长度

//设置闭合标记 $isClosed,默认为 TRUE, 如果需要就近闭合,成功匹配开始标签后其值为 false,成功闭合后为 true
$isClosed = true;

//将要处理的标签全部转为小写
$tagArray = array_map('strtolower', $tagArray);

//“合法”的单闭合标签
$singleTagArray = array(
'<meta',
'<link',
'<base',
'<br',
'<hr',
'<input',
'<img'
);

//校验匹配模式 $type,默认为 NEST 模式
$type = strtoupper($type);
if (!in_array($type, array('NEST', 'CLOSE'))) {
$type = 'NEST';
}

//以一对 < 和 > 为分隔符,将原 html 标签和标签内的字符串放到数组中
$contents = preg_split("/(<[^>]+?>)/si", $html, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);

foreach ($contents as $tag) {
if ('' == trim($tag)) {
$result .= $tag;
continue;
}

//匹配标准的单闭合标签,如<br />
if (preg_match("/<(\w+)[^\/>]*?\/>/si", $tag)) {
$result .= $tag;
continue;
}

//匹配开始标签,如果是单标签则出栈
else if (preg_match("/<(\w+)[^\/>]*?>/si", $tag, $match)) {
//如果上一个标签没有闭合,并且上一个标签属于就近闭合类型
//则闭合之,上一个标签出栈

//如果标签未闭合
if (false === $isClosed) {
//就近闭合模式,直接就近闭合所有的标签
if ('CLOSE' == $type) {
$result .= '</' . end($tagStack) . '>';
array_pop($tagStack);
}
//默认的嵌套模式,就近闭合参数提供的标签
else {
if (in_array(end($tagStack), $tagArray)) {
$result .= '</' . end($tagStack) . '>';
array_pop($tagStack);
}
}
}

//如果参数 $lowerTag 为 TRUE 则将标签名转为小写
$matchLower = $lowerTag == TRUE ? strtolower($match[1]) : $match[1];

$tag = str_replace('<' . $match[1], '<' . $matchLower, $tag);
//开始新的标签组合
$result .= $tag;
array_push($tagStack, $matchLower);

//如果属于约定的的单标签,则闭合之并出栈
foreach ($singleTagArray as $singleTag) {
if (stripos($tag, $singleTag) !== false) {
if ($XHtmlFix == TRUE) {
$tag = str_replace('>', ' />', $tag);
}
array_pop($tagStack);
}
}

//就近闭合模式,状态变为未闭合
if ('CLOSE' == $type) {
$isClosed = false;
}
//默认的嵌套模式,如果标签位于提供的 $tagArray 里,状态改为未闭合
else {
if (in_array($matchLower, $tagArray)) {
$isClosed = false;
}
}
unset($matchLower);
}

//匹配闭合标签,如果合适则出栈
else if (preg_match("/<\/(\w+)[^\/>]*?>/si", $tag, $match)) {

//如果参数 $lowerTag 为 TRUE 则将标签名转为小写
$matchLower = $lowerTag == TRUE ? strtolower($match[1]) : $match[1];

if (end($tagStack) == $matchLower) {
$isClosed = true; //匹配完成,标签闭合
$tag = str_replace('</' . $match[1], '</' . $matchLower, $tag);
$result .= $tag;
array_pop($tagStack);
}
unset($matchLower);
}

//匹配注释,直接连接 $result
else if (preg_match("/<!--.*?-->/si", $tag)) {
$result .= $tag;
}

//将字符串放入 $result ,顺便做下截断操作
else {
if (is_null($length) || $len + mb_strlen($tag) < $length) {
$result .= $tag;
$len += mb_strlen($tag);
} else {
$str = mb_substr($tag, 0, $length - $len + 1);
$result .= $str;
break;
}
}
}

//如果还有将栈内的未闭合的标签连接到 $result
while (!empty($tagStack)) {
$result .= '</' . array_pop($tagStack) . '>';
}
return $result;
}
?>

结果验证

运行上述代码后,检查返回值是否符合预期。例如,输入 <p>第一段<p>第二段</p> 并在嵌套模式下指定 p 为就近闭合标签,应得到 <p>第一段</p><p>第二段</p></p>(外层 p 被补全)。若切换至关闭模式,所有开启的标签都会在下一次开启同类标签或结尾处立即闭合。

简要总结

通过引入 fixHtmlTag 函数,开发者可以有效解决动态生成 HTML 时常见的标签遗漏问题。利用其灵活的模式切换和 XHTML 兼容性设置,能显著提升页面结构的规范性与稳定性。以上就是 PHP 修复未正常闭合 HTML 标签实现代码的详细内容,更多关于 PHP 前端代码优化的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表