通过 PHP 选择性地替换某些 HTML 标签,同时保留一些

Selectivly replace certain HTML tags via PHP while keeping some

我有一个包含 html 代码的表单,由于我无法禁用 MCE 编辑器的自动格式化功能,该代码偶尔会有点乱。

我已经用 PHP 做了一些简单的替换,但还有一些我不太确定。

删除 ALL <span> 标签,例如 <span style="font-family: inherit; font-weight: inherit; line-height: 1.3;"> 包括它们的 </span> 对应但 NOT这些标签内的内容。

例如:<span style="font-family: inherit; font-weight: inherit; line-height: 1.3;">Whosebug</span> 会变成 Whosebug

唯一应该删除的是看起来像:

<span class="MainLink" style="font-weight: bold"><a href="https://website.com/" style="color: #2f82de; text-decoration: none">link name</a></span>

所以基本上任何封装 <a href... link.

关于如何做到这一点的任何想法,我假设我需要使用正则表达式来做到这一点,但可能有 easier/better 方式。

试试这个:

$output = preg_replace('/<span[^>]*>(?!<a[ >])|(?<!\/a>)<\/span>/', '', $input);

Regex101 Tested

这个正则表达式有 两个部分:

  1. 它会删除后面没有 <a 的任何 <span>
  2. 它会删除前面没有 /a> 的任何 </span>

注意:此解决方案是对问题的快速修复,并假定有效 HTML。在某些情况下这可能无法正常工作,但 OP 可能不会有任何这些情况(例如 span 内的自关闭 a 标签)。请参阅 Regex101,了解 所考虑的场景演示。

要执行此操作,您需要一个解析器,而不是正则表达式(另请参阅 The Famous Answer 关于此)

从这个例子开始 DOMDocumentDOMXpath:

$dom = new DOMDocument();
libxml_use_internal_errors(1);
$dom->formatOutput = True;
$dom->loadHTML( $html );
$xpath = new DOMXPath( $dom );

while( $node = $xpath->query( '//span[not(contains(@class,"MainLink"))]' )->item(0) )
{
    $fragment = $dom->createDocumentFragment();
    while( $node->childNodes->length ) 
    {
        $fragment->appendChild( $node->childNodes->item(0) );
    }
    $node->parentNode->replaceChild( $fragment, $node );
}

echo $dom->saveHTML();

这一行:

while( $node = $xpath->query( '//span[not(contains(@class,"leave"))]' )->item(0) )

你在 class 属性中搜索不包含“leave”的每个 <span> 节点:如果找到此模式,则执行循环 (->item(0))。

然后您创建一个新的 DOMDocumentFragment,一个特殊的临时节点,您可以在其中添加所有子节点:

    while( $node->childNodes->length ) 
    {
        $fragment->appendChild( $node->childNodes->item(0) );
    }

将所有节点子节点移动到新片段后,用片段替换空 <span> 节点。


其他对您有帮助的有用的 XPath:

  • //span[not(a)] : select 所有 <span> 节点后面没有 <a> 子节点;
  • //span[not(contains(@class,"leave")) and not(contains(@class,"yes"))] : select 所有 <span> 节点在 class 属性中没有“离开”或“是”。

已编辑以切换捕获组

我总是觉得做这样的事情真的很棘手,因为往往有太多无法预料的情况需要处理,否则他们会回来咬我。

也就是说,这种正则表达式的挑战通常很有趣。

我可能会尝试这样的事情:

(?:<span[^>]*?>)(?!<a)(.*?)(?:<\/span>)

在此处进行操作:https://regex101.com/r/qY8pL5/3

它所做的是首先尝试匹配 span 标签的开始,并找到开始标签最有可能结束的位置。这被放入一个非捕获组中,因此它可以被丢弃。接下来它确保接下来的两个字符不是锚标记,因为不应剥离包裹锚的跨度。下一部分是一个捕获组,它尽可能懒惰地捕获每个字符,直到它到达结束 span 标记。结束跨度标记也收集在非捕获组中,因此可以将其丢弃。

这将匹配独立的 span 标签和包裹在锚点中的 span 标签。它不会匹配包裹锚点的 span 标签。

在 php 中,您可以这样实现它:

$final_string = preg_replace('/(?:<span[^>]*?>)(?!<a)(.*?)(?:<\/span>)/', '', $string);

第一个参数是我们的正则表达式,第二个参数是我们想要用来替换我们的正则表达式匹配的内容 - 在这种情况下第一个(并且在这种情况下只是)捕获组被保留 - 最后我们通过我们希望匹配的字符串。


请注意,@fusion3k 是迄今为止最好的答案,它提供了进行任何类型的真正 HTML 解析的综合方法。