Повторное выражение PHP regex apache
У меня есть регулярное выражение, которое подходит для системы шаблонов, которая, к сожалению, кажется, разбивает apache (она работает в Windows) на некоторые скромно-тривиальные запросы. Я исследовал проблему, и есть несколько предложений по увеличению размера стека и т.д., Ни одна из которых, похоже, не работает, и мне не нравится работать с такими проблемами, увеличивая ограничения в любом случае, поскольку в целом это просто подтолкнуло ошибку к будущему.
В любом случае какие-либо идеи о том, как изменить регулярное выражение, чтобы сделать его менее вероятным?
Идея состоит в том, чтобы поймать самый внутренний блок (в данном случае {block:test}This should be caught first!{/block:test}
), который затем будет str_replace из начальных/конечных тегов и повторно запускает все это через регулярное выражение, пока не останется блоков.
Regex:
~(?P<opening>{(?P<inverse>[!])?block:(?P<name>[a-z0-9\s_-]+)})(?P<contents>(?:(?!{/?block:[0-9a-z-_]+}).)*)(?P<closing>{/block:\3})~ism
Пример шаблона:
<div class="f_sponsors s_banners">
<div class="s_previous">«</div>
<div class="s_sponsors">
<ul>
{block:sponsors}
<li>
<a href="{var:url}" target="_blank">
<img src="image/160x126/{var:image}" alt="{var:name}" title="{var:name}" />
</a>
{block:test}This should be caught first!{/block:test}
</li>
{/block:sponsors}
</ul>
</div>
<div class="s_next">»</div>
</div>
Это длинный выстрел, я полагаю.: (
Ответы
Ответ 1
Попробуйте следующее:
'~(?P<opening>\{(?P<inverse>[!])?block:(?P<name>[a-z0-9\s_-]+)\})(?P<contents>[^{]*(?:\{(?!/block:(?P=name)\})[^{]*)*)(?P<closing>\{/block:(?P=name)\})~i'
Или в читаемой форме:
'~(?P<opening>
\{
(?P<inverse>[!])?
block:
(?P<name>[a-z0-9\s_-]+)
\}
)
(?P<contents>
[^{]*(?:\{(?!/block:(?P=name)\})[^{]*)*
)
(?P<closing>
\{
/block:(?P=name)
\}
)~ix'
Самая важная часть в группе (?P<contents>..)
:
[^{]*(?:\{(?!/block:(?P=name)\})[^{]*)*
Начиная, единственным интересующим нас персонажем является открывающая фигурная скобка, поэтому мы можем разбить любые другие символы с помощью [^{]*
. Только после того, как мы увидим {
, мы проверим, является ли это началом тега {/block}
. Если это не так, мы идем вперед и потребляем его и начинаем сканирование следующего, и повторяем по мере необходимости.
Используя RegexBuddy, я проверил каждое регулярное выражение, поместив курсор в начало тега {block:sponsors}
и отлаживая. Затем я удалял конечную скобку с закрывающего тега {/block:sponsors}
, чтобы принудительно выполнить неудачное совпадение и отлаживать его снова. Ваше регулярное выражение выполнило 940 шагов для успеха и 2265 шагов для отказа. Шахта сделала 57 шагов, чтобы преуспеть, и 83 шага к неудаче.
На стороне примечания я удалил модификатор s
, потому что потому, что я не использую точку (.
) и модификатор m
, потому что это никогда не было необходимо. Я также использовал названный backreference (?P=name)
вместо \3
в соответствии с замечательным предложением @DaveRandom. И я избежал всех фигурных скобок ({
и }
), потому что мне легче читать этот путь.
РЕДАКТИРОВАТЬ: Если вы хотите совместить самый внутренний именованный блок, измените среднюю часть регулярного выражения следующим образом:
(?P<contents>
[^{]*(?:\{(?!/block:(?P=name)\})[^{]*)*
)
... к этому (как предложил @Kobi в своем комментарии):
(?P<contents>
[^{]*(?:\{(?!/?block:[a-z0-9\s_-]+\})[^{]*)*
)
Первоначально группа (?P<opening>...)
захватила бы первый открытый тег, который она увидела, тогда группа (?P<contents>..)
будет потреблять что угодно, включая другие теги, - пока они не были закрывающим тегом, чтобы соответствовать найденному группой (?P<opening>...)
. (Тогда группа (?P<closing>...)
будет идти вперед и потреблять это.)
Теперь группа (?P<contents>...)
отказывается соответствовать любому тегу, открывая или закрывая (обратите внимание на /?
в начале), независимо от имени. Таким образом, регулярное выражение сначала начинает соответствовать тегу {block:sponsors}
, но когда он встречает тег {block:test}
, он отказывается от этого совпадения и возвращается к поиску открывающего тега. Он начинается снова с тега {block:test}
, на этот раз успешно заканчивая совпадение, когда находит тег закрытия {/block:test}
.
Звучит неэффективно, описывая это так, но это действительно не так. Трюк, который я описал ранее, перекрывая не-фигурные скобки, заглушает эффект этих ложных запусков. Там, где вы делали негативный взгляд почти на каждую позицию, теперь вы делаете это только тогда, когда вы сталкиваетесь с {
. Вы могли бы даже использовать притяжательные квантификаторы, как предлагал @godspeedlee:
(?P<contents>
[^{]*+(?:\{(?!/?block:[a-z0-9\s_-]+\})[^{]*+)*+
)
... потому что вы знаете, что он никогда не поглотит ничего, что ему придется отдать позже. Это немного ускорило бы, но на самом деле это не обязательно.
Ответ 2
Вы можете использовать atomic group: (?>...)
или possessive quantifiers: ?+ *+ ++..
для подавления/ограничения обратного отслеживания и ускорения согласования по методу unrolling loop
. Мое решение:
\{block:(\w++)\}([^<{]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}
Я тестировал http://regexr.com?31p03.
соответствует {block:sponsors}...{/block:sponsors}
:
\{block:(sponsors)\}([^<{]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}
http://regexr.com?31rb3
соответствует {block:test}...{/block:test}
:
\{block:(test)\}([^<{]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}
http://regexr.com?31rb6
другое решение:
в исходном коде PCRE вы можете удалить комментарий из config.h
:
/* #undef NO_RECURSE */
после копирования текста из config.h
:
PCRE использует рекурсивные вызовы функций для обработки обратного отслеживания при сопоставлении. Иногда это может быть проблемой для систем, имеющих стеки ограниченного размера.
Определите NO_RECURSE, чтобы получить версию, которая не использует рекурсию в функции match(); вместо этого он создает свой собственный стек паролем, используя pcre_recurse_malloc() для получения памяти из кучи.
или вы можете изменить pcre.backtrack_limit
и pcre.recursion_limit
из php.ini
(http://www.php.net/manual/en/pcre.configuration.php)
Ответ 3
Должно ли решение быть одним регулярным выражением? Более эффективным подходом может быть просто поиск первого вхождения {/block:
(который может быть простым строковым поиском или регулярным выражением), а затем поиск назад с этой точки, чтобы найти соответствующий ему соответствующий тег открытия, соответствующим образом заменить диапазон и повторить до тех пор, пока блоков больше нет. Если каждый раз вы просматриваете первый закрывающий тег, начиная с вершины шаблона, то это даст вам самый глубоко вложенный блок.
Алгоритм зеркального отображения будет работать так же хорошо - найдите последний открывающий тег, а затем выполните поиск вперед оттуда для соответствующего закрывающего тега:
<?php
$template = //...
while(true) {
$last_open_tag = strrpos($template, '{block:');
$last_inverted_tag = strrpos($template, '{!block:');
// $block_start is the index of the '{' of the last opening block tag in the
// template, or false if there are no more block tags left
$block_start = max($last_open_tag, $last_inverted_tag);
if($block_start === false) {
// all done
break;
} else {
// extract the block name (the foo in {block:foo}) - from the character
// after the next : to the character before the next }, inclusive
$block_name_start = strpos($template, ':', $block_start) + 1;
$block_name = substr($template, $block_name_start,
strcspn($template, '}', $block_name_start));
// we now have the start tag and the block name, next find the end tag.
// $block_end is the index of the '{' of the next closing block tag after
// $block_start. If this doesn't match the opening tag something is wrong.
$block_end = strpos($template, '{/block:', $block_start);
if(strpos($template, $block_name.'}', $block_end + 8) !== $block_end + 8) {
// non-matching tag
print("Non-matching tag found\n");
break;
} else {
// now we have found the innermost block
// - its start tag begins at $block_start
// - its content begins at
// (strpos($template, '}', $block_start) + 1)
// - its content ends at $block_end
// - its end tag ends at ($block_end + strlen($block_name) + 9)
// [9 being the length of '{/block:' plus '}']
// - the start tag was inverted iff $block_start === $last_inverted_tag
$template = // do whatever you need to do to replace the template
}
}
}
echo $template;