5

尽管我对伪代码中的正则表达式有足够的了解,但我在翻译我想要在 php regex perl 中做的事情时遇到了麻烦。
我正在尝试使用 preg_match 来提取我的部分表达式。
我有以下字符串${classA.methodA.methodB(classB.methodC(classB.methodD)))},我需要做两件事:

一种。验证语法

  • ${classA.methodA.methodB(classB.methodC(classB.methodD)))} 有效的
  • ${classA.methodA.methodB} 有效的
  • ${classA.methodA.methodB()} 无效
  • ${methodB(methodC(classB.methodD)))} 无效

湾。我需要提取那些信息 ${classA.methodA.methodB(classB.methodC(classB.methodD)))}应该返回

 1.classA
 2.methodA
 3.methodB(classB.methodC(classB.methodD)))

我已经创建了这段代码

$expression = '${myvalue.fdsfs.fsdf.blo(fsdf.fsfds(fsfs.fs))}';
$pattern = '/\$\{(?:([a-zA-Z0-9]+)\.)(?:([a-zA-Z\d]+)\.)*([a-zA-Z\d.()]+)\}/';
if(preg_match($pattern, $expression, $matches))
{
    echo 'found'.'<br/>';
    for($i = 0; $i < count($matches); $i++)
        echo $i." ".$matches[$i].'<br/>';
}

结果是:
找到
0 ${myvalue.fdsfs.fsdf.blo(fsdf.fsfds(fsfs.fs))}
1 myvalue
2 fsdf
3 blo(fsdf.fsfds(fsfs.fs))

显然我很难提取重复的方​​法,并且它没有正确验证它(老实说,一旦我解决了另一个问题,我就把它留到最后了)所以允许使用空括号,并且它不会检查是否打开括号它必须关闭。

谢谢大家

更新

X m.buettner

谢谢你的帮助。我对您的代码进行了快速尝试,但它给出了一个非常小的问题,尽管我可以绕过它。问题与我没有在此处发布的先前代码之一相同,这是我尝试此字符串时:

$expression = '${myvalue.fdsfs}';

使用您的模式定义,它显示:

found
0 ${myvalue.fdsfs}
1 myvalue.fdsfs
2 myvalue
3 
4 fdsfs

如您所见,第三行被捕获为不存在的空白。我不明白它为什么这样做,所以你能建议我如何或者我必须忍受它,因为 php regex 限制?

那就是说我只能告诉你谢谢。您不仅回答了我的问题,而且还尝试输入尽可能多的信息,并提供了许多关于开发模式时应遵循的正确路径的建议。

最后一件事我(愚蠢)忘记添加一个小重要案例,即多个参数除以逗号,所以

$expression = '${classA.methodAA(classB.methodBA(classC.methodCA),classC.methodCB)}';
$expression = '${classA.methodAA(classB.methodBA(classC.methodCA),classC.methodCB,classD.mehtodDA)}';

必须有效。

我对此进行了编辑

    $expressionPattern =             
        '/
        ^                   # beginning of the string
        [$][{]              # literal ${
        (                   # group 1, used for recursion
          (                 # group 2 (class name)
            [a-z\d]+        # one or more alphanumeric characters
          )                 # end of group 2 (class name)
          [.]               # literal .
          (                 # group 3 (all intermediate method names)
            (?:             # non-capturing group that matches a single method name
              [a-z\d]+      # one or more alphanumeric characters
              [.]           # literal .
            )*              # end of method name, repeat 0 or more times
          )                 # end of group 3 (intermediate method names);
          (                 # group 4 (final method name and arguments)
            [a-z\d]+        # one or or more alphanumeric characters
            (?:             # non-capturing group for arguments
              [(]           # literal (
              (?1)          # recursively apply the pattern inside group 1
                (?:     # non-capturing group for multiple arguments        
                  [,]       # literal ,
                  (?1)      # recursively apply the pattern inside group 1 on parameters
                )*          # end of multiple arguments group; repeat 0 or more times
              [)]           # literal )
            )?              # end of argument-group; make optional
          )                 # end of group 4 (method name and arguments)  
        )                   # end of group 1 (recursion group)
        [}]                 # literal }
        $                   # end of the string
        /ix';   

