Регулярное выражение PREG_BACKTRACK_LIMIT_ERROR при извлечении действительно длинного текста без жадности

У меня есть строка вида:

Некоторый текст [Открытие] Действительно действительно длинный текст … [Закрытие] Больше текста [Закрытие] Еще больше текста

Я хочу извлечь действительно длинный текст … из строки с помощью регулярного выражения. Вплоть до первого [закрытия].

Если я сделаю регулярное выражение, подобное этому:

$pMatch = "'\[Opening\](.+)\[Closing\]'si";

Это дает мне:

Действительно очень длинный текст … [Закрытие] Больше текста

Я также могу сделать это не жадным, как это:

$pMatch = "'\[Opening\](.+?)\[Closing\]'si";

Что работает и дает мне правильный вывод:

Действительно действительно длинный текст …

Однако если я заменю «Действительно действительно длинный текст …» на действительно очень длинный текст, это не сработает, и вместо этого я получу PREG_BACKTRACK_LIMIT_ERROR. Я не получаю ошибку, если я использую жадное регулярное выражение. Я просто получаю неправильный вывод, как в первом случае.

Я работал с регулярными выражениями некоторое время, но это меня озадачило. Есть ли способ заставить это работать с регулярным выражением или регулярное выражение не подходит для этой задачи?

Вот код PHP, чтобы воспроизвести проблему:

<?php

$sShortString = "Some Text[Opening]Really Really Long Text...[Closing]More Text[Closing]Even More Text";
$sLongString = "Some Text[Opening]".str_repeat("BLAH", 1000000)."[Closing]More Text[Closing]Even More Text";

$pGreedyMatch = "'\[Opening\](.+)\[Closing\]'si";
$pNonGreedyMatch = "'\[Opening\](.+?)\[Closing\]'si";

header("Content-Type: text/plain");

if (preg_match($pGreedyMatch, $sShortString, $aMatch)) {
echo "Greedy Match:\n";
print_r($aMatch);
}

if (preg_match($pNonGreedyMatch, $sShortString, $aMatch)) {
echo "Non-Greedy Match:\n";
print_r($aMatch);
}

if (preg_match($pGreedyMatch, $sLongString, $aMatch)) {
echo "Greedy Match:\n";
echo "Length: ".strlen($aMatch[1])."\n";
}

if (preg_match($pNonGreedyMatch, $sLongString, $aMatch)) {
echo "Non-Greedy Match:\n";
echo strlen($aMatch[1]);
} else {
echo "Non-Greedy Doesn't Match!\n";
}

$iLastError = preg_last_error();
if ($iLastError == PREG_BACKTRACK_LIMIT_ERROR) {
echo "It's because the backtrack limit was exceeded!\n";
}

?>

Я получаю вывод:

Greedy Match:
Array
(
[0] => [Opening]Really Really Long Text...[Closing]More Text[Closing]
[1] => Really Really Long Text...[Closing]More Text
)
Non-Greedy Match:
Array
(
[0] => [Opening]Really Really Long Text...[Closing]
[1] => Really Really Long Text...
)
Greedy Match:
Length: 4000018
Non-Greedy Doesn't Match!
It's because the backtrack limit was exceeded!

Я работал, используя жадное регулярное выражение и дополнительный код, чтобы убрать текст с [Закрытие] и далее. Я хотел бы лучше понять, что происходит за кулисами, зачем нужно так много времени возвращать назад, и если есть способ изменить регулярное выражение, чтобы оно выполняло задачу.

Я действительно ценю любое понимание!

0

Решение

Нежадный квантификатор имеет свою стоимость, потому что каждый раз, когда он читает символ, он должен сверяться с концом шаблона.

В приведенной выше схеме каждый раз . в (.+?) совпадения, он проверяет соответствие следующих символов [Closing], Каждый раз, когда это происходит, и оно не совпадает, оно должно возвращаться назад и продолжать поиск. Вот почему предел возврата был исчерпан.

Шаблон можно переписать так:

'\[Opening\]([^\[]*(?:\[(?!Closing)[^\[]*)*)(*SKIP)\[Closing\]'si

Давайте рассмотрим этот шаблон по частям, чтобы понять его.

1) Открываем с \[Opening\], Этот шаблон соответствует открывающему тегу.

2) Поскольку наш шаблон не повторяется внутри себя, ()(*SKIP) Директива используется в качестве дальнейшей оптимизации. Это означает, что если мы не соответствуем шаблону, то мы возобновим наш поиск с конца того места, где мы искали. Поведение по умолчанию начнет поиск снова на следующем символе.

Чтобы лучше понять это, представьте, что наша строка sometimes we get [Close to matching, Когда мы доберемся до [мы сканируем [Clos прежде чем мы заключим, что это на самом деле не тот шаблон, который мы хотим. Обычно мы возвращаемся, а затем снова начинаем смотреть на Close, Тем не мение, (*SKIP) позволяет нам продолжать поиск в e to matc,

3) В наших скобках мы начинаем с рисунка [^\[]*что позволяет нам сопоставить столько символов, сколько мы можем, которые не являются [, ^ указывает нет, \[ для [, а также [] окружает его как набор символов. * позволяет повторять столько раз, сколько возможно.

4) Теперь у нас есть (?:)*, () позволяет нам указать строку, и ?: указывает, что не будет сохранен, и * позволяет повторять столько раз, сколько мы хотим (в том числе вообще без раз).

5) Первый символ в этой строке \[ или просто [ мы ожидаем, как часть нашего закрывающего тега.

6) Далее мы имеем (?!Closing\]), (?!) это негативный взгляд. Предварительный просмотр означает, что синтаксический анализатор будет смотреть на следующие символы и либо совпадать, либо не совпадать без использования символов. Это позволяет нам сопоставить что-то, пока это не Closing] фактически не потребляя это.

7) У нас есть еще один [^\[]* что позволяет нам продолжать есть персонажей после нашего отказа от просмотра. Это позволяет нам продолжать использовать строку после того, как мы получим что-то вроде [Clos,

8) Наконец, наше регулярное выражение заканчивается \[Closing\],

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]