2013-12-13 13 views
6

próbuję napisać wyrażenie regularne, które pasuje zagnieżdżonych nawiasów, npJak napisać wyrażenie rekurencyjne odpowiadające zagnieżdżonym nawiasom?

"(((text(text))))(text()()text)(casual(characters(#$%^^&&#^%#@!&**&#^*[email protected]#^**_)))" 

Ciąg jak to powinno być dopasowane, bo wszystkie zagnieżdżone nawiasy są zamknięte, zamiast:

"(((text)))(text)(casualChars*#(!&#*(!))" 

nie powinno lub lepiej, powinna odpowiadać co najmniej pierwszej części "(((tekst)) (tekst)".

Faktycznie, mój regexp jest:

$regex = '/(( (\() ([^[]*?) (?R)? (\)) ){0,}) /x'; 

Ale to nie działa prawidłowo, jak oczekuję. Jak to naprawić? Gdzie się mylę? Dzięki!

+2

Napisałem parser dla SQL, który musiał rekursywnie to zrobić. O wiele łatwiej jest mieć funkcje rekursywne z wyrażeniem regularnym, niż próbować zrobić to rekurencyjnie z samym regex. – EdgeCase

+0

Szczekasz złe drzewo, rozwiązanie czysto regex będzie prawdopodobnie zbyt skomplikowane i trudne do utrzymania. Lepiej byłoby rekurencyjnie analizować ciąg znaków. – GordonM

+0

Nie ... Ok, teoretycznie można to zrobić, ale kiedy uda się to zrobić, prawdopodobnie będzie wyglądać jak gliberish. Oh, zobacz, znaleźliśmy błąd w regex! Ern ... jak to naprawić? Och, musimy też dodać wsparcie dla hamulców! Ern ... jak to dodać? Mówię ci, lepiej użyj bardziej czytelnego parsera. Fakt, że o to pytasz, pokazuje, że prawdopodobnie NIE będziesz w stanie go utrzymać. – Theraot

Odpowiedz

2

Kiedy znalazłem tę odpowiedź nie byłem w stanie dowiedzieć się, jak zmodyfikować wzór do pracy z własnymi ograniczników gdzie gdzie { i. Więc moim podejściem było uczynienie go bardziej ogólnym.

Oto skrypt do generowania wzoru regex z własnymi zmiennymi lewymi i prawymi ogranicznikami.

$delimiter_wrap = '~'; 
$delimiter_left = '{';/* put YOUR left delimiter here. */ 
$delimiter_right = '}';/* put YOUR right delimiter here. */ 

$delimiter_left = preg_quote($delimiter_left, $delimiter_wrap); 
$delimiter_right = preg_quote($delimiter_right, $delimiter_wrap); 
$pattern   = $delimiter_wrap . $delimiter_left 
       . '((?:[^' . $delimiter_left . $delimiter_right . ']++|(?R))*)' 
       . $delimiter_right . $delimiter_wrap; 

/* Now you can use the generated pattern. */ 
preg_match_all($pattern, $subject, $matches); 
+1

Dobrze, dopasowujesz cały ciąg pod warunkiem, że zawsze jest '$ delimiter_right' zamykający otwarty' $ delimiter_left' – tonix

9

ten wzór działa:

$pattern = '~ \((?: [^()]+ | (?R))*+ \) ~x'; 

Zawartość wewnątrz nawiasu jest po prostu opisać:

"Wszystko, co nie jest nawias LUB rekurencji (= drugi nawias)" x 0 lub więcej razy

Jeśli chcesz uchwycić wszystkie podłańcuchy w nawiasach, musisz umieścić ten wzorzec wewnątrz poprzedniej, aby uzyskać wszystkie zachodzące na siebie wyniki:

$pattern = '~(?= (\((?: [^()]+ | (?1))*+ \)))~x'; 
preg_match_all($pattern, $subject, $matches); 
print_r($matches[1]); 

Zauważ, że dodałem grupę przechwytywania i Wymieniłem (?R) przez (?1):

(?R) -> refers to the whole pattern (You can write (?0) too) 
(?1) -> refers to the first capturing group 

Co to jest uprzedzona sztuczka?

Podpowiedź wewnątrz linii do przodu (lub elementu typu lookbehind) nie pasuje do niczego, to tylko twierdzenie (test). Pozwala to na kilkakrotne sprawdzenie tego samego podciągu.

Jeśli wyświetlisz wyniki całego wzoru (print_r($matches[0]);), zobaczysz, że wszystkie wyniki są pustymi ciągami. Jedynym sposobem na uzyskanie podłańcuchów znalezionych przez podwzgórze wewnątrz przodka jest zamknięcie podstruktury w grupie przechwytującej.

Uwaga: rekurencyjny podciąg wzorca można poprawić tak:

\([^()]*+ (?: (?R) [^()]*)*+ \) 
+0

dlaczego to działa? Jak to działa? – hek2mgl

+0

Próbowałem, ale to nie działa, a także nie mam podciąg wzór ... Czy istnieje inny sposób? – tonix

+0

Dzięki za wyjaśnienie. Trzeba się z nim bawić, aby to zrozumieć. – hek2mgl

1

Poniższy kod wykorzystuje mójParser class from Paladio (to pod CC-BY 3.0), to działa na UTF-8.

Sposób działania polega na użyciu funkcji rekurencyjnej do iterowania po ciągu znaków. Będzie wywoływał się za każdym razem, gdy znajdzie (. Wykryje również niedopasowane pary, gdy dojdzie do końca ciągu bez znalezienia odpowiedniego ).

Ponadto ten kod pobiera parametr zwrotny $, którego można użyć do przetworzenia każdego znalezionego fragmentu. Callback otrzymuje dwa parametry: 1) ciąg znaków i 2) poziom (0 = najgłębszy). To, co zwraca zwrot, zostanie zastąpione w treści ciągu (zmiany te są widoczne na połączeniu zwrotnym wyższego poziomu).

