【问题标题】:efficiently remove substring that may not contain a specific word -有效地删除可能不包含特定单词的子字符串 -
【发布时间】:2012-09-13 15:16:22
【问题描述】:

我尝试使用 php 将一些可怕的无效 html 代码映射到我以后需要的 xml 结构。这工作得很好,但总有一些我无法处理的部分。所以决定是,只删除该代码,以使 xml 保持有效。这就是它的样子。

<body>
    <item>abc</item>
    <item>def</item>
    unparsable rest
</body>

所以目标是找到一个解决方案(可能是正则表达式,但我愿意接受任何解决方案)来删除“无法解析的其余部分”。

我尝试在这个正则表达式中使用 preg_replace

/<\/item>(((?!item).)*)\s*<\/body>/iU

它工作得很好,与我想要在 $1 中的部分完全匹配,最后一个 和 之间的所有东西,但是由于 xmls 非常大,计算只是在几毫秒后崩溃。我知道正则表达式在做负前瞻方面不太好,但我认为它没有那么糟糕。

因此需要一个更有效的解决方案。不幸的是,我不能使用 strrpos,因为在

之后还有更多标签

【问题讨论】:

  • 过度简化,您可以检查&lt;/tag&gt;.*?&lt; 基本上在元素的结束标记和下一个(有效的)开始标记之间的任何格式错误。虽然这种询问应该通过解析器完成,而不是用模式强制它。 (不是说你嫁给了模式,但解析器会是更好的选择)。
  • 但是解析器可能无法解析无效的东西,这正是我的问题:)
  • &lt;/item&gt;.*?&lt; 将匹配第一个结束项目,而不是最后一个,因为它不知道,我想要的文本中不应该有另一个项目
  • 我不是说开箱即用的东西,我是说你最好写一个解析器。您提到这是之前转换的结果。之前的转换听起来需要处理,而不是失败的创可贴。
  • 好吧,如果你告诉我一个 HTML-Parser 可以处理像 &lt;p&gt;&lt;b&gt;text&lt;/p&gt;&lt;/b&gt; 这样的事情(这是一个不错的错误),我会接受它:) 但是 loadHtml() 没有机会全部。我实际上正在编写很多不会出错的解析规则,但在某些时候,一页只需要 20 个小时。所以决定只是删除其余部分。

标签: php regex regex-negation performance regex-lookarounds


【解决方案1】:

您的正则表达式模式中有一个tempered greedy token。它本质上很慢,请参阅我链接到的答案中的“性能问题”部分。

所以,我更喜欢在没有Us 修饰符为~&lt;/item&gt;(((?!item).)*?)\s*?&lt;/body&gt;~ismatches your input string within 231 steps 的情况下编写您当前的正则表达式。

注意这里\s*\s*? 没有太大的语义差异,因为&lt;/body&gt; 之前没有其他量化模式。 \s*,贪婪模式,在这种情况下是首选。

让我们使用unroll 模式并将((?!item).)*? 替换为[^i]*(?:i(?!tem)[^i]*)*~&lt;/item&gt;([^i]*(?:i(?!tem)[^i]*)*)\s*&lt;/body&gt;~ismatches your input within 117 steps

这对于字符串来说还是相当多的。 &lt;/item&gt; 之后的空格可以与\s*+ 匹配可能,以减少对该部分字符串的回溯访问。 ~&lt;/item&gt;\s*+([^i]*(?:i(?!tem)[^i]*)*)\s*&lt;/body&gt;~is 显示了改进,现在 it takes 89 steps 可以匹配字符串,并且只有 unparsable rest 位于第 1 组值中。

不幸的是,我们不能在这里玩太多回溯,因为您想从第 1 组值中删除尾随空格。

如果您想匹配&lt;/item&gt;&lt;/body&gt; 之间不包含&lt;item&gt; 的所有内容,则该模式将类似于~&lt;/item&gt;\s*+([^&lt;]*(?:&lt;(?!item&gt;)[^&lt;]*)*)\s*&lt;/body&gt;~is,请参阅regex demo

【讨论】:

    【解决方案2】:

    检查每一行以''结束:

    $t ='<body>
        <item>abc</item>
        <item>def</item>
        unparsable rest
    </body>';
    
    // break the string into lines
    $filtered = array_filter(explode("\n", $t), function($line) {
        // each line
        $line = trim($line); //ignore white spaces
        return $line[0] == '<' && substr($line, -1) == '>';
    });
    // rebuild the string
    $result = implode("\n", $filtered);
    echo $result;
    

    演示:https://3v4l.org/Mt5eG

    【讨论】:

      猜你喜欢
      • 2022-10-24
      • 1970-01-01
      • 1970-01-01
      • 2016-05-05
      • 2014-05-20
      • 2012-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多