PHP 和 RegEx:用不在括号内的逗号(以及嵌套括号)拆分字符串
Posted
技术标签:
【中文标题】PHP 和 RegEx:用不在括号内的逗号(以及嵌套括号)拆分字符串【英文标题】:PHP and RegEx: Split a string by commas that are not inside brackets (and also nested brackets) 【发布时间】:2010-11-08 06:11:44 【问题描述】:两天前,我开始研究代码解析器,但遇到了困难。
如何用不在括号内的逗号分隔字符串,让我告诉你我的意思:
我有这个字符串要解析:
one, two, three, (four, (five, six), (ten)), seven
我想得到这个结果:
array(
"one";
"two";
"three";
"(four, (five, six), (ten))";
"seven"
)
但我得到:
array(
"one";
"two";
"three";
"(four";
"(five";
"six)";
"(ten))";
"seven"
)
我如何在 php RegEx 中做到这一点。
提前谢谢你!
【问题讨论】:
【参考方案1】:您可以更轻松地做到这一点:
preg_match_all('/[^(,\s]+|\([^)]+\)/', $str, $matches)
但如果你使用真正的解析器会更好。也许是这样的:
$str = 'one, two, three, (four, (five, six), (ten)), seven';
$buffer = '';
$stack = array();
$depth = 0;
$len = strlen($str);
for ($i=0; $i<$len; $i++)
$char = $str[$i];
switch ($char)
case '(':
$depth++;
break;
case ',':
if (!$depth)
if ($buffer !== '')
$stack[] = $buffer;
$buffer = '';
continue 2;
break;
case ' ':
if (!$depth)
continue 2;
break;
case ')':
if ($depth)
$depth--;
else
$stack[] = $buffer.$char;
$buffer = '';
continue 2;
break;
$buffer .= $char;
if ($buffer !== '')
$stack[] = $buffer;
var_dump($stack);
【讨论】:
是的,它更简单,但在嵌套括号的情况下不起作用,例如:一、二、三、(四、(五、六)、(十))、七跨度> 这就是你必须使用真正的解析器的地方。正则表达式不能计算或处理状态。 我必须使用正则表达式。正则表达式是递归和贪婪的,你可以使用它们来完成。 不,你不能。当然,现代实现中的某些功能可以实现这一点,例如 .NET 的 Balancing 组(?<name1-name2> … )
msdn.microsoft.com/bs2twtah.aspx。但他们使用状态机,这不再是经典方式的正则表达式。
这个更正确,但仍然不适用于嵌套括号 /[^(,]*(?:([^)]+))?[^),]*/【参考方案2】:
嗯...好的已经标记为已回答,但是既然你要求一个简单的解决方案,我还是会尝试:
$test = "one, two, three, , , ,(four, five, six), seven, (eight, nine)";
$split = "/([(].*?[)])|(\w)+/";
preg_match_all($split, $test, $out);
print_r($out[0]);
输出
Array
(
[0] => one
[1] => two
[2] => three
[3] => (four, five, six)
[4] => seven
[5] => (eight, nine)
)
【讨论】:
非常感谢,非常感谢您的帮助。但是现在我意识到我也会遇到嵌套括号,并且您的解决方案不适用。【参考方案3】:你不能,直接。你至少需要可变宽度的lookbehind,最后我知道PHP的PCRE只有固定宽度的lookbehind。
我的第一个建议是首先从字符串中提取带括号的表达式。不过,我对您的实际问题一无所知,所以我不知道这是否可行。
【讨论】:
是的,这就是我打算使用的 hack。用 $1、$2 或类似的东西替换括号,拆分字符串,然后恢复结果中的括号。谢谢! 重点是你描述的不是正则语言,所以正则表达式不合适。因此,首先解析出所有嵌套部分不是“hack”,而是最明智的做法。【参考方案4】:我想不出使用单个正则表达式的方法,但是很容易将一些有效的东西组合在一起:
function process($data)
$entries = array();
$filteredData = $data;
if (preg_match_all("/\(([^)]*)\)/", $data, $matches))
$entries = $matches[0];
$filteredData = preg_replace("/\(([^)]*)\)/", "-placeholder-", $data);
$arr = array_map("trim", explode(",", $filteredData));
if (!$entries)
return $arr;
$j = 0;
foreach ($arr as $i => $entry)
if ($entry != "-placeholder-")
continue;
$arr[$i] = $entries[$j];
$j++;
return $arr;
如果你这样调用它:
$data = "one, two, three, (four, five, six), seven, (eight, nine)";
print_r(process($data));
它输出:
Array
(
[0] => one
[1] => two
[2] => three
[3] => (four, five, six)
[4] => seven
[5] => (eight, nine)
)
【讨论】:
非常感谢,这应该可以了。这是我最初计划的方式,但我认为存在更简单的方法。 您的方法无法解析“一、二、三、((五)、(四(六)))、七、八、九”。我认为正确的正则表达式是递归的:/(([^()]+|(?R))*)/. 不过,当我第一次写这个答案时,你没有提到它必须能够解析递归表达式。不过,在我写完这篇文章后,其他人肯定会提出更好的解决方案。【参考方案5】:笨拙,但它可以完成工作......
<?php
function split_by_commas($string)
preg_match_all("/\(.+?\)/", $string, $result);
$problem_children = $result[0];
$i = 0;
$temp = array();
foreach ($problem_children as $submatch)
$marker = '__'.$i++.'__';
$temp[$marker] = $submatch;
$string = str_replace($submatch, $marker, $string);
$result = explode(",", $string);
foreach ($result as $key => $item)
$item = trim($item);
$result[$key] = isset($temp[$item])?$temp[$item]:$item;
return $result;
$test = "one, two, three, (four, five, six), seven, (eight, nine), ten";
print_r(split_by_commas($test));
?>
【讨论】:
【参考方案6】:恐怕解析嵌套括号可能非常困难,例如
one, two, (three, (four, five))
仅使用正则表达式。
【讨论】:
【参考方案7】:我觉得值得注意的是,您应该尽可能避免使用正则表达式。为此,您应该知道对于 PHP 5.3+,您可以使用 str_getcsv()。但是,如果您正在处理文件(或文件流),例如 CSV 文件,那么函数 fgetcsv() 可能是您所需要的,并且它从 PHP4 开始可用。
最后,我很惊讶没有人使用preg_split(),或者它没有按需要工作?
【讨论】:
是的,我想使用 preg_split(),但是忽略括号中逗号的 RegEx 是什么? 啊,是的,好点,在尝试一两分钟后,我可以看到它在规定的条件下具有挑战性。 是的,您是对的,我也尝试了您的解决方案,但不起作用。还是谢谢你。以上是关于PHP 和 RegEx:用不在括号内的逗号(以及嵌套括号)拆分字符串的主要内容,如果未能解决你的问题,请参考以下文章