Uwaga: kod nie obejmuje kontroli typów.

nierekursywnych część:

function ParseParenthesis(/*string*/ $string, /*function*/ $callback) 
{ 
    //Create a new parser object 
    $parser = new Parser($string); 
    //Call the recursive part 
    $result = ParseParenthesisFragment($parser, $callback); 
    if ($result['close']) 
    { 
     return $result['contents']; 
    } 
    else 
    { 
     //UNEXPECTED END OF STRING 
     // throw new Exception('UNEXPECTED END OF STRING'); 
     return false; 
    } 
} 

Recursive część:

function ParseParenthesisFragment(/*parser*/ $parser, /*function*/ $callback) 
{ 
    $contents = ''; 
    $level = 0; 
    while(true) 
    { 
     $parenthesis = array('(', ')'); 
     // Jump to the first/next "(" or ")" 
     $new = $parser->ConsumeUntil($parenthesis); 
     $parser->Flush(); //<- Flush is just an optimization 
     // Append what we got so far 
     $contents .= $new; 
     // Read the "(" or ")" 
     $element = $parser->Consume($parenthesis); 
     if ($element === '(') //If we found "(" 
     { 
      //OPEN 
      $result = ParseParenthesisFragment($parser, $callback); 
      if ($result['close']) 
      { 
       // It was closed, all ok 
       // Update the level of this iteration 
       $newLevel = $result['level'] + 1; 
       if ($newLevel > $level) 
       { 
        $level = $newLevel; 
       } 
       // Call the callback 
       $new = call_user_func 
       (
        $callback, 
        $result['contents'], 
        $level 
       ); 
       // Append what we got 
       $contents .= $new; 
      } 
      else 
      { 
       //UNEXPECTED END OF STRING 
       // Don't call the callback for missmatched parenthesis 
       // just append and return 
       return array 
       (
        'close' => false, 
        'contents' => $contents.$result['contents'] 
       ); 
      } 
     } 
     else if ($element == ')') //If we found a ")" 
     { 
      //CLOSE 
      return array 
      (
       'close' => true, 
       'contents' => $contents, 
       'level' => $level 
      ); 
     } 
     else if ($result['status'] === null) 
     { 
      //END OF STRING 
      return array 
      (
       'close' => false, 
       'contents' => $contents 
      ); 
     } 
    } 
} 
Powiązane problemy