PHP正则表达式:这段代码有什么问题吗?

时间:2011-04-03 02:52:32

标签: php regex string

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

我正在尝试在代码/预标记之间替换文本,它会按照我想要的方式执行,但有时会破坏页面。

我使用一些文本示例对其进行了测试,其中一些包含大量&amp; &lt;等字符,使浏览器停止显示带有“远程服务器关闭的连接”消息的页面

2 个答案:

答案 0 :(得分:6)

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

你的正则表达式看起来逻辑上是A-Ok,但是当应用于一个大的主题字符串时,很可能导致大量的递归回溯,这导致PCRE引擎中的堆栈溢出。这种溢出会导致分段错误和PCRE可执行文件(Apache或PHP)崩溃,而不会发出警告。 (症状是由远程服务器“消息关闭的”连接。)这种未处理的崩溃是由于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(优秀的 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包含元素标记名称(用作后向引用),{{ 1}}包含元素内容,$3包含元素结束标记。

答案 1 :(得分:4)

  

这段代码有什么问题吗?

是。您正在尝试使用正则表达式解析HTML。 Tsk,tsk,tsk。我们还不是summon Zalgo

您应该使用DOM

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

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


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

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

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

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

尝试检查输出:

echo $doc->saveXML($first_code_tag);

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