PHP 正则表达式:如何切割嵌套模式?
PHP regexp: how to cut nested patterns?
我有一个很愚蠢的问题,让我困惑了一段时间...
我想解析一些文本,格式如下:
CUT-FROM-A ...
CUT-FROM-B ...
CUT-TO ...
CUT-TO
apple
CUT-FROM-C ...
CUT-TO
orange
在这个例子中,我想提取 'fruits',忽略从 CUT-FROM-X
到相应的 TO
的所有内容。 'corresponding' 我的意思是 "from inside to outside",或者如果它更清楚,请尝试用开括号替换任何 CUT-FROM-A
,并将任何 CUT-TO
替换为闭括号:然后,我想忽略括号内的内容,包括括号。
我希望这很清楚,但恐怕不是...:-(
我想这里的主要困难是 'closing brackets' 都有相同的签名,所以不能轻易地与相关的开瓶器相关联...
我试过这样的(不贪心):
$output_text = preg_replace("/CUT-FROM-.*?TO/s", "", $input_text);
但这会在输出中留下第二个 CUT-TO
...
像这样的东西(贪心):
$output_text = preg_replace("/CUT-FROM-.*TO/s", "", $input_text);
但这吃掉了第一个 'fruit'... :-(
This 是我对 regex101 的测试。
任何人都可以给我一些启示吗?
想一想,您可以处理与您想要的上下文匹配的每一行,而不是替换。
preg_match_all('~^(?!.*CUT-(?:FROM|TO)).+$~mi', $text, $matches);
var_dump($matches[0]);
输出
array(2) {
[0]=> string(5) "apple"
[1]=> string(6) "orange"
}
您可以使用单个 regex
来完成此操作,但您可以使用使用小型 regex
来完成较小任务的简单脚本来更好地完成此操作。
思路:逐行解析文本,使用regex
识别行类型。在每个 'CUT-FROM' 行上,将信息(行本身或其他内容)添加到堆栈(使用 array_push()
)。在每 'CUT-TO' 行从堆栈中删除顶部元素(使用 array_pop()
.
根据需要处理其他行。例如,如果您需要忽略 'CUT-FROM' 和相应的 'CUT-TO' 行之间的行,您需要检查堆栈是否为空以了解您在一对中。如果堆栈为空,则所有 'CUT-FROM' 都与 'CUT-TO' 行配对,并且您正在解析任何外壳之外的行。
这种方法还为您提供了一种检测和处理 (ignore/fix/report/whatever) 输入文本中错误的好方法。
示例程序:
text = <<< END_TEXT
CUT-FROM-A ...
ignore this,
CUT-FROM-B ...
this,
CUT-TO ...
and this
CUT-TO
apple
CUT-FROM-C ...
CUT-TO
orange
END_TEXT;
$lines = explode("\n", $text);
$stack = array();
foreach ($lines as $i => $line) {
// Check if it's a 'CUT-FROM-' line
if (preg_match('/^CUT-FROM-/', $line)) {
array_push($stack, $line);
continue;
}
// Check if it's a 'CUT-TO' line
if (preg_match('/^CUT-TO/', $line)) {
if (array_pop($stack) === NULL) {
// an unpaired 'CUT-TO' was found
echo("An unpaired 'CUT-TO' was found on line ".($i + 1).". Will ignore it.\n");
}
continue;
}
// A regular line
if (count($stack) > 0) {
// inside a (CUT-FROM, CUT-TO) pair
// count($stack) tells how many pairs are around this item
// ignore it
} else {
// outside any pair
echo ($line."\n");
}
}
// Check if all the 'CUT-FROM' lines were closed
if (count($stack) > 0) {
echo('Found that '.count($stack)." 'CUT-TO' lines are missing at the end of processing.\n");
}
由于您要求的是正则表达式解决方案,因此可读的递归正则表达式为:
(?(DEFINE)
(?<cut>
^CUT-FROM-
(?&content)*?
^CUT-TO
)
(?<content>
(?: (?!CUT-(?:FROM-|TO)) . )++
| (?&cut)
)
)
(?&cut)
与 smx
选项一起使用。这匹配您要忽略的所有内容,因此您可以将其替换为空字符串。语法 (?&something)
表示 递归到 something
,它与 \g<something>
.
相同
这是一个更紧凑的版本,其功能基本相同:
^CUT-FROM-
(?:(?:(?!CUT-(?:FROM-|TO)) . )++ | (?R))*?
^CUT-TO
在此版本中,(?R)
表示递归整个模式。它仍然使用 smx
选项。单行版本(没有 x
)将是:
(?sm)^CUT-FROM-(?:(?:(?!CUT-(?:FROM-|TO)).)++|(?R))*?^CUT-TO
但我建议不要做这样的事情。更喜欢带有 (?(DEFINE) ... )
的版本以提高可读性。
我有一个很愚蠢的问题,让我困惑了一段时间...
我想解析一些文本,格式如下:
CUT-FROM-A ...
CUT-FROM-B ...
CUT-TO ...
CUT-TO
apple
CUT-FROM-C ...
CUT-TO
orange
在这个例子中,我想提取 'fruits',忽略从 CUT-FROM-X
到相应的 TO
的所有内容。 'corresponding' 我的意思是 "from inside to outside",或者如果它更清楚,请尝试用开括号替换任何 CUT-FROM-A
,并将任何 CUT-TO
替换为闭括号:然后,我想忽略括号内的内容,包括括号。
我希望这很清楚,但恐怕不是...:-(
我想这里的主要困难是 'closing brackets' 都有相同的签名,所以不能轻易地与相关的开瓶器相关联...
我试过这样的(不贪心):
$output_text = preg_replace("/CUT-FROM-.*?TO/s", "", $input_text);
但这会在输出中留下第二个 CUT-TO
...
像这样的东西(贪心):
$output_text = preg_replace("/CUT-FROM-.*TO/s", "", $input_text);
但这吃掉了第一个 'fruit'... :-(
This 是我对 regex101 的测试。
任何人都可以给我一些启示吗?
想一想,您可以处理与您想要的上下文匹配的每一行,而不是替换。
preg_match_all('~^(?!.*CUT-(?:FROM|TO)).+$~mi', $text, $matches);
var_dump($matches[0]);
输出
array(2) {
[0]=> string(5) "apple"
[1]=> string(6) "orange"
}
您可以使用单个 regex
来完成此操作,但您可以使用使用小型 regex
来完成较小任务的简单脚本来更好地完成此操作。
思路:逐行解析文本,使用regex
识别行类型。在每个 'CUT-FROM' 行上,将信息(行本身或其他内容)添加到堆栈(使用 array_push()
)。在每 'CUT-TO' 行从堆栈中删除顶部元素(使用 array_pop()
.
根据需要处理其他行。例如,如果您需要忽略 'CUT-FROM' 和相应的 'CUT-TO' 行之间的行,您需要检查堆栈是否为空以了解您在一对中。如果堆栈为空,则所有 'CUT-FROM' 都与 'CUT-TO' 行配对,并且您正在解析任何外壳之外的行。
这种方法还为您提供了一种检测和处理 (ignore/fix/report/whatever) 输入文本中错误的好方法。
示例程序:
text = <<< END_TEXT
CUT-FROM-A ...
ignore this,
CUT-FROM-B ...
this,
CUT-TO ...
and this
CUT-TO
apple
CUT-FROM-C ...
CUT-TO
orange
END_TEXT;
$lines = explode("\n", $text);
$stack = array();
foreach ($lines as $i => $line) {
// Check if it's a 'CUT-FROM-' line
if (preg_match('/^CUT-FROM-/', $line)) {
array_push($stack, $line);
continue;
}
// Check if it's a 'CUT-TO' line
if (preg_match('/^CUT-TO/', $line)) {
if (array_pop($stack) === NULL) {
// an unpaired 'CUT-TO' was found
echo("An unpaired 'CUT-TO' was found on line ".($i + 1).". Will ignore it.\n");
}
continue;
}
// A regular line
if (count($stack) > 0) {
// inside a (CUT-FROM, CUT-TO) pair
// count($stack) tells how many pairs are around this item
// ignore it
} else {
// outside any pair
echo ($line."\n");
}
}
// Check if all the 'CUT-FROM' lines were closed
if (count($stack) > 0) {
echo('Found that '.count($stack)." 'CUT-TO' lines are missing at the end of processing.\n");
}
由于您要求的是正则表达式解决方案,因此可读的递归正则表达式为:
(?(DEFINE)
(?<cut>
^CUT-FROM-
(?&content)*?
^CUT-TO
)
(?<content>
(?: (?!CUT-(?:FROM-|TO)) . )++
| (?&cut)
)
)
(?&cut)
与 smx
选项一起使用。这匹配您要忽略的所有内容,因此您可以将其替换为空字符串。语法 (?&something)
表示 递归到 something
,它与 \g<something>
.
这是一个更紧凑的版本,其功能基本相同:
^CUT-FROM-
(?:(?:(?!CUT-(?:FROM-|TO)) . )++ | (?R))*?
^CUT-TO
在此版本中,(?R)
表示递归整个模式。它仍然使用 smx
选项。单行版本(没有 x
)将是:
(?sm)^CUT-FROM-(?:(?:(?!CUT-(?:FROM-|TO)).)++|(?R))*?^CUT-TO
但我建议不要做这样的事情。更喜欢带有 (?(DEFINE) ... )
的版本以提高可读性。