Межстрочный поиск

Проблема

Требуется использовать регулярные выражения для последовательности, состоящей из нескольких строк. Специальные символы . (любой символ, кроме перевода строки), ^ (начало строки) и $ (конец строки), кажется, не работают. Это может произойти при одновременном чтении нескольких записей или всего содержимого файла.

Решение

Воспользуйтесь модификатором /m, /s или обоими сразу. Модификатор /s разрешает совпадение с переводом строки (обычно этого не происходит). Если последовательность состоит из нескольких строк, шаблон /foo. *bar/s совпадет с foo" и "bar", находящимися в двух соседних строках. Это не относится к точкам и символьных классах (например, [#%. ]), которые всегда представляют собой обычные точки.
Модификатор /m разрешает совпадение ^ и $ в переводах строк. Например, совпадение для шаблона /^=head[1-7]$/m возможно не только в начале записи, но и в любом из внутренних переводов строк.

Комментарий

При синтаксическом анализе документов, в которых переводы строк не имеют значения, часто используется «силовое» решение — файл читается по абзацам (а иногда даже целиком), после чего происходит последовательное извлечение лексем. Для успешного межстрочного поиска необходимо, чтобы символ . совпадал с переводом строки — обычно этого не происходит. Если в буфер читается сразу несколько строк, вероятно, вы предпочтете, чтобы символы ^ и $ совпадали с началом и концом внутренних строк, а не всего буфера.
Необходимо хорошо понимать, чем /m отличается от /s: первый заставляет ^ и $ совпадать на внутренних переводах строк, а второй заставляет . совпадать с переводом строки. Эти модификаторы можно использовать вместе, они не являются взаимоисключающими.
Фильтр из следующего примера удаляет теги HTML из всех файлов, переданных в @ARGV, и отправляет результат в STDOUT. Сначала мы отменяем разделение записей, чтобы при каждой операции чтения читалось содержимое всего файла. Если @ARGV содержит несколько аргументов, файлов также будет несколько. В этом случае при каждом чтении передается содержимое всего файла. Затем мы удаляем все открывающие и закрывающие угловые скобки и все, что находится между ними. Мы не можем просто воспользоваться .* по двум причинам: во-первых, этот шаблон не учитывает закрывающих угловых скобок, а во-вторых, он не поддерживает межстрочных совпадений. Проблема решается применением .*? в сочетании с модификатором /s — по крайней мере, в данном случае.
# killtags - очень плохое удаление тегов HTML
undef $/;        # При каждом чтении передается весь файл
while (<>) {     # Читать по одному файлу
  s/<.*?>//gs;   # Удаление тегов (очень скверное)
  print;         # Вывод файла в STDOUT
}
Шаблон s/<[^>]*>//g работает намного быстрее, но такой подход наивен: он приведет к неправильной обработке тегов в комментариях HTML или угловых скобок в кавычках (<IMG SRC="here.gif" ALT="<<Ooh la la! >>">).
Программа из следующего примера получает простой текстовый документ и ищет в начале абзацев строки вида "Chapter 20: Better Living Through Chemisery". Такие строки оформляются заголовками HTML первого уровня. Поскольку шаблон получился довольно сложным, мы воспользовались модификатором /х, который разрешает внутренние пропуски и комментарии.
# headerfy:  оформление заголовков глав в HTML
$/ ='';
while ( <> ) {        # Получить абзац
  s{
     \А               # Начало записи  
     (                # Сохранить в $1
       Chapter        # Текстовая строка
       \s+            # Обязательный пропуск
       \d+            # Десятичное число
       \s*            # Необязательный пропуск
       :              # Двоеточие
       .*             # Все, кроме перевода строки, до конца строки
     )
  }{<Н1>$1</Н1>}gх;
  print;
}
Если комментарии лишь затрудняют понимание, ниже тот же пример переписан короче:
s{\A(Chapter\s+\d+\s*:.*)}{<H1>$1</H1>}gxp
Возникает интересная проблема: в одном шаблоне требуется указывать как начало записи, так и конец строки. Начало записи можно было бы определить с помощью ^, но символ $ должен определять не только конец записи, но и конец строки. Мы добавляем модификатор /m, отчего изменяется смысл как ^, так и $. Начало записи вместо определяется с помощью \А. Кстати говоря, метасимвол \Z (хотя в нашем примере он не используется) совпадает с концом записи даже при наличии модификатора /m.
Следующий пример демонстрирует совместное применение /s и /m. На этот раз мы хотим, чтобы символ ^ совпадал с началом любой строки абзаца, а точка — с переводом строки. Эти модификаторы никак не связаны, и их совместное применение ничем не ограничено. Стандартная переменная $. содержит число записей последнего прочитанного файла. Стандартная переменная $ARGV содержит файл, автоматически открываемый при обработке <ARGV>.
$/='';     # Режим чтения абзацев
while (<ARGV>) {
  while (m#^START(.*?)^END#sm) { # /s - совпадение . с переводом строки
                                 # /m - совпадение ^ с началом внутренних строк
    print "chunk $. in $ARGV has <<$1>>\n";
  }
}
Если вы уже привыкли работать с модификатором /m, то ^ и $ можно заменить на \А и \Z. Но что делать, если вы предпочитаете /s и хотите сохранить исходный смысл .? Воспользуйтесь конструкцией [^\n]. Если вы не намерены использовать /s, но хотите иметь конструкцию, совпадающую с любым байтом, сконструируйте символьный класс вида [\000-\377] или даже [\d\D]. Использовать [.\n] нельзя, поскольку в символьных классах . не обладает особой интерпретацией.

См. также




2013-09-10 17:05:19

Proverte kod v komentariyah gde pro list tam oshibki detskie




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