Как вложенные группы захвата нумеруются регулярными выражениями? - PullRequest
68 голосов
/ 21 августа 2009

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

Рассмотрим следующий код 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 ]

53 голосов
/ 22 августа 2009

С perlrequick

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

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

Обновление

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

Подмаска

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

Например, если строка «красный король» сопоставляется с шаблоном

the ((red|white) (king|queen))

захваченные подстроки - "красный король", "красный" и "король" и имеют номера 1, 2 и 3 соответственно.

Если PCRE отходит от совместимости с регулярным выражением Perl, возможно, следует переопределить аббревиатуру - «Регулярные выражения с родственным Perl», «Регулярные выражения, сопоставимые с Perl» или что-то в этом роде. Или просто избавьтесь от букв смысла.

16 голосов
/ 22 августа 2009

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

  • Java - http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html#cg
    «Захватывающие группы нумеруются путем подсчета открывающих скобок слева направо. ... Ноль группы всегда обозначает все выражение».
  • .Net - http://msdn.microsoft.com/en-us/library/bs2twtah(VS.71).aspx
    «Захваты, использующие (), нумеруются автоматически в зависимости от порядка открывающих скобок, начиная с единицы. Первый захват, номер элемента захвата ноль, представляет собой текст, соответствующий всему шаблону регулярного выражения.»)
  • 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 и др.).

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

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

8 голосов
/ 22 августа 2009

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

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

Regex.Replace(@"one two three four", 
              @"(?<one>\w+) (\w+) (?<three>\w+) (\w+)",
              @"$1 $2 $3 $4")

// result: "two four one three"

В действительности, число является псевдонимом для name ; номера, присвоенные именованным группам, начинаются там, где заканчиваются «реальные» пронумерованные группы. Это может показаться странной политикой, но для этого есть веская причина: в регулярных выражениях .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})?
)$

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

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

4 голосов
/ 21 августа 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...