2013-12-13 65 views
6

私は例えば、ネストされた括弧にマッチする正規表現を記述しようとしています:、このような文字列が一致する必要があります入れ子になった括弧に一致する再帰正規表現を書くには?

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

すべてのネストされた括弧ではなく、閉じている原因:

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

べきではありません少なくとも最初の "(((text)))(text)"部分と一致する必要があります。

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

しかし、私は期待していて、それが正しく動作しません:

実は、私の正規表現です。それを修正するには?どこが間違っていますか?ありがとう!

+2

これを再帰的に行うために必要なSQL用のパーサーを作成しました。 regexだけで再帰的にこれをやろうとするよりも、regexで再帰関数を持つほうがずっと簡単です。 – EdgeCase

+0

あなたは間違ったツリーを吠えている、純粋に正規表現の解決策は、おそらく過度に複雑で、維持するのが難しいでしょう。文字列を再帰的に解析するほうがよいでしょう。 – GordonM

+0

Do not ... OK、理論的にはそれはできますが、あなたがそれをすると、それはおそらく幻想的に見えます。ああ、正規表現のバグを見つけましたか?アー...どうしたの?ああ、私たちもブラケットのサポートを追加する必要があります!アーン...どうやって追加しますか?私はあなたに言う、あなたはより人間が読めるパーサーを使うのがよい。あなたがこれを尋ねているという事実は、とにかくそれを維持することはおそらく不可能であることを示しています。 – Theraot

答えて

2

私は、私は私自身の区切り文字で動作するようにパターンを変更する方法を見つけ出すことができませんでした。この答えを見つけましたここで、{}です。だから私のアプローチは、より一般的なものにすることでした。

独自の可変の左右の区切り文字で正規表現パターンを生成するスクリプトです。

$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

いいえ、あなたは '$ delimiter_right'を常に閉じたままの状態で、文字列全体にマッチします。 – tonix

9

このパターンが機能:

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

括弧内のコンテンツは、単に説明である:

"括弧OR再帰ないという全て(=他の括弧)、" X 0回以上

を括弧内のすべての部分文字列をキャッチする場合は、このパターンを先読みの中に入れて、重複するすべての結果を取得する必要があります。

$pattern = '~(?= (\((?: [^()]+ | (?1))*+ \)))~x'; 
preg_match_all($pattern, $subject, $matches); 
print_r($matches[1]); 
(?1)によって私はキャプチャグループを追加していると私は (?R)を交換した

注:

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

この先読みトリックとは何ですか?

ルックアヘッド(またはルックバックハイド)内のサブパターンは何も一致しません。アサーション(テスト)のみです。したがって、同じサブストリングを複数回チェックすることができます。

パターン結果全体(print_r($matches[0]);)を表示すると、すべての結果が空の文字列であることがわかります。ルックアヘッド内のサブパターンによって検出された部分文字列を取得する唯一の方法は、サブパターンをキャプチャグループに囲むことです。

注:再帰的サブパターンは次のように改善することができます。

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

なぜ機能するのですか?どのように機能するのですか? – hek2mgl

+0

私はそれを試しましたが、うまくいかず、サブパターンもキャッチしていません...別の方法がありますか? – tonix

+0

ありがとうございます。それを理解するためにそれを回す必要があります。 – hek2mgl

1

次のコードは、(それはCC-BY 3.0の下です)Parser class from Paladioを使用して、それがUTF-8で動作します。

動作の仕方は、再帰関数を使用して文字列を反復処理することです。 (が見つかるたびに、自分自身を呼び出します。対応する文字列の末尾に達すると、一致しないペアも検出します。)

また、このコードでは、検出された各部分を処理するために使用できる$ callbackパラメータも使用されます。コールバックは、1)文字列、および2)レベル(0 =最も深い)の2つのパラメータを受け取ります。コールバックが返すものは、文字列の内容に置き換えられます(この変更は、より高いレベルのコールバックで表示されます)。

注:コードにはタイプチェックは含まれていません。

非再帰部分:

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; 
    } 
} 

再帰部分:

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 
      ); 
     } 
    } 
} 
関連する問題