preg_replace_callback('#<(code|pre)([^>]*)>(((?!</?\1).)*|(?R))*</\1>#si', 'self::replaceit', $text);
我正在尝试在代码/预标记之间替换文本,它会按照我想要的方式执行,但有时会破坏页面。
我使用一些文本示例对其进行了测试,其中一些包含大量&
<
等字符,使浏览器停止显示带有“远程服务器关闭的连接”消息的页面
答案 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
中遇到<
和朋友(或使用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);
看看它是否为您提供了您期望的内容。