Jak są numerowane zagnieżdżone grupy przechwytywania w wyrażeniach regularnych?

85

Czy istnieje zdefiniowane zachowanie dotyczące sposobu, w jaki wyrażenia regularne powinny obsługiwać zachowanie przechwytywania zagnieżdżonych nawiasów? Mówiąc dokładniej, czy można rozsądnie oczekiwać, że różne silniki będą przechwytywać nawiasy zewnętrzne na pierwszej pozycji i nawiasy zagnieżdżone w kolejnych pozycjach?

Rozważ następujący kod PHP (używając wyrażeń regularnych 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
)

Najpierw przechwytywane jest całe wyrażenie w nawiasach (chcę przetestować), a następnie przechwytywane są wewnętrzne wzorce ujęte w nawiasy („chcę” i „do”). Ma to logiczny sens, ale mogłem zobaczyć równie logiczny przypadek, w którym najpierw przechwytuje się nawiasy dodatkowe, a NASTĘPNIE przechwytuje cały wzór.

Czy to "najpierw przechwyć całą rzecz" zdefiniowane zachowanie w silnikach wyrażeń regularnych, czy będzie zależało od kontekstu wzorca i / lub zachowania silnika (PCRE różni się od C # jest inny niż Java jest inny niż itp.)?

Alan Storm
źródło
Jeśli naprawdę interesują Cię wszystkie odmiany wyrażeń regularnych, tag „niezależny od języka” jest tym, czego potrzebujesz. Jest zbyt wiele smaków, aby je wszystkie wymienić, a większość z nich nie jest zgodna z żadnym prawdziwym standardem (chociaż są niezwykle spójne, jeśli chodzi o numerację grup przechwytywania).
Alan Moore
Dostęp do grupy można uzyskać za pomocą $ 1, $ 2, $ 3 ... itd. Jak uzyskać dostęp do 10. grupy? Czy będzie to 10 dolarów? Myślę, że 10 dolarów nie zadziała, ponieważ zostanie zinterpretowane jako 1 dolar, po którym nastąpi 0. Czy to oznacza, że ​​możemy mieć maksymalnie 9 grup? Jeśli autor może, proszę, uwzględnij to jako część pytania, to będzie to jedyne miejsce, w którym można dowiedzieć się wszystkiego o zagnieżdżonych grupach w wyrażeniach regularnych.
LionHeart

Odpowiedzi:

59

Od perlrequick

Jeśli grupy w wyrażeniu regularnym są zagnieżdżone, $ 1 pobiera grupę z lewym nawiasem otwierającym, 2 $ następny nawias otwierający itd.

Uwaga : wykluczanie nawiasów otwierających grupy bez przechwytywania (? =)

Aktualizacja

Nie używam dużo PCRE, ponieważ generalnie używam prawdziwego;), ale dokumenty PCRE pokazują to samo, co Perl:

PODWZORY

2.Ustawia podwzór jako podwzór przechwytywania. Oznacza to, że gdy cały wzorzec pasuje, ta część ciągu podmiotu, która pasowała do podwzorca, jest zwracana do wywołującego za pośrednictwem ovectorargumentu pcre_exec(). Nawiasy otwierające liczone są od lewej do prawej (zaczynając od 1), aby uzyskać liczbę przechwytywanych wzorców podrzędnych.

Na przykład, jeśli ciąg „czerwony król” zostanie dopasowany do wzorca

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

przechwycone podciągi to „czerwony król”, „czerwony” i „król” i są odpowiednio ponumerowane 1, 2 i 3.

Jeśli PCRE odchodzi od zgodności z wyrażeniami regularnymi w Perlu, być może akronim powinien zostać przedefiniowany - „Perl Cognate Regular Expressions”, „Perl Comparable Regular Expressions” lub coś takiego. Albo po prostu pozbądź się znaczących liter.

