尽管我对伪代码中的正则表达式有足够的了解,但是我仍然无法翻译我想在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)
。由于您的递归模式非常有规律,因此可以使用正则表达式找到一些实用的解决方案。
我要立即提一个警告:由于您允许每个级别调用任意数量的“中间”方法(在代码段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
修饰符略微简化了模式。它只是消除了一些困惑,因为您可以省略字母的大写变体。第三,请注意,在可能的情况下,我使用诸如
[$]
和[.]
的单字符类来转义字符。这只是一个品味问题,您可以自由使用反斜杠变体。我个人更喜欢字符类的可读性(而且我知道这里的其他人也不同意),所以我也想向您介绍这个选项。第四,我在您的模式周围添加了 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/