PHP регулярное выражение: что-то не так с этим кодом? - PullRequest
1 голос
/ 03 апреля 2011

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

?

Я пытаюсь заменить текст между тегами code / pre и он делает то, что я хочу, но иногда он ломает страницу.

Я протестировал его с несколькими образцами текста, и некоторые из них, содержащие большое количество символов &amp; &lt; и т. Д., Заставляют браузер перестать отображать страницу с сообщением «соединение закрыто удаленным сервером»

Ответы [ 2 ]

6 голосов
/ 03 апреля 2011

Я бы хотел помочь.Я видел эту проблему раньше!

Ваше регулярное выражение выглядит логично 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 (который имеет стек 256 КБ).Если вы работаете на сервере * nix, вы можете увеличить это значение до 16777. Причина этих чисел заключается в том, что значение recursion _limit должно быть установлено на размер исполняемого стека, деленный на 500. Исполняемый файл WIn32 обычно имеет стек 256 КБ.и исполняемые файлы * nix обычно создаются со стеком 8 МБ.Филипп Хейзел (автор механизма превосходный PCRE) подробно рассмотрел эту проблему.См .: Страница справочника pcrestack

Как только вы это сделаете, сообщите об этом, и я помогу с следующей фазой ...

(Обратите внимание, что это НЕ(?R) выражение, вызывающее проблему. Более позднее.)

Регулярное выражение может быть значительно улучшено (как для решения этой проблемы, так и для повышения его скорости), путем реализации "Джеффри Фридла" Unrolling-the-Петля " Эффективность техники.Это значительно сократит количество необходимых возвратов и, вероятно, решит вашу проблему.Вот улучшенная (и тщательно прокомментированная) версия вашего регулярного выражения.

$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 содержит конечный тег элемента.

4 голосов
/ 03 апреля 2011

что-то не так с этим кодом?

Да. Вы пытаетесь разобрать HTML с помощью регулярного выражения. Тск, тск, тск. Давай пока не будем вызывать Зальго .

Вы должны использовать DOM .

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

В результате вы получите набор списков Node , которые вы можете обрабатывать по своему усмотрению. Если вы встречаете &lt; и друзей в textContent (или при повторной сериализации содержимого с использованием saveXML) и вам нужны фактические теги, рассмотрите htmlspecialchars_decode.


Получение первого и последнего элемента в $code_tags, который представляет собой список узлов DOM :

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

Хотя вы можете обрабатывать список узлов как массив внутри foreach, он не индексируется напрямую, поэтому выполняется полная проверка свойства длины и использование метода item. Помните, что когда в списке только один элемент, первый и последний узлы будут идентичны. К счастью, вы можете просто проверить, является ли $code_tags->length больше единицы, прежде чем проверять последнее в дополнение к первому.

Я не уверен, что это поможет вам. Исходя из ваших других вопросов, похоже, что вы используете эту методологию для работы с BBCode и что вы превратили квадратные скобки в меньшие и большие. Заметьте, это не проблема 1043 *, но это может сделать жизнь интересной.

Попробуйте проверить вывод:

echo $doc->saveXML($first_code_tag);

чтобы узнать, дает ли он ожидаемый вами контент.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...