PHP preg_match_all 模式中的子模式名称

PHP preg_match_all subpattern names in a pattern

任务很明确。在输入中我们有一个可变的正则表达式模式,它应该包含命名的子模式,在输出中我们需要得到一个子模式名称数组:

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+)/');

应该return:

array('name', 'digit');

P.S.: 根据 PCRE documentation 子模式名称最多由 32 个字母数字字符和下划线组成。

由于我们无法控制输入模式,因此我们需要考虑所有可能的命名语法。根据 PHP documentation 他们是:
(?P<name>pattern)(?<name>pattern)(?'name'pattern)

我们还需要考虑嵌套子模式,例如:
(?<name1>.*(?<name2>pattern).*).

无需计算重复名称、保留外观顺序或获取数字、非捕获或其他类型的子模式。仅列出名称(如果存在)。

您可以使用

获得所有有效命名捕获组名称的列表
"~(?<!\\)(?:\\{2})*\(\?(?|P?<([_A-Za-z]\w{0,31})>|'([_A-Za-z]\w{0,31})')~"

参见regex and an online PHP demo

要点是匹配未转义的 ( 后跟 ? 后跟 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+ 双反斜杠(允许 ( 之前的任何转义反斜杠)
  • \( - (
  • \? - 一个?
  • (?|P?<([_A-Za-z]\w{0,31})>|'([_A-Za-z]\w{0,31})') - 分支重置组:
    • P?<([_A-Za-z]\w{0,31})> - 一个可选的 P<、一个 _ 或一个 ASCII 字母,0 到 31 个字符 (digits/letters/_)(捕获到第 1 组)和 >
    • | - 或
    • '([_A-Za-z]\w{0,31})' - ',一个 _ 或一个 ASCII 字母,0 到 31 个字字符 (digits/letters/_)(也捕获到第 1 组),然后 '

组名模式全部捕获到组1中,你只需要得到$res[1]

Wiktor 的解决方案看起来确实很彻底,但这是我想出的。

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<,然后搜索 (.+?),这会转换为 angular 括号之间内容的 non-greedy 版本。然后函数只是 returns $matches 数组中的第一个偏移量,它指向匹配的第一组括号。