3.8. Классы символов
Классы символов - это просто форма перечисления (указание альтернатив), в котором каждая группа состоит из одного символа. В простейшем случае список возможных символов заключается в квадратные скобки:
/[aeiou]/ # Соответствует любой из букв а, е, i, о, и; эквивалентно
# /(a|e|i|o|u)/, только группа не запоминается.
Внутри класса символов управляющие последовательности типа \n по-прежнему распознаются, но такие метасимволы, как . и ?, не имеют специального смысла:
/[.\n?]/ # Сопоставляется с точкой, символом новой строки,
# вопросительным знаком.
Символ каре (^) внутри класса символов имеет специальный смысл, если находится в начале; в этом случае он формирует дополнение к списку символов:
[^aeiou] # Любой символ, КРОМЕ а, е, i, о, и.
Дефис внутри класса символов обозначает диапазон (в лексикографическом порядке):
/[а-mA-М]/ # Любой символ из первой половины алфавита.
/[^а-mA-М]/ # Любой ДРУГОЙ символ, а также цифры и символы. отличные
# от букв и цифр.
Дефис в начале или в конце класса символов, а также каре в середине теряют специальный смысл и интерпретируются буквально. То же относится к левой квадратной скобке, но правая квадратная скобка, очевидно, должна экранироваться:
/[-^[\]]/ # Сопоставляется с дефисом, каре и правой квадратной скобкой.
Регулярные выражения в Ruby могут содержать ссылки на именованные классы символов вида [[:name:]]. Так, [[:digit:]] означает то же самое, что образец [0-9]. Во многих случаях такая запись оказывается короче или, по крайней мере, понятнее.
Есть еще такие именованные классы: [[:print:]] (символы, имеющие графическое начертание) и [[:alpha:]] (буквы):
s1 = "abc\007def"
/[[:print:]]*/.match(s1)
m1 = Regexp::last_match[0] # "abc"
s2 = "1234def"
/[[:digit:]]*/.match(s2)
m2 = Regexp::last_match[0] # "1234"
/[[:digit:]] + [[:alpha:]]/.match(s2)
m3 = Regexp::last_match[0] # "1234d"
Каре перед именем класса символов формирует его дополнение:
/[[:^alpha:]]/ # Все символы, кроме букв.
Для многих классов имеется также сокращенная нотация. Наиболее распространены сокращения \d (любая цифра), \w (любой символ, входящий в состав "слова") и \s (пропуски - пробел, знак табуляции или новой строки):
str1 = "Wolf 359"
/\w+/.match(str1) # Соответствует "Wolf" (то же, что /[a-zA-Z_0-9]+/)
/\w+ \d+/.match(str1) # Соответствует "Wolf 359"
/\w+ \w+/.match(str1) # Соответствует "Wolf 359"
/\s+/.match(str1) # Соответствует " "
"Дополнительные" формы обычно записываются в виде прописной буквы:
/\W/ # Любой символ, не входящий в состав слова.
/\D/ # Все кроме цифр.
/\S/ # Все кроме пропусков.
Дополнительная информация, относящаяся только к Oniguruma, приводится в разделе 3.13.
3.9. Обобщенные регулярные выражения
Регулярные выражения, особенно длинные, часто выглядят загадочно. Модификатор x позволяет записывать регулярное выражение на нескольких строках. При этом пробелы и символы новой строки игнорируются, так что можно делать для наглядности отступы. Заодно разрешается оставлять комментарии, хотя это возможно даже в простых регулярных выражениях.
Чтобы привести несколько искусственный пример умеренно сложного регулярного выражения, предположим, что имеется такой список адресов:
addresses =
[ "409 W Jackson Ave", "No. 27 Grande Place",
"16000 Pennsylvania Avenue", "2367 St. George St.",
"22 Rue Morgue", "33 Rue St. Denis",
"44 Rue Zeeday", "55 Santa Monica Blvd.",
"123 Main St., Apt. 234", "123 Main St., #234",
"345 Euneva Avenue, Suite 23", "678 Euneva Ave, Suite A"]
Здесь каждый адрес состоит из трех частей: номер дома, название улицы и необязательный номер квартиры. Я предполагаю, что перед числом может быть необязательная строка No., а точку в ней можно опускать. Еще предположим, что название улицы может включать символы, обычно входящие в состав слова, а также апостроф, дефис и точку. Наконец, если адрес содержит необязательный номер квартиры, то ему должны предшествовать запятая и одна из строк Apt., Suite или # (знак номера).
Вот какое регулярное выражение я составил для разбора адреса. Обратите внимание, насколько подробно оно прокомментировано (может быть, даже излишне подробно):
regex = / ^ # Начало строки.
((No\.?)\s+)? # Необязательно: No[.]
\d+ \s+ # Цифры и пробелы.
((\w|[.'-])+ # Название улицы... может
\s* # состоять из нескольких слов.
)+
(,\s* # Необязательно: запятая и т.д.
(Apt\.?|Suite|\#) # Apt[.], Suite, #
\s+ # Пробелы.
(\d+|[A-Z]) # Цифры или одна буква.
)?
$ # Конец строки.
/x
Идея понятна. Когда сложность регулярного выражения достигает некоего порога (какого именно - дело вкуса), делайте его обобщенным, чтобы можно было добавить форматирование и комментарии.
Возможно, вы заметили, что я пользовался обычными комментариями Ruby (# ...), а не специальными, применяемыми в регулярных выражениях ((?#...)). Почему? Просто потому, что это разрешено! Специальный комментарий необходим только тогда, когда его следует закончить раньше конца строки (например, если в той же строке за комментарием продолжается регулярное выражение).
3.10. Сопоставление точки символу конца строки
Обычно точка соответствует любому символу, кроме конца строки. Если задан модификатор многострочности m, точка будет сопоставляться и с этим символом. Другой способ - задать флаг Regexp::MULTILINE при создании регулярного выражения:
str = "Rubies are red\nAnd violets are blue.\n"
pat1 = /red./
pat2 = /red./m
str =~ pat1 # nil
str =~ pat2 # 11
Этот режим не оказывает влияния на то, где устанавливается соответствие якорям (^, $, \A, \Z). Изменяется только способ сопоставления с точкой.
3.11. Внутренние модификаторы
Обычно модификаторы (например, i или m) задаются после регулярного выражения. Но что если мы хотим применить модификатор только к части выражения?
Существует специальная нотация для включения и выключения модификаторов. Заключенный в круглые скобки вопросительный знак, за которым следует один или несколько модификаторов, "включает" их до конца регулярного выражения. А если некоторым модификаторам предшествует минус, то соответствующие режимы "выключаются":
/abc(?i)def/ # Соответствует abcdef, abcDEF, abcDef,
# но не ABCdef.
/ab(?i)cd(?-i)ef/# Соответствует abcdef, abCDef, abcDef, ...,
# но не ABcdef или abcdEF.
/(?imx).*/ # To же, что /.*/imx
/abc(?i-m).*/m # Для последней части регулярного выражения включить
# распознавание регистра, выключить многострочный
# режим.
При желании можно поставить перед подвыражением двоеточие, и тогда заданные модификаторы будут действовать только для этого подвыражения:
/ab(?i:cd)ef/ # То же, что /ab(?i)cd(?-i)ef/
По техническим причинам использовать таким образом модификатор о нельзя. Модификатор x - можно, но я не знаю, кому бы это могло понадобиться.
3.12. Внутренние подвыражения
Для указания подвыражений применяется нотация ?>:
re = /(?>abc)(?>def)/ # То же, что /abcdef/
re.match("abcdef").to_a # ["abcdef"]
Отметим, что наличие подвыражения еще не означает группировки. С помощью дополнительных скобок их, конечно, можно превратить в запоминаемые группы.
Еще обратим внимание на то, что эта конструкция собственническая, то есть жадная и при этом не допускает возврата в подвыражение.
str = "abccccdef"
re1 = /(abc*)cdef/
re2 = /(?>abc*)cdef/
re1 =~ str # 0
re2 =~ str # nil
re1.match(str).to_a # ["abccccdef", "abccc"]
re2.match(str).to_a # []
В предыдущем примере подвыражение abc* выражения re2 поглощает все вхождения буквы с и (в соответствии с собственническим инстинктом) не отдает их назад, препятствуя возврату.
3.13. Ruby и Oniguruma
Новая библиотека регулярных выражений в Ruby называется Oniguruma. Это японское слово означает что-то вроде "колесо духов". (Те, кто не владеет японским, часто пишут его неправильно; имейте в виду, что тут не обойтись без "guru"!)
Новая библиотека превосходит старую в нескольких отношениях. Прежде всего, она лучше работает с иноязычными строками, а также добавляет кое-какие интересные возможности к регулярным выражениям. Наконец, лицензия на ее использование мягче, чем на использование Ruby в целом. Когда писалась эта книга, Oniguruma еще не была полностью интегрирована в Ruby.
В следующем разделе мы расскажем, как определить, присутствует ли библиотека Oniguruma. А затем покажем, как можно ее собрать, если она не включена в дистрибутив.
3.13.1. Проверка наличия Oniguruma
Если вас интересует библиотека Oniguruma, то первым делом нужно выяснить, есть ли она в вашем экземпляре Ruby. В версиях 1.8.4 и младше ее, скорее всего, нет. Стандартно она включается в дистрибутив версии 1.9.
Вот как можно без труда выяснить, присутствует ли Oniguruma, проверив три условия. Во-первых, как я сказал, она стандартно поставляется в версии 1.9 и старше. В последних версиях обеих библиотек для работы с регулярными выражениями определена строковая константа Regexp::ENGINE. Если она содержит подстроку Oniguruma, то у вас новая библиотека. И последний шаг: если вы все еще не знаете, с какой библиотекой работаете, можно попытаться вычислить регулярное выражение, записанное в "новом" синтаксисе. Если при этом возникнет исключение SyntaxError, значит, у вас старая библиотека; в противном случае - новая.
def oniguruma?
return true if RUBY_VERSION >= "1.9.0"
if defined?(Regexp::ENGINE) # Константа ENGINE определена?
if Regexp::ENGINE.include?('Oniguruma')
return true # Какая-то версия Oniguruma.
else
return false # Старая библиотека,
end
end
eval("/(?<!a)b/") # Новый синтаксис.
return true # Сработало: новая библиотека.
rescue SyntaxError # Не сработало: старая библиотека.
return false
end
puts oniguruma?
3.13.2. Сборка Oniguruma
Если в вашу версию библиотека Oniguruma не включена, можете самостоятельно откомпилировать Ruby и скомпоновать с недостающей библиотекой. Ниже приведены соответствующие инструкции. Эта процедура должна работать начиная с версии 1.6.8 (хотя она уже совсем старенькая).
Получить исходный текст Oniguruma можно из архива приложений Ruby RAA (http://raa.ruby-lang.org/) или найти в другом месте. Исходные тексты Ruby, естественно, находятся на официальном сайте.
Если вы работаете на платформе UNIX (в том числе в среде Cygwin в Windows или Mac OS/X), выполните следующие действия:
1. gunzip oniguruma.tar.gz
2. tar xvf oniguruma.tar
3. cd oniguruma
4. ./configure with-rubydir=<ruby-source-dir>
5. Одно из следующих:
make 16 # Для Ruby 1.6.8
make 18 # Для Ruby 1.8.0/1.8.1
6. cd ruby-source-dir
7. ./configure
8. make clean
9. make
10. make test # Простой тест интерпретатора Ruby.
11. cd ../oniguruma # Укажите путь к библиотеке.
12. make rtest
Или:
make rtest RUBYDIR=ruby-install-dir
Если же вы работаете на платформе Win32, скажем в Windows XP, то потребуются Visual C++ и исполняемый файл patch.exe. Выполните следующие действия:
1. Распакуйте архив любой имеющейся у вас программой.
2. copy win32\Makefile Makefile
3. Одно из следующих:
nmake 16 RUBYDIR=ruby-source-dir # для Ruby 1.6.8
nmake 18 RUBYDIR=ruby-source-dir # для Ruby 1.8.0/1.8.1
4. Следуйте инструкции в файле ruby-source-dir\win32\README.win32.
При возникновении ошибок обратитесь в список рассылки или конференцию.
3.13.3. Некоторые новые возможности Oniguruma
Oniguruma добавляет много новых возможностей к механизму работы с регулярными выражениями в Ruby. Из самых простых отметим дополнительную управляющую последовательность для указания класса символов. Если \d и \D соответствуют десятичным цифрам и не цифрам, то \h и \H являются аналогами для шестнадцатеричных цифр:
"abc" =~ /\h+/ #0
"DEF" =~ /\h+/ # 0
"abc" =~ /\Н+/ # nil
Добавилось возможностей у классов символов в квадратных скобках. Для организации вложенных классов можно применять оператор &&. Вот как можно записать регулярное выражение, соответствующее любой букве, кроме гласных а, е, i, о, u:
reg1 = /[a-z&&[^aeiou]]/ # Задает пересечение.
А следующее выражение соответствует всему алфавиту, кроме букв от m до p:
reg2 = /[a-z&&[^m-р]]/
Поскольку такие выражения выглядят не очень понятно, рекомендую пользоваться этим средством осмотрительно.
Другие возможности Oniguruma, например оглядывание назад и именованные соответствия, будут рассмотрены ниже. Все связанное с интернационализацией отложим до главы 4.
3.13.4 Позитивное и негативное оглядывание назад
Если заглядывания вперед вам недостаточно, то Oniguruma предлагает еще и оглядывание назад, позволяющее определить, предшествует ли текущему положению заданный образец.
Как и многое другое в регулярных выражениях, эту возможность довольно трудно понять и обосновать. Спасибо Эндрю Джексону за следующий пример.
Предположим, что вам нужно проанализировать некоторую генетическую последовательность (молекула ДНК состоит из четырех основных белков, которые обозначаются А, С, G и T.) Допустим, что мы ищем все неперекрывающиеся цепочки нуклеотидов (длины 4), следующие за T. Нельзя просто попытаться найти T и взять следующие четыре символа, поскольку T может быть последним символом в предыдущем соответствии.
gene = 'GATTACAAACTGCCTGACATACGAA'
seqs = gene.scan(/T(\w{4})/)
# seqs равно: [["TACA"], ["GCCT"], ["ACGA"]]
Ho в этом коде мы пропустили цепочку GACA, которая следует за GCCT. Позитивное оглядывание назад позволит найти все нужные цепочки:
gene = 'GATTACAAACTGCCTGACATACGAA'
seqs = gene.scan(/(?<=T)(\w{4})/)
# seqs равно: [["TACA"], ["GCCT"], ["GACA"], ["ACGA"]]
Следующий пример - небольшая модификация примера, предложенного К. Косако (К. Kosako). Предположим, что есть текст в формате XML (или HTML), и мы хотим перевести в верхний регистр весь текст вне тегов (то есть cdata) Вот как можно сделать это с помощью оглядывания назад:
text =<<-EOF
<body> <h1>This is a heading</h1>
<p> This is a paragraph with some
<i>italics</i> and some <b>boldface</b>
in it...</p>
</body>
EOF
pattern = /(?:^| # Начало или...