Конец предыдущего или начало текущего совпадения?



Одно из различий между реализациями определяется тем, с чем же в действительности совпадает \G - с началом текущего или с концом предыдущего совпадения? В подавляющем большинстве случаев это одно и то же, но в некоторых редких случаях эти две позиции могут различаться. Суть проблемы проще понять на искусственном примере: применении вы­ражения x? к строке 'abcde'. Регулярное выражение может успешно совпасть в позиции '̭abcde', но текст в совпадении не участвует. При глобальном поиске с заменой регулярное выражение применяется по­вторно, причем если не принять особых мер, - с той позиции, в кото­рой закончилось предыдущее совпадение. Чтобы предотвратить за­цикливание, при обнаружении подобной позиции механизм регуляр­ных выражений принудительно переходит к следующему символу \G. В этом легко убедиться, применив команду s/x?/!/g к строке 'abcde', - вы получите строку '!a!b!c!d!e!’.

Один из побочных эффектов подобного перевода позиции заключается в том, что «конец предыдущего совпадения» после этого отличается от «начала текущего совпадения». В таких случаях возникает вопрос: с ка­кой из двух позиций совпадает \Gj В Perl применение команды s\Gx?/!/g к строке 'abcde' порождает строку '! abcde', следовательно, в Perl \G на самом деле совпадает только с концом предыдущего совпадения. При искусственном смещении позиции \G гарантированно терпит неудачу.

С другой стороны, применение той же команды в ряде других про­грамм приводит к результату '!a!b!c!d!e\ из чего следует, что \G со­впадает в начале каждого текущего совпадения и решение об этом при­нимается после искусственного смещения позиции.

В решении этого вопроса не всегда можно доверять документации, прилагаемой к программе. Я обнаружил, что в документации Micro­soft .NET и пакета Java. util. regex были приведены неверные сведе­ния, о чем я сообщил разработчикам (после чего данное упущение бы­ло исправлено). Тестирование показало, что в РНР и Ruby \G соответ­ствует началу текущего совпадения, а в Perl. java. util. regex и языках .NET - концу предыдущего совпадения.

4.Границы слов: \b, \B, \<, \>

Эти метасимволы совпадают не с символом, а с определенной пози­цией в строке. Существует два разных подхода. В одном позиция нача­ла и конца слова обозначается разными метасимволами (обычно \< и\>), а в другом определяется единая метапоследовательность (обычно \b). В обоих случаях также обычно определяется метапоследовательность для любой позиции, не явля­ющейся границей слова (обычно \В). Программы, которые не различают якорные метасимволы для начала и конца слова, но поддерживают опережающую проверку, могут имитировать их при помощи опережающей проверки.

Граница слова обычно определяется как позиция, с одной стороны от которой находится «символ слова», а с другой - символ, не относя­щийся к этой категории. У каждой программы имеются свои пред­ставления о том, что следует считать «символом слова» с точки зрения границ слов. Было бы логично, если бы границы слова определялась по метасимволам \w, но это не всегда так. Например, в пакете регуляр­ных выражений для Java от Sun \w относится только к символам коди­ровки ASCII вместо Юникода, поддерживаемого в Java, поэтому в таб­лице используется опережающая проверка со свойством Юникода \pL (которое является сокращенной формой \р{L}.

В любом случае проверка границы слова всегда сводится к проверке со­седних символов. Ни один механизм регулярных выражений не при­нимает решений на основе лексического анализа - строка «NE14AD8» везде считается словом, а «М.I.Т.» к словам не относится.

 

 

5. Опережающая и ретроспективная проверки (?=…), (?!...), (?<=…), (?<!...)

Одна из разновидностей позиционной проверки - опережающая про­верка - анализирует текст, расположенный справа, и проверяет воз­можность совпадения подвыражения. Если совпадение возможно, проверка считается успешной. Позитивная опережающая проверка задается специальной последовательностью (?=…) Например, подвы­ражение (?=\d) совпадает в тех позициях, за которыми следует циф­ра. Также существует ретроспективная проверка, при которой текст анализируется в обратном направлении (к левому краю). Ретроспек­тивная проверка задается специальной последовательностью (?<=…) Например, подвыражение (?<=\d) совпадает в тех позициях, слева от которых находится цифра (т. е. в позиции после цифры).

Какие выраже­ния могут использоваться в обеих разновидностях ретроспективной проверки? В большинстве реализаций устанавливается ограничение на длину текста при ретроспективной проверке (но для опережающей проверки длина текста не ограничивается!).

Самые жесткие правила действуют в Perl и Python, где ретроспективная проверка ограничивается строками фиксированной длины. Например, конструкции (?<!\w) и (?<! this|that) допустимы, а (?<! books?) и (?<!^\w+:) запрещены, поскольку они могут совпадать с текстом переменной дли­ны. В таких случаях, как с (?<! books?), задача решается выражением (?<! book)(?<! books)], но понять его с первого взгляда весьма непросто.

