Как написать рекурсивное регулярное выражение, которое соответствует вложенным круглым скобкам?

Я пытаюсь написать regexp, который соответствует вложенным круглым скобкам, например:

"(((text(text))))(text()()text)(casual(characters(#$%^^&&#^%#@!&**&#^*!@#^**_)))" 

Строку, подобную этой, должна быть сопоставлена, потому что все вложенные круглые скобки закрыты, вместо этого:

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

Не следует или лучше сочетать, по крайней мере, первую часть «((текст))) (текст).

На самом деле, мое регулярное выражение:

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

Но это не работает должным образом, как я ожидаю. Как это исправить? Где я ошибаюсь? Благодаря!

Solutions Collecting From Web of "Как написать рекурсивное регулярное выражение, которое соответствует вложенным круглым скобкам?"

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

Вот сценарий для генерации шаблона регулярного выражения с вашими собственными переменными влево и вправо .

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

Эта модель работает:

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

Содержимое внутри круглых скобок просто описывается:

"все, что не является скобкой ИЛИ рекурсией (= другая скобка)" x 0 или более раз

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

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

Обратите внимание, что я добавил группу захвата, и я заменил (?R) на (?1) :

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

Что это за трюк?

Подшаблон внутри lookahead (или lookbehind) ничего не соответствует, это только утверждение (тест). Таким образом, он позволяет несколько раз проверять одну и ту же подстроку.

Если вы показываете результаты всего шаблона ( print_r($matches[0]); ), вы увидите, что все результаты – это пустые строки. Единственный способ получить подстроки, найденные подшаблоном внутри lookahead, заключается в том, чтобы заключить подшаблон в группу захвата.

Примечание: рекурсивный подшаблон можно улучшить следующим образом:

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

Следующий код использует мой класс Parser из Paladio (он находится под CC-BY 3.0), он работает на UTF-8.

То, как он работает, – это использование рекурсивной функции для итерации по строке. Он будет называть себя каждый раз, когда он найдет ( он также обнаружит несогласованные пары, когда он достигнет конца строки, не найдя соответствующий ) .

Кроме того, этот код принимает параметр $ callback, который вы можете использовать для обработки каждого найденного фрагмента. Обратный вызов получает два параметра: 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 ); } } } в 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 ); } } }