Можно ли использовать регулярные выражения Perl для соответствия сбалансированному тексту? - PullRequest
13 голосов
/ 15 декабря 2010

Я бы хотел сопоставить текст, заключенный в скобки и т. Д. В Perl. Как я могу это сделать?


Это вопрос от официального perlfaq . Мы импортируем perlfaq в Stack Overflow .

1 Ответ

23 голосов
/ 15 декабря 2010

Это официальный ответ на часто задаваемые вопросы минус любые последующие изменения.

Ваша первая попытка, вероятно, должна быть 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>
...