На следующем уровне поддержки в ретроспективной проверке допус­каются альтернативы разной длины, поэтому выражение (?<! books?) может быть записано в виде (?<! book | books). PCRE (и все функции preg в РНР) это позволяют.

На следующем уровне допускаются регулярные выражения, совпадаю­щие с текстом переменной, но конечной длины. Например, выражение (?<! books?) разрешено, а выражение (?<!~\w+:) запрещено, поскольку совпадение \w+ может иметь непредсказуемую длину. Этот уровень поддерживается пакетом регулярных выражений для Java от Sun.

Справедливости ради стоит заметить, что первые три уровня в дейст­вительности эквивалентны, потому что все они могут быть выражены, хотя и несколько громоздко, средствами минимального уровня (с фик­сированной длиной). Остальные уровни - всего лишь «синтаксическая обертка», позволяющая сделать то же самое более удобным способом. Впрочем, существует и четвертый уровень, при котором подвыраже­ние в ретроспективной проверке может совпадать с произвольным объемом текста, поэтому выражение (?<!~\w+:) является разрешен­ным. Этот уровень, поддерживаемый языками платформы .NET от Microsoft, принципиально превосходит остальные уровни, однако его неразумное применение может серьезно замедлить работу программы (столкнувшись с ретроспективой, которая может совпадать с текстом произвольной длины, механизм вынужден проверять условие от нача­ла строки, что требует огромного объема лишней работы при проверке в конце длинного текста).

  1. Комментарии и модификаторы режимов.

1. Модификаторы режимов (?модификатор), например: (?i) или (?-i)

Многие современные диалекты позволяют менять режимы поиска и форматирования непосредственно на уровне регулярного выражения. Самый распространенный пример - конструкции (?i) (включение поиска без учета регистра) и (?-i) (отключение). Напри­мер, в выражении <B>(?i)very(?-i)</B> совпадение для very ищется без учета регистра символов, но в совпадении для имен тегов регистр учитывается. Например, выражение может совпасть с '<B>VERY</B>' и '<B>Very</B>\ но не с '<b>Very</b>\

Приведенный пример работает в большинстве систем с поддержкой (?i)j включая Perl, java.util.regex, Ruby и языки .NET. Он не будет работать ни в Python, ни в Tel, которые не поддерживают (?-i).

В большинстве реализаций действие конструкции (?i), заключенной в круглые скобки, ограничивается этими скобками (т. е. за пределами скобок режим автоматически отключается). Это позволяет просто убрать (?-i) из выражения и поместить (?i) сразу после открываю­щей скобки: <В>(?: (?i)very)</B>.

Возможности модификации режимов не ограничиваются поиском без учета регистра (i). В большинстве систем поддерживаются модифика­торы, перечисленные в табл. ниже. Отдельные системы вводят допол­нительные символы для расширения круга решаемых задач. В част­ности, существует множество дополнительных модификаторов в РНР, а также в Tel.

Обозначение Режим
i Поиск без учета регистра символов
x Свободное форматирование и комментарии
s Режим совпадения точки со всеми символами
m Расширенный режим привязки к границам строк

2. Интервальное изменение режима (?модификатор:…), например: (?i:)

Интерваль­ные модификаторы имеют синтаксис (?i:-) и активизируют режим только для части выражения, заключенной в круглые скобки. В этом случае пример <В>(?: (?i)very)</B> упрощается до <В>(?i: very)</B>.

Там, где этот синтаксис поддерживается, он обычно может использо­ваться со всеми буквенными обозначениями модификаторов. В Tel и Python поддерживается форма (?i), но отсутствует интервальный модификатор (?i :-).

