标签保留 id 和 class 属性,而移除 href、title、onclick、style 等潜在风险或冗余属性。此时,绝不可依赖正则表达式(preg_replace)处理HTML结构:HTML语法灵活多变(大小写混用、引号类型多样、布尔属性无值、属性值内含转义符号、data-* 属性干扰等),正则极易误匹配、漏删或破坏结构,导致输出不合法甚至XSS漏洞。推荐方案是使用 PHP 原生的 DOMDocument —— 它能真正解析HTML为标准DOM树,确保语义准确、容错性强。以下为完整、健壮的实现:
$html = '<div id="one-id" class="someClassName">Some text <a href="#" title="Words" id="linkId" class="classLink" data-ignored="123">link</a> with only the class and id attributes.</div>';
$dom = new DOMDocument();
// 关键:禁用自动添加包装,避免污染原始结构
$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
// 遍历所有元素节点(* 匹配任意标签)
foreach ($dom->getElementsByTagName('*') as $node) {
// 从后往前遍历属性列表(因removeAttribute会改变索引,倒序可安全删除)
for ($i = $node->attributes->length - 1; $i >= 0; $i--) {
$attr = $node->attributes->item($i);
// 白名单检查:仅保留 'id' 和 'class',其余一律移除
if (!in_array($attr->name, ['id', 'class'], true)) {
$node->removeAttribute($attr->name);
}
}
}
// 输出净化后的HTML(无多余换行/缩进,如需美化可用saveHTMLFile + tidy或自定义格式化)
echo $dom->saveHTML();
✅ 输出结果:
<div id="one-id" class="someClassName">Some text <a id="linkId" class="classLink">link</a> with only the class and id attributes.</div>
关键注意事项:
- 使用 LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD 参数至关重要,它阻止 DOMDocument 自动注入 、 和 ,保持原始片段结构;
- 属性遍历必须逆序($i--),因为 removeAttribute() 会实时改变 attributes 集合长度和索引,正序遍历将跳过相邻属性;
- in_array(..., true) 启用严格比较,避免 'ID' 或 'Id' 被误判(如需支持大小写不敏感,可统一转小写后再比对);
- 若需扩展白名单(如增加 data-* 属性),可改用正则匹配:if (!preg_match('/^(id|class|data-[a-z0-9_-]+)$/i', $attr->name));
- DOMDocument::saveHTML() 默认输出编码为 ISO-8859-1,若原文含 UTF-8 特殊字符,建议在 loadHTML() 前添加 或使用 mb_convert_encoding() 预处理。
该方法兼具准确性、可维护性与安全性,是HTML属性白名单过滤的标准实践。