daotoad
źródło
1
@Sinan: używa PCRE w PHP, czyli „wyrażeń regularnych zgodnych z Perlem”; więc powinno wyglądać tak samo, jak bezpośrednie używanie Perla
Pascal MARTIN
3
Pascal, PCRE powstał jako próba stworzenia zestawu wyrażeń regularnych zgodnych z Perlem, ale w ostatnich latach te dwa elementy nieco się rozeszły. Wciąż bardzo podobne, ale istnieją subtelne różnice w zestawach zaawansowanych funkcji. (Również, na pytanie, interesują mnie wszystkie platformy)
Alan Storm
1
Właściwie to Perl obecnie wykonuje większość „odpływów”, ale masz rację: „Zgodność z Perlem” szybko zmienia się z błędnej nazwy na brak sekwencji. : D
Alan Moore
1
@Alan, Perl jest zdecydowanie w ruchu. P5.10 zmieniło kilka rzeczy, ale 6 będzie znacznie inne. Prawie na pewno P będzie trzeba interpretować jako „Perl 5”. PCRE to świetny projekt, którego nie mogę wystarczająco pochwalić, był wybawieniem dla więcej niż kilku projektów.
daotoad
1
Dodałem to pod pierwszym cudzysłowem. Uwaga : Z wyłączeniem nawiasów otwierających grupy bez przechwytywania (? =). Nie zdawałem sobie sprawy, że nie byłem zalogowany, kiedy go edytowałem. Dopiero gdy dodałem ten komentarz, zostałem poproszony o podanie poświadczeń. Tak więc do zatwierdzenia potrzeba jeszcze 1 osoby!
JGFMK
17

Tak, to wszystko jest dość dobrze zdefiniowane dla wszystkich języków, które Cię interesują:

  • Java - http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html#cg
    "Grupy przechwytywania są numerowane, licząc nawiasy otwierające od lewej do prawej. ... Grupa zero zawsze oznacza całe wyrażenie. "
  • .Net - http://msdn.microsoft.com/en-us/library/bs2twtah(VS.71).aspx
    "Przechwytywania za pomocą () są numerowane automatycznie w oparciu o kolejność nawiasów otwierających, zaczynając od jedynki. Pierwszy przechwytywanie, przechwytywanie elementu o numerze zero, to tekst dopasowany przez cały wzorzec wyrażenia regularnego. ")
  • PHP (funkcje PCRE) - http://www.php.net/manual/en/function.preg-replace.php#function.preg-replace.parameters
    "\ 0 lub $ 0 odnosi się do tekstu dopasowanego przez cały wzorzec. Nawiasy otwierające są liczone od lewej do prawej (zaczynając od 1), aby uzyskać numer podrzędnego wzorca przechwytywania. " (Dotyczyło to również przestarzałych funkcji POSIX)
  • PCRE - http://www.pcre.org/pcre.txt
    Aby dodać do tego, co powiedział Alan M, wyszukaj „Jak pcre_exec () zwraca przechwycone podciągi” i przeczytaj piąty akapit poniżej:

    Pierwsza para liczb całkowitych, owektor [0] i owektor [1], identyfikują
    część ciągu tematycznego dopasowana do całego wzorca. Następny
    para jest używana do pierwszego przechwytywania wzorców i tak dalej. Wartość
    zwracana przez pcre_exec () jest o jedną więcej niż para o najwyższym numerze
    ustawiono. Na przykład, jeśli przechwycono dwa podciągi, plik
    zwracana wartość to 3. Jeśli nie ma wzorców przechwytywania, zwraca
    wartość z udanego dopasowania wynosi 1, co oznacza, że ​​jest to tylko pierwsza para
    przesunięć zostało ustawione.
    
  • Perl jest inny - http://perldoc.perl.org/perlre.html#Capture-buffers
    $ 1, $ 2 itd. Dopasowują grupy przechwytywania zgodnie z oczekiwaniami (tj. Przez występowanie nawiasu otwierającego), jednak $ 0 zwraca nazwę programu, a nie cały ciąg zapytania - zamiast tego używasz $ &.

Najprawdopodobniej znajdziesz podobne wyniki dla innych języków (Python, Ruby i inne).

