Главная Обратная связь

Дисциплины:






Позитивное и негативное заглядывание вперед



Понятно, что регулярное выражение сопоставляется со строкой линейно (осуществляя при необходимости возвраты). Поэтому существует понятие «текущего положения» в строке, это аналог указателя файла или курсора.

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

В следующем примере строка "New world" будет сопоставлена, если за ней следует одна из строк "Symphony" или "Dictionary". Однако третье слово не будет частью соответствия.

s1 = "New World Dictionary"

s2 = "New World Symphony"

s3 = "New World Order"

 

reg = /New World(?= Dictionary | Symphony)/

m1 = reg.match(s1)

m.to_a[0] # "New World"

m2 = reg.match(s2)

m.to_a[0] # "New World"

m3 = reg.match(s3) # nil

Вот пример негативного заглядывания:

reg2 = /New World(?! Symphony)/

m1 = reg.match(s1)

m.to_a[0] # "New World"

m2 = reg.match(s2)

m.to_a[0] # nil

m3 = reg.match(s3) # "New World"

В данном случае строка "New world" подходит, только если за ней не следует строка "Symphony".

Обратные ссылки

Каждая заключенная в круглые скобки часть регулярного выражения является отдельным соответствием. Они нумеруются, и есть несколько способов сослаться на такие части по номерам. Сначала рассмотрим традиционный «некрасивый» способ.

Сослаться на группы можно с помощью глобальных переменных $1, $2 и т.д:

str = "а123b45с678"

if /(a\d+)(b\d+)(c\d+)/ =~ str

puts "Частичные соответствия: '#$1', '#$2', '#$3'"

# Печатается: Частичные соответствия: 'а123', 'b45', 'c768'

end

Эти переменные нельзя использовать в подставляемой строке в методах sub и gsub:

str = "а123b45с678"

str.sub(/(a\d+)(b\d+)(c\d+)/, "1st=#$1, 2nd=#$2, 3rd=#$3")

# "1st=, 2nd=, 3rd="

Почему такая конструкция не работает? Потому что аргументы sub вычисляются перед вызовом sub. Вот эквивалентный код:

str = "а123b45с678"

s2 = "1st=#$1, 2nd=#$2, 3rd=#$3"

reg = /(a\d+)(b\d+)(c\d+)/

str.sub(reg,s2)

# "1st=, 2nd=, 3rd="

Отсюда совершенно понятно, что значения $1, $2, $3 никак не связаны с сопоставлением, которое делается внутри вызова sub.

В такой ситуации на помощь приходят специальные коды \1, \2 и т.д.:

str = "а123b45с678"

str.sub(/(a\d+)(b\d+)(c\d+)/, '1st=\1, 2nd=\2, 3rd=\3')



# "1st=a123, 2nd=b45, 3rd=c768"

Обратите внимание на одиночные (твердые) кавычки в предыдущем примере. Если бы мы воспользовались двойными (мягкими) кавычками, не приняв никаких мер предосторожности, то элементы, которым предшествует обратная косая черта, были бы интерпретированы как восьмеричные числа:

str = "а123b45с678"

str.sub(/(a\d+)(b\d+)(c\d+)/, "1st=\1, 2nd=\2, 3rd=\3")

# "1st=\001, 2nd=\002, 3rd=\003"

Обойти эту неприятность можно за счет двойного экранирования:

str = "а123b45с678"

str.sub(/(a\d+)(b\d+)(c\d+)/, "1st=\\1, 2nd=\\2, 3rd=\\3")

# "1st=a123, 2nd=b45, 3rd=c678"

Допустима и блочная форма подстановки, в которой можно использовать глобальные переменные:

str = "а123b45с678"

str.sub(/(a\d+)(b\d+)(c\d+)/) { "1st=#$1, 2nd=#$2, 3rd=#$3" }

# "1st=a123, 2nd=b45, 3rd=c678"

При таком применении блока числа с обратной косой чертой нельзя использовать ни в двойных, ни в одиночных кавычках. Если вы немного поразмыслите, то поймете, что это разумно.

Упомяну попутно о том, что существуют незапоминаемые группы (noncapturing groups). Иногда при составлении регулярного выражения нужно сгруппировать символы, но чему будет соответствовать в конечном счете такая группа, несущественно. На этот случай и предусмотрены незапоминаемые группы, описываемые синтаксической конструкцией (?:...):

str = "а123b45с678"

str.sub(/(a\d+)(?:b\d+)(c\d+)/, "1st=\\1, 2nd=\\2, 3rd=\\3")

# "1st=a123, 2nd=c678, 3rd="

В предыдущем примере вторая группа не запоминается, поэтому та группа, которая должна была бы быть третьей, становится второй.

Лично мне не нравится ни одна из двух нотаций (\1 и $1). Иногда они удобны, но никогда не бывают необходимы. Все можно сделать «красивее», в объектно-ориентированной манере.

Метод класса Regexp.last_match возвращает объект класса MatchData (как и метод экземпляра match). У этого объекта есть методы экземпляра, с помощью которых программист может получить обратные ссылки.

Обращаться к объекту MatchData можно с помощью квадратных скобок, как если бы это был массив соответствий. Специальный элемент с индексом 0 содержит текст всей сопоставляемой строки, а элемент с индексом n ссылается на n-ую запомненную группу:

pat = /(. + [aiu])(.+[aiu])(.+[aiu])(.+[aiu])/i

# В этом образце есть четыре одинаковых группы.

refs = pat.match("Fujiyama")

# refs is now: ["Fujiyama","Fu","ji","ya","ma"]

x = refs[1]

y = refs[2..3]

refs.to_a.each {|x| print "#{x}\n"}

Отметим, что объект refs — не настоящий массив. Поэтому, если мы хотим обращаться с ним как с таковым, применяя итератор each, следует сначала преобразовать его в массив с помощью метода to_a (как показано в примере).

Есть и другие способы нахождения сопоставленной подстроки внутри исходной строки. Методы begin и end возвращают смещения начала и конца соответствия. (Важно понимать, что смещение конца — это индекс символа, следующего за найденным соответствием.)

str = "alpha beta gamma delta epsilon"

# 0....5....0....5....0....5....

# (для удобства подсчета)

pat = /(b[^ ]+ )(g[^ ]+ )(d[^ ]+ )/

# Три слова, каждое из которых представляет собой отдельное соответствие.

refs = pat.match(str)

# "beta "

p1 = refs.begin(1) # 6

p2 = refs.end(1) # 11

# "gamma "

p3 = refs.begin(2) # 11

p4 = refs.end(2) # 17

# "delta "

p5 = refs.begin(3) # 17

p6 = refs.end(3) # 23

# "beta gamma delta"

p7 = refs.begin(0) # 6

p8 = refs.end(0) # 23

Аналогично метод offset возвращает массив из двух чисел: смещение начала и смещение конца соответствия. Продолжим предыдущий пример:

range0 = refs.offset(0) # [6,23]

range1 = refs.offset(1) # [6,11]

range2 = refs.offset(2) # [11,17]

range3 = refs.offset(3) # [17,23]

Части строки, которые находятся перед сопоставленной подстроки и после нее, можно получить методами pre_match и post_match соответственно. В том же коде:

before = refs.pre_match # "alpha "

after = refs.post_match # "epsilon"

Классы символов

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

/[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.





sdamzavas.net - 2017 год. Все права принадлежат их авторам! В случае нарушение авторского права, обращайтесь по форме обратной связи...