X Casimir et Hippolyte

您的建议也很好,但它暗示使用此代码时会出现一些复杂的情况。我的意思是代码本身很容易理解,但它变得不那么灵活。也就是说,它也给了我很多信息,这些信息肯定会在未来有所帮助。

X 异形

感谢您的支持,但是当我尝试此操作时,您的代码会下降:

$sourcestring='${classA1.methodA0.methodA1.methodB1(classB.methodC(classB.methodD))}';

结果是:

Array

( [0] => 数组 ( [0] => ${classA1.methodA0.methodA1.methodB1(classB.methodC(classB.methodD))} )

[1] => Array
    (
        [0] => classA1
    )

[2] => Array
    (
        [0] => methodA0
    )

[3] => Array
    (
        [0] => methodA1.methodB1(classB.methodC(classB.methodD))
    )

)

它应该是

    [2] => Array
    (
        [0] => methodA0.methodA1
    )

[3] => Array
    (
        [0] => methodB1(classB.methodC(classB.methodD))
    )

)

或者

[2] => Array
    (
        [0] => methodA0
    )

[3] => Array
    (
        [0] => methodA1
    )

[4] => Array
    (
        [0] => methodB1(classB.methodC(classB.methodD))
    )

)
4

3 回答 3

6

这是困难的一个。递归模式通常超出了正则表达式的可能范围,即使可能,也可能导致非常难以理解和维护的表达式。

您使用的是 PHP 和 PCRE,它确实支持递归正则表达式结构(?n)。由于您的递归模式非常规则,因此可以使用正则表达式找到一些实用的解决方案。

我应该立即提及一个警告:由于您允许每个级别(在您的代码片段fdsfs和中fsdf)任意数量的“中间”方法调用,因此您无法在单独的捕获中获取所有这些。这对于 PCRE 来说是根本不可能的。每个匹配将始终产生相同的有限数量的捕获,这取决于您的模式包含的左括号的数量。如果重复使用一个捕获组(例如使用类似的东西([a-z]+\.)+),那么每次使用该组时,前一个捕获将被覆盖,并且您只会获得最后一个实例。因此,我建议您将所有“中间”方法调用一起捕获,然后简单地捕获explode该结果。

同样,您不能(如果您愿意)一次获得多个嵌套级别的捕获。因此,您想要的捕获(最后一个包含所有嵌套级别)是唯一的选择 - 然后您可以再次将该模式应用于最后一个匹配以进一步降低级别。

现在为实际表达式:

$pattern = '/
    ^                     # beginning of the string
    [$][{]                # literal ${
    (                     # group 1, used for recursion
      (                   # group 2 (class name)
        [a-z\d]+          # one or more alphanumeric characters
      )                   # end of group 2 (class name)
      [.]                 # literal .
      (                   # group 3 (all intermediate method names)
        (?:               # non-capturing group that matches a single method name
          [a-z\d]+        # one or more alphanumeric characters
          [.]             # literal .
        )*                # end of method name, repeat 0 or more times
      )                   # end of group 3 (intermediate method names);
      (                   # group 4 (final method name and arguments)
        [a-z\d]+          # one or or more alphanumeric characters
        (?:               # non-capturing group for arguments
          [(]             # literal (
          (?1)            # recursively apply the pattern inside group 1
          [)]             # literal )
        )?                # end of argument-group; make optional
      )                   # end of group 4 (method name and arguments)  
    )                     # end of group 1 (recursion group)
    [}]                   # literal }
    $                     # end of the string
    /ix';

一些一般注意事项:对于复杂的表达式(以及支持它的正则表达式风格),始终使用自由间距x修饰符,它允许您引入空格和注释以根据您的需要格式化表达式。没有它们,模式如下所示:

'/^[$][{](([a-z\d]+)[.]((?:[a-z\d]+[.])*)([a-z\d]+(?:[(](?1)[)])?))[}]$/ix'

即使您自己编写了正则表达式并且您是唯一一个参与该项目的人 - 从现在开始一个月后尝试理解这一点。

其次,我通过使用不区分大小写的i修饰符稍微简化了模式。它只是消除了一些混乱,因为您可以省略字母的大写变体。

第三,请注意,我在可能的情况下使用单字符类[$][.]转义字符。这只是一个口味问题,您可以自由使用反斜杠变体。我个人更喜欢字符类的可读性(我知道这里的其他人不同意),所以我也想向您展示这个选项。

第四,我在您的模式周围添加了锚点,因此${...}.

最后,递归是如何工作的?(?n)类似于 backreference \n,因为它指的是捕获组n(通过从左到右打开括号来计数)。不同之处在于反向引用尝试再次匹配 group 匹配的内容n,而(?n)再次应用该模式。那就是(.)\1连续两次匹配任何字符,而(.)(?1)匹配任何字符然后再次应用模式,因此匹配另一个任意字符。如果您在第 th 组中使用这些(?n)构造之一,您将获得递归。或指整个模式。这就是所有的魔法。n(?0)(?R)

上述模式应用于输入

 '${abc.def.ghi.jkl(mno.pqr(stu.vwx))}'

将导致捕获

0 ${abc.def.ghi.jkl(mno.pqr(stu.vwx))}
1 abc.def.ghi.jkl(mno.pqr(stu.vwx))
2 abc
3 def.ghi.
4 jkl(mno.pqr(stu.vwx))

请注意,您实际预期的输出存在一些差异:

0是整个匹配(在这种情况下只是输入字符串)。PHP 将始终首先报告此问题,因此您无法摆脱它。

1是包含递归部分的第一个捕获组。您在输出中不需要它,但(?n)不幸的是不能引用非捕获组,因此您也需要它。

2是所需的类名。

3是中间方法名称的列表,加上一个尾随句点。使用explode它很容易从中提取所有方法名称。

4是最终的方法名称,带有可选(递归)参数列表。现在您可以使用它,并在必要时再次应用该模式。请注意,对于完全递归的方法,您可能需要稍微修改模式。也就是说:在单独的第一步中去掉${and },以便整个模式具有与最终捕获完全相同的(递归)模式,并且您可以使用(?0)代替(?1). 然后匹配,删除方法名称和括号,然后重复,直到最后一次捕获中没有括号。

有关递归的更多信息,请查看PHP 的 PCRE 文档


为了说明我的最后一点,这是一个递归提取所有元素的片段:

if(!preg_match('/^[$][{](.*)[}]$/', $expression, $matches))
    echo 'Invalid syntax.';
else
    traverseExpression($matches[1]);

function traverseExpression($expression, $level = 0) {
    $pattern = '/^(([a-z\d]+)[.]((?:[a-z\d]+[.])*)([a-z\d]+(?:[(](?1)[)])?))$/i';
    if(preg_match($pattern, $expression, $matches)) {
        $indent = str_repeat(" ", 4*$level);
        echo $indent, "Class name: ", $matches[2], "<br />";
        foreach(explode(".", $matches[3], -1) as $method)
            echo $indent, "Method name: ", $method, "<br />";
        $parts = preg_split('/[()]/', $matches[4]);
        echo $indent, "Method name: ", $parts[0], "<br />";
        if(count($parts) > 1) {
            echo $indent, "With arguments:<br />";
            traverseExpression($parts[1], $level+1);
        }
    }
    else
    {
        echo 'Invalid syntax.';
    }
}

再次注意,我不建议将模式用作单线,但这个答案已经足够长了。

于 2013-06-07T17:47:52.720 回答
4

您可以使用相同的模式进行验证和提取,例如:

$subjects = array(
'${classA.methodA.methodB(classB.methodC(classB.methodD))}',
'${classA.methodA.methodB}',
'${classA.methodA.methodB()}',
'${methodB(methodC(classB.methodD))}',
'${classA.methodA.methodB(classB.methodC(classB.methodD(classC.methodE)))}',
'${classA.methodA.methodB(classB.methodC(classB.methodD(classC.methodE())))}'
);

$pattern = <<<'LOD'
~
# definitions
(?(DEFINE)(?<vn>[a-z]\w*+))

# pattern
^\$\{
    (?<classA>\g<vn>)\.
    (?<methodA>\g<vn>)\.
    (?<methodB>
        \g<vn> ( 
            \( \g<vn> \. \g<vn> (?-1)?+ \)
        )?+
    )
}$

~x
LOD;

foreach($subjects as $subject) {
    echo "\n\nsubject: $subject";
    if (preg_match($pattern, $subject, $m))
        printf("\nclassA: %s\nmethodA: %s\nmethodB: %s",
            $m['classA'], $m['methodA'], $m['methodB']);
    else
        echo "\ninvalid string";    
}

正则表达式解释:
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

在模式的末尾,您可以看到允许在模式内使用空格、换行符和注释的修饰符 x。

首先,模式从命名组vn(变量名)的定义开始,在这里您可以定义所有模式的 classA 或 methodB 的外观。然后你可以在所有模式中引用这个定义\g<vn>

请注意,您可以定义是否要为类和方法添加其他定义的不同类型的名称。例子:

(?(DEFINE)(?<cn>....))  # for class name
(?(DEFINE)(?<mn>....))  # for method name 

模式本身:

(?<classA>\g<vn>)使用vn中定义的模式 在命名组classA中捕获

方法A也是一样

methodB不同,因为它可以包含嵌套括号,这就是我在这部分使用递归模式的原因。

细节:

\g<vn>         # the method name (methodB)
(              # open a capture group
    \(         # literal opening parenthesis
    \g<vn> \. \g<vn> # for classB.methodC⑴
    (?-1)?+    # refer the last capture group (the actual capture group)
               # one or zero time (possessive) to allow the recursion stop
               # when there is no more level of parenthesis
    \)         # literal closing parenthesis
)?+            # close the capture group 
               # one or zero time (possessive)
               # to allow method without parameters

\g<vn>(?>\.\g<vn>)+⑴<i>如果您想允许多个方法,您可以将其替换为。

关于所有格量词:

您可以+在量词 ( * + ?) 之后添加以使其具有所有格,优点是正则表达式引擎知道它不必回溯以测试与子模式匹配的其他方法。正则表达式然后更有效。

于 2013-06-07T17:47:25.693 回答
2

描述

此表达式将仅匹配和捕获${classA.methodA.methodB(classB.methodC(classB.methodD)))}${classA.methodA.methodB}格式。

(?:^|\n|\r)[$][{]([^.(}]*)[.]([^.(}]*)[.]([^(}]*(?:[(][^}]+[)])?)[}](?=\n|\r|$)

在此处输入图像描述

团体

第 0 组获得从开始美元符号到闭合波浪括号的整个匹配

  1. 得到类
  2. 获取第一个方法
  3. 获取第二种方法,后跟所有文本,但不包括关闭的波浪括号。如果该组有空方括号,()则该匹配将失败

PHP 代码示例:

<?php
$sourcestring="${classA1.methodA1.methodB1(classB.methodC(classB.methodD)))}
${classA2.methodA2.methodB2}
${classA3.methodA3.methodB3()}
${methodB4(methodC4(classB4.methodD)))}
${classA5.methodA5.methodB5(classB.methodC(classB.methodD)))}";
preg_match_all('/(?:^|\n|\r)[$][{]([^.(}]*)[.]([^.(}]*)[.]([^(}]*(?:[(][^}]+[)])?)[}](?=\n|\r|$)/im',$sourcestring,$matches);
echo "<pre>".print_r($matches,true);
?>

$matches Array:
(
    [0] => Array
        (
            [0] => ${classA1.methodA1.methodB1(classB.methodC(classB.methodD)))}
            [1] => 
${classA2.methodA2.methodB2}
            [2] => 
${classA5.methodA5.methodB5(classB.methodC(classB.methodD)))}
        )

    [1] => Array
        (
            [0] => classA1
            [1] => classA2
            [2] => classA5
        )

    [2] => Array
        (
            [0] => methodA1
            [1] => methodA2
            [2] => methodA5
        )

    [3] => Array
        (
            [0] => methodB1(classB.methodC(classB.methodD)))
            [1] => methodB2
            [2] => methodB5(classB.methodC(classB.methodD)))
        )

)

免责声明

  • 我在类和方法名称的末尾添加了一个数字,以帮助说明组中发生的事情
  • OP 中提供的示例文本没有平衡的开闭圆括号。
  • 虽然()将被禁止(())将被允许
于 2013-06-07T17:49:19.123 回答