【问题标题】:Selectivly replace certain HTML tags via PHP while keeping some通过 PHP 选择性地替换某些 HTML 标签,同时保留一些
【发布时间】:2016-07-05 22:37:41
【问题描述】:

我有一个包含 html 代码的表单,由于我无法禁用 MCE 编辑器的自动格式设置,有时会有点混乱。

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

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

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

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

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

所以基本上任何封装了<a href... 链接。

任何想法如何做到这一点,我认为我需要使用正则表达式来做到这一点,但可能有更简单/更好的方法。

【问题讨论】:

  • 澄清一下,您不想删除<span></span> 的内容吗?只是包装?
  • 4castle 是的,对不起。我会编辑并说明清楚。我一直试图让自己成为一些正则表达式,但我对它们很糟糕。 ^\<span.+\>$

标签: php regex replace preg-replace


【解决方案1】:

试试这个:

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

Regex101 Tested

这个正则表达式有两部分

  1. 它会删除后面没有 &lt;a 的所有 &lt;span&gt;
  2. 它会删除前面没有 /a&gt; 的所有 &lt;/span&gt;

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

【讨论】:

  • 让我知道这是否有效,我可能对您的测试用例了解不够。
  • 我将如何使用它,我假设preg_replace()
  • 我再次更新了我的答案,并进一步解释了正则表达式以使其大放异彩。如果您发现错误,请告诉我可以修复的任何错误。
  • 效果很好,我将使用 xjstrategebx 中的这个或另一个,因为我的任务很简单,我知道解析器路由在逻辑上更适合更大的项目。
  • 太棒了!是的,我同意。为了提倡我的回答,这个正则表达式几乎是他的两倍,并且不会与嵌套的 span 标签中断。
【解决方案2】:

已编辑以切换捕获组

我总是觉得做这样的事情真的很棘手,因为往往有很多不可预见的情况需要处理,否则它们会回来咬我。

话虽如此,这种正则表达式的挑战通常很有趣。

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

(?:&lt;span[^&gt;]*?&gt;)(?!&lt;a)(.*?)(?:&lt;\/span&gt;)

在这里行动:https://regex101.com/r/qY8pL5/3

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

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

在 php 中你可以这样实现它:

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

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


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

【讨论】:

  • 虽然这个正则表达式应该适用于问题中提出的特定情况,但在撰写本文时最完整的答案是@fusion3k 关于选择 DOM 遍历而不是正则表达式进行任何和所有 HTML 解析的答案。我会推荐这种方法而不是这种方法。
  • 该问题澄清了它不希望删除 span 标签内的 html,只删除 span 标签。这会删除里面的 html。
  • 好点,我让捕获组倒退了。正如最初编写的那样,它将删除开始和结束跨度标记之间的所有内容,无论是 HTML 还是纯文本。将更新 - 不过,对于 OP,@fusion3k 的答案在概念上是正确的。
  • 这样好多了。但我应该指出,如果span 中有span,这个正则表达式仍然存在问题。另外,我同意@fusion3k 有最安全的答案,但为了快速修复,正则表达式可以在这里正常工作。该算法并不过分复杂。
  • 是的,我对这个问题的解释只是容器跨度,而不管它的内容,即使它有另一个跨度。我的猜测是容器内的第二个跨度将匹配第一个关闭跨度并破坏一些东西。回到我的开场白,每次遇到你没有计划好的事情时,外部性都会使正则表达式解析变得棘手。
【解决方案3】:

要执行此操作,您需要一个解析器,而不是正则表达式(另请参阅 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”的每个&lt;span&gt; 节点:如果找到此模式(-&gt;item(0)),则执行循环。

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

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

将所有子节点移动到新片段后,将空的&lt;span&gt; 节点替换为片段。


其他可以帮助您的有用 XPath:

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

【讨论】:

  • 尽管我自己使用正则表达式进行响应(无论好坏,我读到的问题都要求这样做并且有义务),要进行任何形式的真正 HTML 编辑,@fusion3k 的答案在概念上是正确的. PHP 的 DOM 解析很笨拙,当对象属性被隐藏时,调试非常痛苦,但是遍历 DOM 是确保正则表达式匹配的不可预见的情况不会使您的生活成为噩梦的唯一真正可靠的方法,而 PHP 的 DOM 解析将把工作做好。
猜你喜欢
  • 2011-08-22
  • 1970-01-01
  • 2018-09-06
  • 1970-01-01
  • 1970-01-01
  • 2021-09-12
  • 2012-07-22
  • 1970-01-01
  • 2020-06-25
相关资源
最近更新 更多