2013-12-13 56 views
6

我試圖寫一個匹配嵌套的括號中的正則表達式,如:如何編寫匹配嵌套圓括號的遞歸正則表達式?

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

像這樣的字符串應該匹配,導致所有的嵌套的括號是封閉的,而不是:

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

不應該或更好的,應該至少匹配第一個「(((文本)))(文本)」部分。

其實,我的正則表達式是:

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

但正如我期待它不能正常工作。如何解決這個問題?我錯在哪裏?謝謝!

+2

我寫了一個SQL解析器,需要遞歸地執行此操作。使用正則表達式遞歸函數要比使用正則表達式遞歸地執行遞歸函數要容易得多。 – EdgeCase

+0

你在吠叫錯誤的樹,純粹的正則表達式解決方案可能會過於複雜和難以維護。你會更好地遞歸解析字符串。 – GordonM

+0

不要......好吧,理論上它可以完成,但是當你設法做到這一點時,它可能看起來就像是gl。一樣。哦,看看我們在正則表達式中發現了一個錯誤!恩......你怎麼解決這個問題?噢,我們還需要增加對brakets的支持!恩......你怎麼補充的?我告訴你,你最好使用更易讀的解析器。你問這個事實,表明你可能無法維護它。 – 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'關閉打開的'$ delimiter_left' – tonix

9

這種模式的工作原理:

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

內括號中的內容是簡單地描述:

「所有不在括號OR遞歸(=其他括號)」 ×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

下面的代碼使用Parser class from Paladio(這是在CC-BY 3.0),它適用於UTF-8。

它的工作方式是通過使用遞歸函數遍歷字符串。它會在每次找到(時調用它自己。它還會在到達字符串末尾時檢測到不匹配對,但未找到相應的)

此外,此代碼需要一個$回調參數,您可以使用它來處理它找到的每個部分。回調接收兩個參數:1)字符串,2)等級(0 =最深)。無論回調返回將在字符串的內容中被替換(這種更改在更高級別的回調中可見)。

注意:代碼不包括類型檢查。

非遞歸部分:

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