Имена подшаблонов PHP preg_match_all в шаблоне

Задача достаточно ясна. На входе у нас переменный шаблон регулярного выражения, который якобы содержит именованные подшаблоны, а на выходе нам нужно получить массив имен подшаблонов:

function get_subpattern_names($any_input_pattern) {
  // What pattern to use here?
  $pattern_to_get_names = '/.../';

  preg_match_all($pattern_to_get_names, $any_input_pattern, $matches);

  return $matches;
}

Итак, вопрос в том, что использовать в качестве $pattern_to_get_names в приведенной выше функции?

Например:

get_subpattern_names('/(?P<name>\w+): (?P<digit>\d+)/');

должен вернуться:

array('name', 'digit');

P.S.: Согласно документации PCRE, имена подшаблонов состоят из 32 буквенно-цифровых символов. символов и подчеркивания.

Поскольку мы не контролируем шаблон ввода, нам нужно учитывать все возможные варианты синтаксиса именования. Согласно документации PHP, это:
(?P<name>pattern), (?<name>pattern) и (?'name'pattern).

Нам также необходимо учитывать вложенные подшаблоны, например:
(?<name1>.*(?<name2>pattern).*).

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


person Mike Shiyan    schedule 11.12.2017    source источник
comment
См. ideone.com/UoLUYB и regex101.com/r/Rfx389/1   -  person Wiktor Stribiżew    schedule 11.12.2017


Ответы (2)


Вы можете получить список всех допустимых имен именованных групп захвата, используя

"~(?<!\\\\)(?:\\\\{2})*\(\?(?|P?<([_A-Za-z]\w{0,31})>|'([_A-Za-z]\w{0,31})')~"

См. регулярное выражение и онлайн-демонстрация PHP.

Суть в том, чтобы сопоставить неэкранированную (, за которой следует ?, за которой следует либо P<, либо <, а затем шаблон имени группы, заканчивающийся на > или ', за которым следует шаблон имени группы, а затем '.

$rx = "~(?<!\\\\)(?:\\\\{2})*\(\?(?|P?<([_A-Za-z]\w{0,31})>|'([_A-Za-z]\w{0,31})')~";
$s = "(?P<name>\w+): (?<name2>\w+): (?'digit'\d+)";
preg_match_all($rx, $s, $res);
print_r($res[1]);

урожаи

Array
(
    [0] => name
    [1] => name2
    [2] => digit
)

Сведения о шаблоне

  • (?<!\\) - нет \ сразу слева от текущего местоположения
  • (?:\\\\)* - 0+ двойных обратных косых черт (чтобы разрешить любую экранированную обратную косую черту до ()
  • \( - a (
  • \? - a ?
  • (?|P?<([_A-Za-z]\w{0,31})>|'([_A-Za-z]\w{0,31})') - a branch reset group:
    • P?<([_A-Za-z]\w{0,31})> - an optional P, <, a _ or an ASCII letter, 0 to 31 word chars (digits/letters/_) (captured into Group 1), and >
    • | - or
    • '([_A-Za-z]\w{0,31})' - ', _ или буква ASCII, символы слов от 0 до 31 (цифры/буквы/_) (также захвачены в группу 1), а затем '

Все шаблоны имен групп захвачены в группу 1, вам просто нужно получить $res[1].

person Wiktor Stribiżew    schedule 11.12.2017

Решение Виктора кажется довольно тщательным, но вот что я придумал.

print_r(get_subpattern_names('/(?P<name>\w+): (?P<digit>\d+)/'));

function get_subpattern_names($input_pattern){
    preg_match_all('/\?P\<(.+?)\>/i', $input_pattern, $matches);
    return $matches[1];
}

Это должно работать в большинстве случаев. Что еще более важно, это гораздо более читабельно и не требует пояснений.

По сути, я ищу ?P<, за которым следует (.+?), что означает нежадную версию что-то между угловыми скобками. Затем функция просто возвращает первое смещение в массиве $matches, которое указывает на первый набор совпавших скобок.

person Mav    schedule 11.12.2017
comment
Извините, ваш ответ не учитывает другие возможные синтаксисы. - person Mike Shiyan; 18.12.2017