【问题标题】:PHP regex: is there anything wrong with this code?PHP 正则表达式:这段代码有什么问题吗?
【发布时间】:2011-04-03 02:52:31
【问题描述】:

preg_replace_callback('#<(code|pre)([^>]*)>(((?!</?\1).)*|(?R))*</\1>#si', 'self::replaceit', $text);

?

我正在尝试替换 code/pre 标记之间的文本,它可以执行我想要的操作,但有时它会破坏页面。

我用几个文本样本对其进行了测试,其中一些包含大量 & < 等字符使浏览器停止显示页面并显示“连接已被远程服务器关闭”消息

【问题讨论】:

    标签: php regex string


    【解决方案1】:

    我想帮忙。我以前见过这个问题!

    您的正则表达式在逻辑上看起来 A-Ok,但是当应用于较大的主题字符串时,它可能会导致大量递归回溯,这会导致 PCRE 引擎中的堆栈溢出。此溢出会导致分段错误和 PCRE 可执行文件(Apache 或 PHP)崩溃,而不会发出警告。 (症状是 "connection closed by remote server" 消息。)这种未处理的崩溃是由于 PHP 对 pcre.recursion_limit 参数的默认设置选择不当(默认为 100,000,即太高)。首先让我们看看这是否真的是问题的一部分。

    将以下代码添加到您的脚本中:

    // Place this at the top of the script
    ini_set("pcre.recursion_limit", "524"); // 256KB stack. Win32 Apache
    
    $re = '#<(code|pre)([^>]*)>(((?!</?\1).)*|(?R))*</\1>#si';
    $text = preg_replace_callback($re, 'self::replaceit', $text);
    // Check the return value for NULL which indicates a PCRE error.
    if ($text === null) exit("PCRE Error! Subject too large or complex.");
    

    有了这个,您应该不再收到“连接关闭”消息,而是 PCRE 错误退出消息。请注意,上述 524 设置适用于 Win32 Apache httpd.exe(具有 256KB 堆栈)。如果您在 *nix 服务器上运行,则可以将此值提高到 16777。这些数字背后的原因是 recursion _limit 值应设置为可执行文件堆栈大小除以 500。WIN32 可执行文件通常具有 256KB 堆栈和 *nix 可执行文件通常使用 8MB 堆栈构建。 Philip Hazel,(excellent PCRE 引擎的作者)已经详细解决了这个问题。见:pcrestack man page

    完成此操作后,请回来报告,我会在下一阶段提供帮助...

    (请注意,导致问题的不是 (?R) 表达式。稍后会详细说明。)

    通过实施 Jeffrey Friedl 的 “Unrolling-the-Loop” 效率技术,可以显着改进正则表达式(在解决此问题和提高速度方面)。这将大大减少必要的回溯次数,并可能解决您的问题。这是您的正则表达式的改进(和彻底评论)版本。

    $re = '% # Match an outermost PRE or CODE element.
        (               # $1: PRE/CODE element open tag
          <(code|pre)   # $2: Open tag name
          [^>]*+>       # Remainder of opening tag.
        )               # End $1: PRE/CODE element open tag.
        (               # $3: PRE/CODE element contents.
          (?:           # Group for contents alternatives
            (?R)        # Either a nested PRE or CODE element
          |             # Or non- <CODE, </CODE, <PRE or </PRE stuff.
            [^<]*+      # Begin: {normal* (special normal*)*} construct
            (?:         # See: "Mastering Regular Expressions".
              <         # {special} Match a <, but only if it is
              (?!/?\2)  # not the start of a nested or closing tag.
              [^<]*+    # match more {normal*}
            )*+         # Finish "Unrolling the loop"
          )*+           # Zero or more contents alternatives.
        )               # End $3: PRE/CODE element contents.
        (</\2>)         # $4: PRE/CODE element close tag
        %ix';
    

    然而,这个正则表达式的不同之处在于它使用了四个捕获组:$1 包含整个元素开始标记,$2 包含元素标记名称(用作反向引用),$3 包含元素内容,$4 包含元素结束标记。

    【讨论】:

    • 是的,我确实收到了这个错误:PCRE Error! Subject too large or complex.
    • ps:我设法找到了解决我的问题的方法,方法是将&lt;pre&gt;/&lt;code&gt; 临时替换为[pre]/[code] 和不同的正则表达式模式:stackoverflow.com/questions/5527574/… :)
    • @ridgerunner 感谢您的精彩解释。肯定让我免于头疼。
    【解决方案2】:

    这段代码有什么问题吗?

    是的。您正在尝试使用正则表达式解析 HTML。啧啧啧啧。我们还是不要summon Zalgo

    您应该使用DOM

    $doc = new DOMDocument();
    $doc->loadHTML($text);
    $code_tags = $doc->getElementsByTagName('code');
    $pre_tags = $doc->getElementsByTagName('pre');
    

    这将为您留下一组Node 列表,您可以根据需要处理其中的内容。如果遇到&amp;lt;textContent 中的朋友(或使用saveXML 重新序列化内容时),并且需要实际标签,请考虑htmlspecialchars_decode


    获取$code_tags中的第一个和最后一个元素,即DOM Node List

    $first_code_tag = $code_tags->item(0);
    $last_code_tag = $code_tags->item( $code_tags->length - 1 );
    

    虽然您可以将节点列表视为foreach 内的数组,但它不能直接索引,因此需要检查长度属性和使用item 方法。请注意,当列表中只有一个项目时,第一个和最后一个节点将是相同的。值得庆幸的是,您只需检查$code_tags-&gt;length 是否大于一,然后再检查除第一个之外的最后一个。

    我不确定这是否会对您有所帮助。根据您的其他问题,听起来您正在使用这种方法来处理 BBCode,并且您已经将方括号变成了小于和大于。请注意,这不是问题,但它可能会让生活变得有趣。

    尝试检查以下输出:

    echo $doc->saveXML($first_code_tag);
    

    看看它是否为您提供了您期望的内容。

    【讨论】:

    • 我能否以某种方式获得第一个和最后一个 PRE/CODE,然后用它们之间包裹的内容做任何我想做的事情?
    • @Alexandra,我已经更新了我的帖子,其中包含有关获取节点列表中第一个和最后一个节点的信息,但考虑到你的其他问题,我不再确定这会有所帮助适用于您当前的用例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多