Максимальный и минимальный поиск
Проблема
Имеется шаблон с максимальным квантификатором —*,+,? или {}. Требуется перейти от максимального
поиска к минимальному.
Классический пример — наивная подстановка для удаления тегов из HTML-документа.
Хотя s#<TT>.*</TT>##gsi
выглядит соблазнительно, в действительности будет удален весь текст от первого открывающего до
последнего закрывающего тега ТТ. От строки "Even <TT>vi</TT> can edit <TT>troff</TT>
effectively."
остается лишь "Even effectively" — смысл полностью изменился!
Решение
Замените максимальный квантификатор соответствующим минимальным. Другими словами, *, +, ?
или {} соответственно заменяются *?, +?, ?? и {}?.
Комментарий
В Perl существуют два набора квантификаторов: максимальные (*, +, ? и {}) и минимальные
(*?,+?,?? и {}?). Например, для строки "Perl is a Swiss Army Сhainsaw! " шаблон /(r.*s)/
совпадет с "rl is a Swiss Army Chains", а шаблон /(r.*?s)/ – с "rl is".
Предположим, шаблон содержит максимальный квантификатор. При поиске подстроки, которая
может встречаться переменное число раз (например, 0 и более раз для * или 1 и более раз для +),
механизм поиска всегда предпочитает «и более». Следовательно, шаблон /foo.*bar/ совпадает от
первого "foo" до последне го "bar", а не до следующего "bar", как можно ожидать. Чтобы при
поиске предпочтение отдавалось минимальным, а не максимальным совпадениям, поставь после
квантификатора вопросительный знак. Таким образом, *?, как и *, соотвествует 0 и более
повторений, но при этом выбирается совпадение минимальной, не максимальной длины.
# Максимальный поиск
s/<.*>//gs; # Неудачная попытка удаления тегов
# Минимальный поиск
s/<.*?>//gs; # Неудачная попытка удаления тегов
Показанное решение не обеспечивает правильного удаления тегов из HTMI документа,
поскольку отдельное регулярное выражение не заменит полноценного анализатора.
Впрочем, с минимальными совпадениями дело обстоит не так просто. Не стоит ошибочно полагать,
что BEGIN.*?END в шаблоне всегда соответствует самому короткому текстовому фрагменту между соседними
экземплярами BEGIN и END. Возьмем шаблон /BEGIN(.*?)END/. После поиска в строке "BEGIN and BEGIN and END"
переменная $1 будет содержать "and BEGIN and". Вероятно, вы рассчитывали на другой результат.
Представьте, что мы хотим извлечь из HTML-документа весь текст, оформленный полужирным и курсивным
шрифтом одновременно:
<b><i>this</i> and <i>that</i> are important</b> Oh, <b><i>me too!</i></b>
Может показаться, что шаблон для поиска текста, находящегося между парми тегов HTML
(то есть не включающий теги), должен выглядеть так:
m{ <b><i>(.*?)</i></b> }sx;
Как ни странно, шаблон этого не делает. Многие ошибочно полагают, что он сначала находит
последовательность "<b><i>", затем нечто отличное от "<b><i>* а затем —
"</i></b>", оставляя
промежуточный текст в $1. Хотя по отношению к входным данным он часто работает именно так,
в действительности делаете совершенно иное. Шаблон просто находит левую строку минимальной длины,
которая соответствует всему шаблону. В данном примере это вся строка. Если вы хотели
ограничиться текстом между "<b><i>" и "</i></b>",
не включающий другие теги полужирного или
курсивного начертания, результат окажется не верным.
Если искомая строка состоит всего из одного символа, инвертированный класc
(например, /Х[^Х]*)Х/) заметно превосходит минимальный поиск но эффективности. Однако
обобщенный шаблон, который находит «сначала BEGIN, зател не-BEGIN, затем END» для
произвольных BEGIN и END и сохраняет промежуточный текст в $1, выглядит следующим образом:
/BEGIN((?:(?!BEGIN).)*)END/
Наш пример с тегами HTML выглядит примерно так:
m{ <b><i>( (?: (?!</b>|</i>). )* ) </i></b> }sx;
или так:
m{ <b><i>( (?: (?!</[ib]>). )* ) </i></b> }sx;
Как замечает Джеффри Фридл, это скороспелое решение не очень эффективно. В ситуациях, где
скорость действительно важна, он предлагает воспользоваться более сложным шаблоном:
m{
<b><i>
[^<]* # Заведомо допустимо
(?: # Символ '<' возможен, если он не входит в недопустимую конструкцию
(?! </?[ib]> ) # Недопустимо
< # Все нормально, найти <
[^<]* # и продолжить
) *
</i></b>
}sx
См. также
Proverte kod v komentariyah gde pro list tam oshibki detskie
Оставить комментарий:
|
|