PHP:用逗号分割字符串,但不是在大括号或引号之间?

Posted

技术标签:

【中文标题】PHP:用逗号分割字符串,但不是在大括号或引号之间?【英文标题】:PHP: split string on comma, but NOT when between braces or quotes? 【发布时间】:2013-02-20 10:52:48 【问题描述】:

php 中,我有以下字符串:

$str = "AAA, BBB, (CCC,DDD), 'EEE', 'FFF,GGG', ('HHH','III'), (('JJJ','KKK'), LLL, (MMM,NNN)) , OOO"; 

我需要把这个字符串分成以下几部分:

AAA
BBB
(CCC,DDD)
'EEE'
'FFF,GGG'
('HHH','III')
(('JJJ','KKK'),LLL, (MMM,NNN))
OOO

我尝试了几个正则表达式,但找不到解决方案。 有什么想法吗?

更新

在处理格式错误的数据、转义引号等时,我认为使用正则表达式并不是最好的解决方案。

感谢这里提出的建议,我找到了一个使用解析的函数,我重写了它以满足我的需要。它可以处理不同类型的括号,分隔符和引号也是参数。

 function explode_brackets($str, $separator=",", $leftbracket="(", $rightbracket=")", $quote="'", $ignore_escaped_quotes=true ) 

    $buffer = '';
    $stack = array();
    $depth = 0;
    $betweenquotes = false;
    $len = strlen($str);
    for ($i=0; $i<$len; $i++) 
      $previouschar = $char;
      $char = $str[$i];
      switch ($char) 
        case $separator:
          if (!$betweenquotes) 
            if (!$depth) 
              if ($buffer !== '') 
                $stack[] = $buffer;
                $buffer = '';
              
              continue 2;
            
          
          break;
        case $quote:
          if ($ignore_escaped_quotes) 
            if ($previouschar!="\\") 
              $betweenquotes = !$betweenquotes;
            
           else 
            $betweenquotes = !$betweenquotes;
          
          break;
        case $leftbracket:
          if (!$betweenquotes) 
            $depth++;
          
          break;
        case $rightbracket:
          if (!$betweenquotes) 
            if ($depth) 
              $depth--;
             else 
              $stack[] = $buffer.$char;
              $buffer = '';
              continue 2;
            
          
          break;
        
        $buffer .= $char;
    
    if ($buffer !== '') 
      $stack[] = $buffer;
    

    return $stack;
  

【问题讨论】:

这个怎么样:***.com/questions/1084764/… 如果我必须在 mysql 层而不是 PHP 中执行此操作怎么办? 【参考方案1】:

不要使用preg_split,而是使用preg_match_all

$str = "AAA, BBB, (CCC,DDD), 'EEE', 'FFF,GGG', ('HHH','III'), (('JJJ','KKK'), LLL, (MMM,NNN)) , OOO"; 

preg_match_all("/\((?:[^()]|(?R))+\)|'[^']*'|[^(),\s]+/", $str, $matches);

print_r($matches);

将打印:

数组
(
    [0] => 数组
        (
            [0] => AAA
            [1] => BBB
            [2] => (CCC,DDD)
            [3] => 'EEE'
            [4] => 'FFF,GGG'
            [5] => ('HHH','III')
            [6] => (('JJJ','KKK'), LLL, (MMM,NNN))
            [7] => 噢噢噢
        )

)

正则表达式\((?:[^()]|(?R))+\)|'[^']*'|[^(),\s]+可以分为三部分:

    \((?:[^()]|(?R))+\),匹配平衡的括号对 '[^']*' 匹配带引号的字符串 [^(),\s]+ 匹配任何不包含 '('')'',' 或空白字符的字符序列

【讨论】:

虽然你可以匹配,但它通常不能保证当它针对错误的输入字符串运行时。 嗨,巴特,非常感谢。你能想出什么办法让 'FFF,GGG' 显示为 1 匹配吗? 再次感谢,它现在很好用,所以我会接受您的正确答案。但我仍然决定在我的项目中使用解析,因为输入数据格式错误和转义引号的可能性,请参阅我对问题的更新。 @Dylan:我的解决方案可以抵抗格式错误的输入数据,并且可以修改为使用转义引号。但是话又说回来,如果没有深入的正则表达式知识,它就不容易维护,并且无法指出语法错误的确切位置(它知道错误在前面的某个地方,但不确切在哪里)。在这种情况下,手动解析会更好。 @BartKiers 根据我的用例,这个答案看起来很棒,但不起作用,你能在***.com/questions/37183910/…帮助我吗【参考方案2】:

疯狂的解决方案

一个简单的正则表达式,它标记化并验证它提取的所有标记:

\G\s*+((\((?:\s*+(?2)\s*+(?(?!\)),)|\s*+[^()',\s]++\s*+(?(?!\)),)|\s*+'[^'\r\n]*+'\s*+(?(?!\)),))++\))|[^()',\s]++|'[^'\r\n]*+')\s*+(?:,|$)

Regex101

把它放在字符串文字中,带分隔符:

'/\G\s*+((\((?:\s*+(?2)\s*+(?(?!\)),)|\s*+[^()\',\s]++\s*+(?(?!\)),)|\s*+\'[^\'\r\n]*+\'\s*+(?(?!\)),))++\))|[^()\',\s]++|\'[^\'\r\n]*+\')\s*+(?:,|$)/'

ideone

结果是在捕获组 1。在 ideone 上的示例中,我指定了PREG_OFFSET_CAPTURE 标志,以便您可以检查组 0(整个匹配)中的 last match 是否整个源字符串是否被消费。

假设

未引用的文本不得包含任何空白字符,如\s 所定义。因此,它可能不会跨越多行。 未引用的文本不得包含()',。 不带引号的文本必须至少包含 1 个字符。 单引号文本不能跨越多行。 单引号文本可能不包含引号。因此,无法指定'。 单引号文本可能为空。 括号标记包含以下一项或多项作为子标记:非引号文本标记、单引号文本标记或其他括号标记。 在括号token中,相邻的2个子token之间正好被一个,隔开 括号标记以( 开头,以) 结尾。 因此,括号令牌必须具有平衡括号,并且不允许使用空括号()。 输入将包含以下一项或多项:非引号文本、单引号文本或括号标记。输入中的标记用逗号, 分隔。单个尾随逗号 , 被视为有效。 空白字符(由\s定义,包括换行符)在标记、逗号,分隔标记和括号(、@987654341之间任意允许@ 括号标记。

故障

\G\s** ( ( \( (?: \s** (?2) \s** (?(?!\)),) | \s** [^()',\s]++ \s** (?(?!\)),) | \s** '[^'\r\n]*+' \s** (?(?!\)),) )++ \) ) | [^()',\s]++ | '[^'\r\n]*+' ) \s*+(?:,|$)

【讨论】:

以上是关于PHP:用逗号分割字符串,但不是在大括号或引号之间?的主要内容,如果未能解决你的问题,请参考以下文章

Java正则表达式用逗号分割字符串,但忽略引号和括号[重复]

shell脚本双引号大括号if语句注意事项

oracle 用逗号分割一个带引号的字符串

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

用记事本++中的正则表达式替换引号中的所有逗号

用逗号分割数字,忽略双引号里面的逗号