Mówisz, że równie logiczne jest wymienienie najpierw wewnętrznych grup przechwytywania i masz rację - to po prostu kwestia indeksowania przy zamykaniu, a nie otwieraniu parens. (jeśli dobrze cię rozumiem). Robienie tego jest jednak mniej naturalne (na przykład nie jest zgodne z konwencją kierunku czytania), a więc utrudnia (prawdopodobnie nieznacznie) określenie, poprzez wnikliwość, która grupa przechwytująca będzie miała dany indeks wyników.

Umieszczenie całego ciągu dopasowującego na pozycji 0 również ma sens - głównie ze względu na spójność. Pozwala to całemu dopasowanemu ciągowi pozostawać w tym samym indeksie niezależnie od liczby grup przechwytywania od wyrażenia regularnego do wyrażenia regularnego i niezależnie od liczby grup przechwytywania, które faktycznie pasują do czegokolwiek (na przykład Java zwinie długość tablicy dopasowanych grup dla każdego przechwytywania group nie pasuje do żadnej treści (pomyśl na przykład coś takiego jak „wzorzec (. *)”). Zawsze możesz sprawdzić capturing_group_results [capturing_group_results_length - 2], ale to nie przekłada się dobrze na języki do Perla, który dynamicznie tworzy zmienne ($ 1 , $ 2 itd.) (Perl jest oczywiście złym przykładem, ponieważ używa $ & jako dopasowanego wyrażenia, ale masz pomysł :).

Alan Donnelly
źródło
1
Dobra
A co z JavaScript!?!
mesqueeb
9

Każdy rodzaj wyrażenia regularnego, jaki znam, grupuje według kolejności, w jakiej pojawiają się nawiasy otwierające. To, że zewnętrzne grupy są ponumerowane przed zawartymi w nich podgrupami, jest po prostu naturalnym wynikiem, a nie wyraźną polityką.

Ciekawie robi się z nazwanymi grupami . W większości przypadków stosują tę samą politykę numeracji przez względne pozycje parenów - nazwa jest jedynie aliasem numeru. Jednak w wyrażeniach regularnych .NET nazwane grupy są numerowane oddzielnie od grup numerowanych. Na przykład:

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

// result: "two four one three"

W efekcie liczba jest aliasem nazwy ; numery przypisane do nazwanych grup zaczynają się tam, gdzie kończą się grupy „rzeczywiste”. Może się to wydawać dziwną zasadą, ale jest ku temu dobry powód: w wyrażeniach regularnych .NET możesz użyć tej samej nazwy grupy więcej niż raz w wyrażeniu regularnym. To umożliwia stosowanie wyrażeń regularnych, takich jak ten z tego wątku, do dopasowywania liczb zmiennoprzecinkowych z różnych lokalizacji:

^[+-]?[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})?
)$

Jeśli istnieje separator tysięcy, zostanie on zapisany w grupie „tysiąc” niezależnie od tego, która część wyrażenia regularnego do niego pasuje. Podobnie separator dziesiętny (jeśli taki istnieje) będzie zawsze zapisywany w grupie „decimal”. Oczywiście istnieją sposoby na identyfikację i wyodrębnienie separatorów bez nazwanych grup wielokrotnego użytku, ale jest to o wiele wygodniejsze, myślę, że usprawiedliwia to dziwny schemat numeracji.

Jest jeszcze Perl 5.10+, który daje nam większą kontrolę nad grupami przechwytywania, niż wiem, co robić. :RE

Alan Moore
źródło
4

Kolejność przechwytywania w kolejności lewej strony jest standardowa na wszystkich platformach, na których pracowałem. (Perl, php, ruby, egrep)

Devin Ceartas
źródło
„uchwycenie w kolejności lewej strony” Dzięki za to, jest to dużo bardziej zwięzły sposób opisania zachowania.
Alan Storm
1
Możesz zmienić numerację ujęć w Perl 5.10 i Perl 6.
Brad Gilbert