В Perl 5.10 появилась возможность давать имена сохраняемым совпадениям в регулярных выражениях. Чтобы эффективно пользоваться этим нововведением, необходимо разобраться в том, как работают хеши %+ и %-, куда попадают совпавшие фрагменты.
Именованные скобки в регулярных выражениях — это конструкция вида (?<name>...). Если сопоставление оказалось успешным, то к искомому фрагменту возможно обратиться через элементы хешей %+ и %-. Например, в этом примере совпадение будет доступно в переменной %+{name}.
Сложности начинаются, если регулярное выражение составлено таким образом, что в нем оказывается несколько скобок с одинаковым именем. Такое (если это сделано намерено) может произойти в том случае, когда автор выражения, например, предполагает, что на реальных данных должен совпадать только один именованный фрагмент. В другом случае регулярное выражение может быть составлено из отдельных подвыражений: здесь возможны как многократные вхождения, так и многократное сопоставление в цикле.
Вот пример, где из строки 120 EUR in USD, please извлекают названия валют.
use v5.10;
use strict;
use Data::Dumper;
my $string = '120 EUR in USD, please';
my @currencies = $string =~ /(?<currencyName>[A-Z]{3})/g;
say Dumper(\@currencies);
say Dumper(\%+);
say Dumper(\%-);
Именованное выражение ?<currencyName> встречается один раз, но обрабатывается в цикле. В массиве @currencies окажется два элемента:
$VAR1 = [
'EUR',
'USD'
];
А в хешах %+ и %- будет лишь по одному:
$VAR1 = {
'currencyName' => 'USD'
};
$VAR1 = {
'currencyName' => [
'USD'
]
};
Важно обратить внимание на то, что в эти хеши попала последняя совпавшая подстрока.
Теперь вынесем часть регулярного выражения в отдельную переменную и дважды применим ее в сохраняющих скобках с разными именами:
my $re = '[A-Z]{3}';
$string =~ /(?<from>$re).*(?<to>$re)/;
say Dumper(\%+);
say Dumper(\%-);
Поскольку сейчас имена не пересекаются, все совпадения окажутся доступными:
$VAR1 = {
'to' => 'USD',
'from' => 'EUR'
};
$VAR1 = {
'to' => [
'USD'
],
'from' => [
'EUR'
]
};
Обратить внимание, кстати, стоит на то, что в хеше %- поименованные элементы всегда содержат списки, даже если они состоят из одного элемента.
Наличие списков, однако, помогает, когда одноименных элементов становится больше:
$string =~ /(?<currencyName>$re).*(?<currencyName>$re)/;
say Dumper(\%+);
say Dumper(\%-);
В этом примере хеш %+ вновь содержит один элемент — последнее совпадение, но в ключе $-{currencyName} теперь список элементов из двух строк:
$VAR1 = {
'currencyName' => 'EUR'
};
$VAR1 = {
'currencyName' => [
'EUR',
'USD'
]
};
Ничто не мешает вынести и само имя фрагмента в отдельную переменную:
my $named = '?<currencyName>[A-Z]{3}';
$string =~ /($named).*($named)/;
say Dumper(\%+);
say Dumper(\%-);
Результат будет таким же, как и в предыдущем случае:
$VAR1 = {
'currencyName' => 'EUR'
};
$VAR1 = {
'currencyName' => [
'EUR',
'USD'
]
};Важно помнить, что если именованное выражение было необязательным и не совпало, его следы все равно будут присутствовать в хеше %-:
$string = "120 EUR, please";
$string =~ /($named).*($named)?/;
say Dumper(\%-);
Этот пример вернет хеш со списком из строки и значения undef:
$VAR1 = {
'currencyName' => [
'EUR',
undef
]
};
Порядок элементов списка совпадает с порядком именованных скобок в регулярном выражении.
Комментировать