Конец предыдущего или начало текущего совпадения?
Одно из различий между реализациями определяется тем, с чем же в действительности совпадает \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 совпадает в начале каждого текущего совпадения и решение об этом принимается после искусственного смещения позиции.
|
|
В решении этого вопроса не всегда можно доверять документации, прилагаемой к программе. Я обнаружил, что в документации Microsoft .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. Модификаторы режимов (?модификатор), например: (?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, \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; Мы поможем в написании вашей работы! |
Мы поможем в написании ваших работ!