7

在 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;
  }
4

2 回答 2

9

代替 a preg_split,做 a 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]+可以分为三个部分:

  1. \((?:[^()]|(?R))+\), 匹配平衡的括号对
  2. '[^']*'匹配带引号的字符串
  3. [^(),\s]+匹配任何不包含'('')'','空白字符的字符序列
于 2013-03-05T21:11:21.967 回答
3

疯狂的解决方案

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

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

正则表达式101

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

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

ideone

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

假设

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

分解

\G\s**
(
  (
    \(
    (?:
        \s**
        (?2)
        \s**
        (?(?!\)),)
      |
        \s**
        [^()',\s]++
        \s**
        (?(?!\)),)
      |
        \s**
        '[^'\r\n]*+'
        \s**
        (?(?!\)),)
    )++
    \)
  )
  |
  [^()',\s]++
  |
  '[^'\r\n]*+'
)
\s*+(?:,|$)
于 2013-03-05T22:46:32.727 回答