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 组 (?&lt;name1-name2&gt; … ) 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:用不在括号内的逗号(以及嵌套括号)拆分字符串的主要内容,如果未能解决你的问题,请参考以下文章

如何在.net正则表达式中找到不在括号内的逗号?

PHP/REGEX:获取括号内的字符串

正则表达式用逗号分隔 - 不在括号或单引号内

Java Regex - 拆分逗号分隔列表,但在括号内排除逗号

使RegEx组将行拆分为列

修复 RegEx 以正确捕获括号内的文本