Как нумеруются вложенные группы захвата в регулярных выражениях?


существует ли определенное поведение для того, как регулярные выражения должны обрабатывать поведение захвата вложенных скобок? Более конкретно, можно ли разумно ожидать, что различные механизмы будут захватывать внешние круглые скобки в первой позиции и вложенные круглые скобки в последующих позициях?

рассмотрим следующий PHP код (с использованием регулярных выражений PCRE)

<?php
  $test_string = 'I want to test sub patterns';
  preg_match('{(I (want) (to) test) sub (patterns)}', $test_string, $matches);
  print_r($matches);
?>

Array
(
    [0] => I want to test sub patterns  //entire pattern
    [1] => I want to test           //entire outer parenthesis
    [2] => want             //first inner
    [3] => to               //second inner
    [4] => patterns             //next parentheses set
)

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

Итак, это" захват всего сначала " определенное поведение в движках регулярных выражений, или это будет зависеть от контекста шаблона и/или поведения движка (PCRE отличается от C#отличается от Java чем и т. д.)?

4 59

4 ответа:

С perlrequick

если группировки в регулярном выражении являются вложенный, $1 получает группу с самая левая открывающая скобка, $2 следующей открывающей скобки и т. д.

будьте осторожны: исключая скобки открытия группы без захвата (?=)

обновление

Я не использую PCRE много, как я обычно использую реальную вещь ;), но документы PCRE показать то же самое, что В Perl:

ПОДШАБЛОНЫ

2. Он устанавливает подшаблон в качестве подшаблона захвата. Это означает, что, когда весь шаблон совпадает, та часть строки субъекта, которая соответствует подшаблону, передается обратно вызывающему через

Да, это все довольно хорошо определено для всех языков, которые вас интересуют:

  • Java - http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html#cg
    "Группы захвата нумеруются путем подсчета их открывающих скобок слева направо. ... Группа Ноль всегда обозначает все выражение."
  • .Net - http://msdn.microsoft.com/en-us/library/bs2twtah(против.71).аспн
    "Захваты с использованием () нумеруются автоматически в соответствии с порядком открывающей скобки, начиная с единицы. Первый захват, номер элемента захвата ноль, - это текст, сопоставленный всему шаблону регулярного выражения.")
  • PHP (функции PCRE) - http://www.php.net/manual/en/function.preg-replace.php#function.preg-replace.parameters
    "\0 или $0 относится к текст соответствует всему шаблону. Открывающие круглые скобки нумеруются слева направо (начиная с 1), чтобы получить число подмаска.(Это было также верно для устаревших функций POSIX)
  • PCRE - http://www.pcre.org/pcre.txt
    Чтобы добавить к тому, что сказал Алан м, найдите "как pcre_exec () возвращает захваченные подстроки" и прочитайте пятый абзац, который следует:

    The  first  pair  of  integers, ovector[0] and ovector[1], identify the
    portion of the subject string matched by the entire pattern.  The next
    pair  is  used for the first capturing subpattern, and so on. The value
    returned by pcre_exec() is one more than the highest numbered pair that
    has  been  set.  For example, if two substrings have been captured, the
    returned value is 3. If there are no capturing subpatterns, the  return
    value from a successful match is 1, indicating that just the first pair
    of offsets has been set.
    
  • в Perl разные - http://perldoc.perl.org/perlre.html#Capture-buffers
    $1, 2 $и т. д. матч группы захвата, как вы ожидаете (т. е. при появлении открывающей скобки), однако $0 возвращает имя программы, а не всю строку запроса, чтобы получить то, что вы используете вместо$&.

вы, скорее всего, найдете аналогичные результаты для других языков (Python, Ruby и других).

вы говорите, что одинаково логично сначала перечислить внутренние группы захвата и вы правы-это просто вопрос индексации при закрытии, а не открытии, parens. (если я правильно вас понял). Это менее естественно, хотя (например, это не соответствует соглашению о направлении чтения) и поэтому затрудняет (вероятно, не значительно) определение по insepection, какая группа захвата будет иметь заданный индекс результата.

размещение всей строки соответствия в позиции 0 также имеет смысл-в основном для согласованности. Это позволяет вся согласованная строка должна оставаться в том же индексе независимо от количества групп захвата от регулярного выражения до регулярного выражения и независимо от количества групп захвата, которые фактически соответствуют чему-либо (Java, например, свернет длину массива сопоставленных групп для каждой группы захвата не соответствует никакому содержимому (подумайте, например, что-то вроде "a (.*)узор.)" Вы всегда можете проверить capturing_group_results[capturing_group_results_length-2], но это не очень хорошо переводится на языки Perl, которые динамически создают переменные ($1, $2 и т. д.) (Perl-плохой пример, конечно, так как он использует $& для соответствующего выражения, но вы получаете идею :).

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

где это становится интересным с именованные группы. В большинстве случаев они следуют той же политике нумерации по относительным позициям родителей-имя является просто псевдонимом для числа. Тем не менее, в .Чистая регулярные выражения именованные группы нумеруются отдельно от пронумерованных групп. Например:

Regex.Replace(@"one two three four", 
              @"(?<one>\w+) (\w+) (?<three>\w+) (\w+)",
              @"   ")

// result: "two four one three"

по сути, - это псевдоним для имя; номера, присвоенные именованным группам, начинаются там, где заканчиваются" реальные " нумерованные группы. Это может показаться странной политикой, но для этого есть веская причина: в регулярных выражениях .NET вы можете использовать одно и то же имя группы более одного раза в регулярном выражении. Что делает возможным выражений типа этой теме для сопоставления с плавающей точкой номера из разных регионов:

^[+-]?[0-9]{1,3}
(?:
    (?:(?<thousand>\,)[0-9]{3})*
    (?:(?<decimal>\.)[0-9]{2})?
|
    (?:(?<thousand>\.)[0-9]{3})*
    (?:(?<decimal>\,)[0-9]{2})?
|
    [0-9]*
    (?:(?<decimal>[\.\,])[0-9]{2})?
)$

Если есть разделитель тысяч, он будет сохранен в группе "тысяча" независимо от того, какой частью регулярного выражения совпадают. Аналогично, десятичный разделитель (если он есть) всегда будет сохранен в группе "decimal". Конечно, есть способы идентифицировать и извлекать разделители без многоразовых именованных групп, но этот способ намного удобнее, я думаю, что он более чем оправдывает странную схему нумерации.

и затем есть Perl 5.10+, который дает нам больше контроля над захватами групп, чем я знаю, что делать. : D

порядок захвата в порядке левого paren является стандартным для всех платформ, на которых я работал. (perl, php, ruby, egrep)