Можно ли использовать регулярные выражения Perl для соответствия сбалансированному тексту?
Я хотел бы сопоставить текст, заключенный в скобки и т. Д. В Perl. Как я могу это сделать?
Это вопрос от официального perlfaq. Мы импортируем perlfaq в переполнение стека.
1 ответ
Это официальный ответ на часто задаваемые вопросы минус любые последующие изменения.
Ваша первая попытка, вероятно, должна быть модулем Text:: Balanced, который находится в стандартной библиотеке Perl начиная с Perl 5.8. У этого есть множество функций, чтобы иметь дело с хитрым текстом. Модуль Regexp:: Common также может помочь, предоставляя готовые шаблоны, которые вы можете использовать.
Начиная с Perl 5.10, вы можете сопоставлять сбалансированный текст с регулярными выражениями, используя рекурсивные шаблоны. До Perl 5.10 вам приходилось прибегать к различным приемам, таким как использование кода Perl в (??{})
последовательности.
Вот пример использования рекурсивного регулярного выражения. Цель состоит в том, чтобы охватить весь текст в угловых скобках, включая текст во вложенных угловых скобках. Этот образец текста имеет две "основные" группы: группу с одним уровнем вложенности и группу с двумя уровнями вложенности. Всего в угловых скобках пять групп:
I have some <brackets in <nested brackets> > and
<another group <nested once <nested twice> > >
and that's it.
Регулярное выражение для соответствия сбалансированному тексту использует две новые (для Perl 5.10) функции регулярных выражений. Они описаны в perlre, и этот пример является модифицированной версией в этой документации.
Во-первых, добавление нового притяжательного +
к любому квантификатору находит самое длинное совпадение и не возвращается назад. Это важно, поскольку вы хотите обрабатывать любые угловые скобки с помощью рекурсии, а не возврата назад. Группа [^<>]++
находит одну или несколько неугловых скобок без возврата.
Во-вторых, новый (?PARNO)
относится к подшаблону в конкретной группе захвата, заданной PARNO
, В следующем регулярном выражении первая группа захвата находит (и запоминает) сбалансированный текст, и вам нужен тот же шаблон в первом буфере, чтобы пройти через вложенный текст. Это рекурсивная часть. (?1)
использует шаблон во внешней группе захвата как независимую часть регулярного выражения.
Собрав все это вместе, вы получите:
#!/usr/local/bin/perl5.10.0
my $string =<<"HERE";
I have some <brackets in <nested brackets> > and
<another group <nested once <nested twice> > >
and that's it.
HERE
my @groups = $string =~ m/
( # start of capture group 1
< # match an opening angle bracket
(?:
[^<>]++ # one or more non angle brackets, non backtracking
|
(?1) # found < or >, so recurse to capture group 1
)*
> # match a closing angle bracket
) # end of capture group 1
/xg;
$" = "\n\t";
print "Found:\n\t@groups\n";
Вывод показывает, что Perl нашел две основные группы:
Found:
<brackets in <nested brackets> >
<another group <nested once <nested twice> > >
Приложив немного больше усилий, вы можете получить все группы в угловых скобках, даже если они находятся в других угловых скобках. Каждый раз, когда вы получаете сбалансированное совпадение, удаляйте его внешний разделитель (это тот, который вы только что сопоставили, поэтому не сопоставляйте его снова) и добавляйте его в очередь строк для обработки. Продолжайте делать это, пока не получите совпадений:
#!/usr/local/bin/perl5.10.0
my @queue =<<"HERE";
I have some <brackets in <nested brackets> > and
<another group <nested once <nested twice> > >
and that's it.
HERE
my $regex = qr/
( # start of bracket 1
< # match an opening angle bracket
(?:
[^<>]++ # one or more non angle brackets, non backtracking
|
(?1) # recurse to bracket 1
)*
> # match a closing angle bracket
) # end of bracket 1
/x;
$" = "\n\t";
while( @queue )
{
my $string = shift @queue;
my @groups = $string =~ m/$regex/g;
print "Found:\n\t@groups\n\n" if @groups;
unshift @queue, map { s/^<//; s/>$//; $_ } @groups;
}
Выходные данные показывают все группы. Самые внешние совпадения отображаются первыми, а вложенные совпадения появляются позже:
Found:
<brackets in <nested brackets> >
<another group <nested once <nested twice> > >
Found:
<nested brackets>
Found:
<nested once <nested twice> >
Found:
<nested twice>