PHP регулярное выражение для обнаружения текста в скобках, игнорируя вложенные скобки

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

Допустим, я хочу

Lorem ipsum [1. dolor sit amet, [consectetuer adipiscing] elit.]. Aenean commodo ligula eget dolor.[2. Dolor, [consectetuer adipiscing] elit.] Aenean massa[3. Lorem ipsum] dolor.

возвращать

[1] => "dolor sit amet, [consectetuer adipiscing] elit."[2] => "Dolor, [consectetuer adipiscing] elit."[3] => "Lorem ipsum"

Пока я получил

'/\[([0-9]+)\.\s([^\]]+)\]/gi'

но он ломается, когда появляются вложенные скобки. Посмотреть демо

Как я могу игнорировать внутренние скобки от обнаружения?
Спасибо заранее!

9

Решение

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

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

if (preg_match_all($pattern, $text, $matches))
$result =  array_combine($matches[1], $matches[2]);

Детали шаблона:

~     # pattern delimiter
\[    # literal opening square bracket
(?:(\d+)\.\s)? # optional item number (*)
(              # capture group 2
[^][]*+         # all that is not a square bracket (possessive quantifier)
(?:             #
(?R)        # recursion: (?R) is an alias for the whole pattern
[^][]*      # all that is not a square bracket
)*+             # repeat zero or more times (possessive quantifier)
)
]                  # literal closing square bracket
~x  # free spacing mode

(*) обратите внимание, что часть номера элемента должна быть необязательной, если вы хотите использовать рекурсию с (?R) (например [consectetuer adipiscing] не имеет номер элемента.). Это может быть проблематично, если вы хотите избежать квадратных скобок без номера позиции. В этом случае вы можете построить более надежный шаблон, если вы измените необязательную группу (?:(\d+)\.\s)? условному заявлению: (?(R)|(\d+)\.\s)

Условный оператор:

(?(R)        # IF you are in a recursion
# THEN match this (nothing in our case)
|          # ELSE
(\d+)\.\s  #
)

Таким образом, номер позиции становится обязательным.

2

Другие решения

Вы можете использовать рекурсивные ссылки на предыдущие группы:

(?<no_brackets>[^\[\]]*){0}(?<balanced_brackets>\[\g<no_brackets>\]|\[(?:\g<no_brackets>\g<balanced_brackets>\g<no_brackets>)*\])

Увидеть это в действии

Идея состоит в том, чтобы определить желаемые совпадения как нечто без скобок, окруженное [] или что-то, что содержит последовательность без скобок или сбалансированных скобок с первым правилом.

5

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

$str = "Lorem ipsum [1. dolor sit amet, [consectetuer adipiscing] elit.]. Aenean commodo ligula eget dolor.[2. Dolor, [consectetuer adipiscing] elit.] Aenean massa[3. Lorem ipsum] dolor.";
preg_match_all('/\[(?>[^\[\]]|(?R))*]/', $str, $matches);
$res = array_map(function($el) {
return preg_replace('/^\[\d+\.(.*?)\s*\]$/s', '$1', $el);
},
$matches[0]);
print_r($res);

Увидеть IDEONE демо

\[(?>[^\[\]]|(?R))*] регулярные выражения [тогда ничего кроме [ а также ] или вложенный [...] строит. Узнайте больше о рекурсии с регулярным выражением в regular-expressions.info. Здесь regex demo.

Регулярное выражение внутри preg_repace^\[\d+\.(.*?)\s*\]$ — будет соответствовать начальному [ с 1 или более цифрами и точкой после, а также сопоставить и захватить остальные до последнего необязательного пробела (\s*) и закрытие ] ( $ убедитесь, что скобка совпадает в конце строки). С $1 мы можем восстановить оставшуюся часть строки и использовать ее для заполнения нового массива. Увидеть 2-е демо здесь.

1