У меня есть строка вида:
Некоторый текст [Открытие] Действительно действительно длинный текст … [Закрытие] Больше текста [Закрытие] Еще больше текста
Я хочу извлечь действительно длинный текст … из строки с помощью регулярного выражения. Вплоть до первого [закрытия].
Если я сделаю регулярное выражение, подобное этому:
$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!
Я работал, используя жадное регулярное выражение и дополнительный код, чтобы убрать текст с [Закрытие] и далее. Я хотел бы лучше понять, что происходит за кулисами, зачем нужно так много времени возвращать назад, и если есть способ изменить регулярное выражение, чтобы оно выполняло задачу.
Я действительно ценю любое понимание!
Нежадный квантификатор имеет свою стоимость, потому что каждый раз, когда он читает символ, он должен сверяться с концом шаблона.
В приведенной выше схеме каждый раз .
в (.+?)
совпадения, он проверяет соответствие следующих символов [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\]
,
Других решений пока нет …