Максимальный и минимальный поиск

Проблема

Имеется шаблон с максимальным квантификатором —*,+,? или {}. Требуется перейти от максимального поиска к минимальному.
Классический пример — наивная подстановка для удаления тегов из 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

См. также




2013-09-10 17:05:19

Proverte kod v komentariyah gde pro list tam oshibki detskie




Оставить комментарий:
Ваше Имя:
Email:
Антибот: *  
Ваш комментарий: