尽管我对伪代码中的正则表达式有足够的了解,但是我仍然无法翻译我想在php regex perl中执行的操作。
我正在尝试使用preg_match提取表达式的一部分。
我有以下字符串${classA.methodA.methodB(classB.methodC(classB.methodD)))},我需要做2件事:

一种。验证语法

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

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

    1. A类
    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卡西米尔和希波吕特

    您的建议也不错,但是使用此代码时可能会出现一些复杂的情况。我的意思是代码本身很容易理解,但灵活性却降低了。话虽如此,它也为我提供了很多信息,这些信息将来肯定会对您有所帮助。

    X Denomales

    感谢您的支持,但是当我尝试这样做时,您的代码就掉了:
    $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))
        )
    
    )
    

    最佳答案

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

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

    我要立即提一个警告:由于您允许每个级别调用任意数量的“中间”方法(在代码段fdsfsfsdf中),因此您无法在单独的捕获中获得所有这些方法。使用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修饰符略微简化了模式。它只是消除了一些困惑,因为您可以省略字母的大写变体。

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

    第四,我在您的模式周围添加了 anchor ,以便在${...}之外没有无效的语法。

    最后,递归如何工作? (?n)与反向引用\n相似,因为它引用捕获组n(通过从左到右打开括号来计数)。区别在于,反向引用尝试再次匹配组n匹配的内容,而(?n)再次应用该模式。也就是说(.)\1连续两次匹配任何字符,而(.)(?1)匹配任何字符,然后再次应用模式,因此匹配了另一个任意字符。如果您在第(?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是最终的方法名称,带有可选的(递归)参数列表。现在,您可以执行此操作,并在必要时再次应用该模式。请注意,对于完全递归的方法,您可能需要略微修改模式。那就是:在单独的第一步中剥离${},以便整个模式具有与最终捕获完全相同的(递归)模式,并且您可以使用(?0)而不是(?1)。然后匹配,删除方法名称和括号,然后重复,直到最后一次捕获中不再有括号为止。

    有关递归的更多信息,请查看PHP's PCRE documentation

    为了说明我的最后一点,这是一个递归提取所有元素的代码段:
    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.';
        }
    }
    

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

    关于PHP Regex preg_match提取,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/16987673/

    10-12 23:13