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

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

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

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

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

Не должно или лучше должно соответствовать хотя бы первой части (((текст))) (текст).

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

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

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

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

Этот шаблон работает:

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

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

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

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

$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]);), вы увидите, что все результаты являются пустыми строками. Единственный способ получить подстроки, найденные подшаблоном внутри предисловия, - заключить подшаблон в группу захвата.

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

\( [^()]*+ (?: (?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
            );
        }
    }
}
Другие вопросы по тегам