ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
6.12. Локальный контекст в регулярных выраженияхПроблемаТребуется преобразовать регистр в другом локальном контексте или заставить метасимвол \w совпадать с символами национальных алфавитов - например, Jose или dejd vu. Предположим, у вас имеется полгигабайта текста на немецком языке, для которого необходимо составить предметный указатель. Вы хотите извлекать слова (с помощью \w+) и преобразовывать их в нижний регистр (с помощью 1с или \L). Однако обычные версии \w и 1с не находят слова немецкого языка и не изменяют регистр символов с диакритическими знаками.РешениеРегулярные выражения и функции обработки текста Perl имеют доступ к локальному контексту POSIX. Если включить в программу директиву use locale, Perl позаботится о символах национальных алфавитов - конечно, при наличии разумной спецификации LC_CTYPE и системной поддержки. use locale;КомментарийПо умолчанию \w+ и функции преобразования регистра работают с буквами верхнего и нижнего регистров, цифррами и подчеркиваниями. Преобразуются лишь простейшие английские слова, и даже в очень распространенных заимствованных словах происходят сбои. Директива use locale помогает справиться с затруднениями. Пример 6.10 показывает, чем отличаются выходные данные для английского (en) и немецкого (de) локальных контекстов. Пример 6.10. localeg#!/usr/bin/perl -w # localeg - выбор локального контекста use locale; use POSIX 'locale_h'; $name = "andreas k\xF6nig"; @locale{qw(German English)} = qw(de_DE.ISO_8859-1 us-ascii); setlocale(LC_CTYPE, $locale{English}) or die "Invalid locale $locale{English}"; @english_names = (); while ($name =~ /\b(\w+)\b/g) { push(@english_names, ucrirst($1)); } setlocale(LC_CTYPE, $locale{German}) or die "Invalid locale $locale{German}"; @german_names = (): while ($name =~ /\b(\w+)\b/g) { push(@german_names, ucfirst($1)); } print "English names: @english_names\n": print "German names: @german_names\n"; English names: Andreas К Nig German names: Andreas Konig Решение основано на поддержке локальных контекстов в POSIX. Ваша система может обладать, а может и не обладать такой поддержкой. Но даже если система заявляет о поддержке локальных контекстов POSIX, в стандарте не определены имена локальных контекстов. Разумеется, переносимость такого решения не гарантирована. > Смотри также --------------------------------
6.13. Неформальный поискПроблемаТребуется выполнить неформальный поиск по шаблону. Задача часто возникает в ситуации, когда пользовательский ввод может быть неточным или содержащим ошибки.РешениеВоспользуйтесь модулем String::Approx от СРАМ:use Strin::Approx qw(amatch);
КомментарийМодуль String::Approx вычисляет, насколько шаблон отличается от каждой строки списка. Если количество односимвольных вставок, удалений или замен для получения строки из шаблона не превышает определенного числа (по умолчанию 10 процентов длины шаблона), строка "совпадает" с шаблоном. В скалярном контексте amatch возвращает количество успешных совпадений. В списковом контексте возвращаются совпавшие строки.use String::Approx qw(amatch);
> Смотри также
6.14. Поиск от последнего совпаденияПроблемаТребуется возобновить поиск с того места, где было найдено последнее совпадение. Такая возможность пригодится при многократном извлечении фрагментов данных из строки,РешениеВоспользуйтесь комбинацией модификатора /g, метасимвола \G и функции роз.КомментарийПри наличии модификатора /д механизм поиска запоминает текущую позицию в строке. При следующем поиске с /д совпадения ищутся, начиная с сохраненной позиции. Это позволяет создать цикл while для извлечения необходимой информации из строки:while (/(\d+)/g) { print "Found $1\n": } Присутствие \G в шаблоне привязывает поиск к концу предыдущего совпадения. Например, если число хранится в строке с начальными пробелами, замена каждого пробела нулем может выполняться так: $n = " 49 here";
\G часто применяется в циклах while. Например, в следующем примере анализируется
список чисел, разделенных запятыми:
$_ = "The year 1752 lost 10 days on the 3rd of September";
Как видите, при последовательном применении шаблонов можно изменять позицию начала поиска с помощью модификатора /д. Позиция последнего совпадения связывается со скалярной величиной, в которой происходит поиск, а не с шаблоном. Позиция не копируется вместе со строкой и не сохраняется оператором local. Позиция последнего совпадения читается и задается функцией роз. Аргументом функции является строка, для которой читается или задается позиция последнего совпадения. Если аргумент не указан, роз работает с переменной $_: print "The position in \$a is ", pos($a):
6.15. Максимальный и минимальный поискПроблемаИмеется шаблон с максимальным квантификатором -*,+,? или {}. Требуется перейти от максимального поиска к минимальному. Классический пример - наивная подстановка для удаления тегов из HTML-документа. Хотя s#. *##gsi выглядит соблазнительно, в действительности будет удален весь текст от первого открывающего до последнего закрывающего тега ТТ. От строки "Even vi can edit troff effectively." остается лишь "Even effectively" - смысл полностью изменился!РешениеЗамените максимальный квантификатор соответствующим минимальным. Другими словами, *, +, ? или {} соответственно заменяются *?,+?,?? и {}?.КомментарийВ Perl существуют два набора квантификаторов: максимальные (*, +, ? и {}) и минимальные1 (*?, +?, ?? и {}?). Например, для строки "Perl is a Swiss Army Chainsaw!" шаблон/(г. *s)/совпадет с "rl is a Swiss Army Chains", а шаблон /(r.*?s)/-c "rl is". Также часто называемые "жадными" (greedy) и "скупыми" (stingy) квантификаторами. -Примеч. перев. Предположим, шаблон содержит максимальный квантификатор. При поиске подстроки, которая может встречаться переменное число раз (например, 0 и более раз для * или 1 и более раз для +), механизм поиска всегда предпочитает "и более". Следовательно, шаблон /foo. *bar/ совпадает от первого "too" до последнего "bar", а не до следующего "bar", как можно ожидать. Чтобы при поиске предпочтение отдавалось минимальным, а не максимальным совпадениям, поставьте после квантификатора вопросительный знак. Таким образом, *?, как и *, соответствует 0 и более повторений, но при этом выбирается совпадение минимальной, а не максимальной длины.# Максимальный поиск s/<.*>//gs; # Неудачная попытка удаления тегов # Минимальный поиск s/<.*?>//gs; # Неудачная попытка удаления тегов Показанное решение не обеспечивает правильного удаления тегов из HTML-документа, поскольку отдельное регулярное выражение не заменит полноценного анализатора. Правильное решение этой проблемы продемонстрировано в рецепте 20.6. Впрочем, с минимальными совпадениями дело обстоит не так просто. Не стоит ошибочно полагать, что BEGIN. *?END в шаблоне всегда соответствует самому короткому текстовому фрагменту между соседними экземплярами BEGIN и END. Возьмем шаблон /BEGIN(. *?)END/. После поиска в строке "BEGIN and BEGIN and END" переменная $1 будет содержать "and BEGIN and". Вероятно, вы рассчитывали на другой результат. Представьте, что мы хотим извлечь из HTML-документа весь текст, оформ ленный полужирным и курсивным шрифтом одновременно: bxi this /i> and are important Может показаться, что шаблон для поиска текста, находящегося между тегами HTML (то есть не включающий теги), должен выглядеть так: m{ bXL (.*?) /ix/b> }sx; Как ни странно, шаблон этого не делает. Многие ошибочно полагают, что он сначала находит последовательность " /BEGIN(C?:(?!BEGIN).)*)END/ Наш пример с тегами HTML выглядит примерно так: m{ или так: т{ <Ь><1>( (?: (?![ib]>). )* ) }sx; Как замечает Джеффри Фридл, это скороспелое решение не очень эффективно. В ситуациях, где скорость действительно важна, он предлагает воспользоваться более сложным шаблоном: m{b i ["<]* # Заведомо допустимо (?: # Символ '<' возможен, если он не входит в недопустимую конструкцию (?! ?[ib]>) # Недопустимо < # Все нормально, найти < ["<]* # и продолжить )* 1>Ь>}sx > Смотри также --------------------------------
6.16. Поиск повторяющихся словПроблемаТребуется найти в документе повторяющиеся слова.РешениеВоспользуйтесь обратными ссылками в регулярных выражениях.КомментарийМеханизм поиска запоминает часть строки, которая совпала с частью шаблона, заключенной в круглые скобки. Позднее в шаблоне обозначение \1 ссылается на первый совпавший фрагмент, \2 - на второй и т. д. Не используйте обозначение $1 - оно интерпретируется как переменная и интерполируется до начала поиска. Шаблон /([A-Z])\1/ совпадает с символом верхнего регистра, за которым следует не просто другой символ верхнего регистра, а именно тот, что был сохранен в первой паре скобок. Следующий фрагмент читает входной файл по абзацам. При этом используется принятое в Perl определение абзаца как фрагмента, заканчивающегося двумя и более смежными переводами строк. Внутри каждого абзаца находятся все но- вторяющиеся слова. Программа не учитывает регистр и допускает межстрочные совпадения. Модификатор /х разрешает внутренние пропуски и комментарии, упрощающие чтение регулярных выражений. Модификатор /i позволяет найти оба экземпляра "is" в предложении "Is is this ok?". Модификатор/д в цикле while продолжает поиск повторяющихся слов до конца текста. Внутри шаблона метасимволы \Ь (граница слова) и \s (пропуск) обеспечивают выборку целых слов.$/ = o o; while (<>) { while ( m{ \b (\S+) \b (\s+\1 \b ) + }xig } { print "dup word '$1' at paragraph $.\n"; } } Приведенный фрагмент найдет удвоенное test в следующем примере: This is a test test of the duplicate word funder. Проверка \8+ между двумя границами слов обычно нежелательна, поскольку граница слова определяется как переход между \w (алфавитно-цифровым символом или подчеркиванием) и либо концом строки, либо He-\w. Между двумя \Ь обычный смысл \8+ (один и более символов, не являющихся пропусками) распространяется до последовательности символов, не являющихся пропусками, первый и последний символ которой должны быть алфавитно-цифровыми символами или подчеркиваниями. Рассмотрим другой интересный пример использования обратных ссылок. Представьте себе два слова, причем конец первого совпадает с началом второго - например, "nobody" и "bodysnatcher". Требуется найти подобные "перекрытия" и сформировать строку вида "nobodysnatcher". Это вариация на тему нашей основной проблемы - повторяющихся слов. Чтобы решить эту задачу, программисту на С, привыкшему к традиционной последовательной обработке байтов, придется написать длинную и запутанную программу. Но благодаря обратным ссылкам задача сводится к одному простому поиску: $а = 'nobody';
if (($X, $Y, $Z) =
((oо' х 281) =~ /-(о+)\1{11}(о+)\2{14}(о+)\3{15}$/))
> Смотри также --------------------------------
|