3. Комментарии: (?#...) и #...

Некоторые диалекты поддерживают комментарии в форме (?#...) На практике этот синтаксис используется редко, обычно программисты предпочитают режим свободного форматирования . Тем не ме­нее этот тип комментариев удобен в языках, синтаксис которых за­трудняет включение символов новой строки в строковые литералы (например, VB.NET).

4. Литеральный текст: \Q…\E

Специальная последовательность \Q...\E впервые появилась в Perl. Все метасимволы, следующие после \Q, интерпретируются как литералы (разумеется, кроме \Е; если последний отсутствует, специальная ин­терпретация метасимволов подавляется до конца регулярного выра­жения). Таким образом, последовательности символов, обычно вос­принимаемые как метасимволы, интерпретируются как обычный текст. Данная возможность чаще всего используется при подстановке содержимого переменных при построении регулярных выражений.

Допустим, при поиске в WWW критерий, полученный от пользовате­ля, был сохранен в переменной $query, и вы хотите провести поиск по этому критерию командой m/$query/i. Если $query содержит текст вро­де 'C:\WINDOWS\ это приведет к неожиданным результатам - произой­дет ошибка времени выполнения, поскольку в условии поиска присут­ствует конструкция, недопустимая в регулярном выражении (завер­шающий одиночный символ \).

В Perl проблема решается командой m/\Q$query\E/i; 'C:\WINDOWS\’ фак­тически превращается в C:\\WINDOWS\\, а в результате поиска будет найден текст 'C:\WINDOWS\’ как и ожидает пользователь.

Данная возможность приносит меньше пользы в системах с процедур­ным и объектно-ориентированным интерфейсом, поскольку в них регулярные выражения определяются в виде обычных строк. При построении строки можно без особого труда вызвать функцию, которая «защищает» содержимое переменной и делает его безопасным для использования в регулярном выражении. Например, в VB для этой цели вызывается метод Regex. Escape, в РНР используется функция preg_quote, в Java - метод quote.

В настоящее время из всех известных мне механизмов регулярных вы­ражений полная поддержка \Q…\E реализована только в пакете Java, util. regex от Sun и в PCRE (т. е. в семействе функций preg языка РНР). Но ведь я только что упомянул, что конструкция \Q…\E впервые по­явилась в Perl (а также привел пример на Perl) - так почему Perl не включен в это утверждение? Дело в том, что Perl поддерживает \Q---\E в литералах (регулярных выражениях, задаваемых непосредственно в программе), но не в содержимом переменных, которые могут в них интерполироваться.

Пакет java. util. regex поддерживает использование конструкции \Q…\E внутри символьных классов. Реализация ее в версиях Java до 1.6.0 со­держит ошибку, поэтому данная конструкция не должна использо­ваться в этих версиях Java.

  1. Группировка, сохранение, условные и управляющие конструкции.

1. Сохраняющие круглые скобки: (…), \1, \2 …

Стандартные круглые скобки обычно выполняют две функции: груп­пировку и сохранение. Они почти всегда включаются в выражения в виде (…), но в ряде диалектов используется запись \(…\) К их числу относятся диалекты GNU Emacs, sed, vi и grep.

Сохраняющие скобки идентифицируются по порядковому номеру от­крывающей скобки от левого края выражения. Если в диалекте поддерживаются обратные ссылки, то на текст, совпавший с подвыражением в круглых скобках, можно ссылаться в том же регулярном выражении при помощи метасимволов \1, \2 и т. д.

Чаще всего круглые скобки применяются для извлечения данных из строки. Текст, совпавший с подвыражением в круглых скобках (для краткости - «текст, совпавший с круглыми скобками»), остается до­ступным после применения выражения, при этом в разных програм­мах используется разный синтаксис (например, в Perl - переменные $1, $2 и т. д.). Многие программисты ошибочно пытаются использовать \1 за пределами регулярного выражения; это возможно только в sed и vi.

                                                        

2. Группирующие круглые скобки: (?:...)

Группирующие круглые скобки (?:…) не сохраняют совпавший текст, а лишь группируют компоненты регулярных выражений в конструкции выбора и при применении квантификаторов. Группирующие круглые скобки не учитываются при нумерации переменных $1, $2 и т. д. Напри­мер, после успешного совпадения выражения (1|one)(?:and|or)(2|two) переменная $1 содержит 1 или one, a $2 содержит 2 или two. Группи­рующие круглые скобки также называются несохраняющими.

Несохраняющие круглые скобки полезны по целому ряду причин. Они упрощают применение сложных регулярных выражений, позво­ляя читателю не думать о том, будет ли совпавший текст использо­ваться в других местах конструкциями типа $1. Кроме того, они повы­шают эффективность - механизму регулярных выражений не нужно сохранять совпавший текст, поэтому он быстрее работает и расходует меньше памяти (вопросы эффективности подробно рассматриваются в главе 6).

Несохраняющие круглые скобки также используются при построении регулярных выражений из отдельных компонентов

3. Именованное сохранение: (?<Имя>…)

Python и языки .NET позволяют сохранить текст, совпавший с круг­лыми скобками, под заданным именем. В Python используется син­таксис (?Р<имя>…), а в языках .NET - синтаксис (?<имя>…)

Пример для .NET:

\b(?<Area>\d\d\d)-(?<Exch>\d\d\d)-(?<Num>\d\d\d\d)\b

и для Python/PHP:

\b(?P<Area>\d\d\d\)-(?P<Exch>\d\d\d)-(?P<Num>\d\d\d\d)\b

Приведенное выражение ассоциирует компоненты телефонного номе­ра с именами Area, Exch и Num, после чего на совпавший текст можно ссылаться по имени. Так, в VB.NET и большинстве других языков .NET используется синтаксис RegexObj.Groups("Area"), в С# - Regfex-Obj.Groups["AreaM], в Python - ite£exO&/.groups(,,Area"), а в PHP $matches["Area"]. Работа с данными по именам делает программу бо­лее понятной.

Внутри регулярного выражения ссылки на совпавший текст имеют вид \k<Area> в .NET и (?P=Area) в Python и РНР.

В Python и .NET (но не в РНР) допускается многократное использова­ние имен в выражениях. Например, если код междугородной связи в телефонном номере записывается в виде '(###)' или '###-', для его поиска можно воспользоваться следующим выражением (исполь­зован синтаксис .NET): …(?:\((?<Area>\d\d\d)\)|(?<Area>\d\d\d-)… Ка­кая бы из альтернатив ни совпала, код из трех цифр будет ассоцииро­ван с именем Area.

4. Атомарная группировка: (?>…)

Конструкция атомарной группировки (?>-) легко объясняется после знакомства с основными принципами работы механизмов регулярных выражений. Сейчас я скажу только, что текст, совпадающий с подвыражением в круглых скобках, фиксируется (становится ато­марным, т. е. неизменяемым) для оставшейся части совпадения, если позднее не окажется, что все совпадение для атомарных круглых ско­бок не должно быть отброшено или вычислено заново. Неделимую, «атомарную» природу совпадения проще всего пояснить конкретным примером.

Регулярное выражение ¡. *! совпадает с текстом ¡Hola!  ,но эта строка не совпадет, если выражение . * заключено в конструкцию атомарной группировки: ¡(?>. *)! . В обоих случаях . * сначала распространяется на текст максимальной длины '¡Hola!', но в первом случае восклица­тельный знак заставляет . * уступить часть найденных символов (по­следний символ '!'), чтобы обеспечить общее совпадение. Во втором случае . * находится внутри атомарной конструкции, которая нико­гда и ничего не уступает; на долю завершающего знака «! » ничего не остается, поэтому общее совпадение невозможно.

Атомарная группировка находит ряд важных применений, хотя из данного примера трудно сделать подобный вывод. В частности, ато­марная группировка повышает эффективность поиска и поз­воляет с высокой степенью точности управлять тем, что может или не может считаться совпадением.

5. Конструкция выбора: …|…|…

Конструкция выбора проверяет наличие совпадений для нескольких подвыражений с заданной позиции. Каждое подвыражение называет­ся альтернативой. Символ | можно называть по-разному, но чаще всего его называют ИЛИ или вертикальная черта. В некоторых диа­лектах вместо символа  | используется метапоследовательность \ | .

Практически во всех диалектах конструкция выбора обладает очень низким приоритетом. Это означает, что выражение this and|or that эквивалентно (this and) I (or that), a He this (and|or) that, хотя визу­ально последовательность and|or воспринимается как единое целое.

В большинстве диалектов допускаются пустые альтернативы вида (this| that|). Пустое подвыражение означает «совпадение всегда», так что пример логически сравним с (this|that )?.

Стандарт POSIX запрещает пустые альтернативы, они не поддержива­ются в lex и большинстве версий awk

6. Условная конструкция: (? if then | else)

Условная конструкция позволяет реализовать стандартную логику if/then/else на уровне регулярного выражения. Часть if содержит особую разновидность условного выражения, рассматриваемую ниже, a then и else представляют собой обычные подвыражения. Если условие if ис­тинно, применяется выражение then, а если ложно - выражение else. Часть else может отсутствовать, в этом случае символ | также необя­зателен.

Круг допустимых значений if зависит от диалекта, но в большинстве реализаций поддерживаются как минимум специальные ссылки на сохраняющие подвыражения и конструкции позиционной проверки.


Дата добавления: 2018-04-04; просмотров: 333; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!