【问题标题】:PHP: Parse comma-delimited string outside single and double quotes and parenthesesPHP:解析单引号和双引号以及括号之外的逗号分隔字符串
【发布时间】:2015-08-05 04:37:51
【问题描述】:

我已经找到了这个问题的几个部分答案,但没有一个能满足我的所有需求......

我正在尝试解析用户生成的字符串,就好像它是一系列 php 函数参数以确定参数的数量:

这个字符串:

$arg1,$arg2='ABC,DEF',$arg3="GHI\",JKL",$arg4=array(1,'2)',"3\"),")

将作为函数的参数插入:

function my_function( [insert string here] ){ ... }

我需要解析逗号上的字符串,考虑到单引号和双引号、括号以及转义的引号和括号来创建数组:

array(4) {
  [0] => $arg1
  [1] => $arg2='ABC,DEF'
  [2] => $arg3="GHI\",JKL"
  [3] => $arg4=array(1,'2)',"3\"),")
}

感谢任何有关正则表达式或解析器函数的帮助!

【问题讨论】:

标签: php regex parsing


【解决方案1】:

使用经典的 csv 工具无法解决此问题,因为有多个字符能够保护字符串的某些部分。 使用preg_split 是可能的,但会导致非常复杂和低效的模式。所以最好的方法是使用preg_match_all。然而,有几个问题需要解决:

  • 根据需要,必须忽略括在引号或括号中的逗号(视为没有特殊含义的字符,而不是分隔符)
  • 你需要提取参数,但你需要检查字符串是否也有良好的格式,否则匹配结果可能完全错误!

对于第一点,您可以定义子模式来描述每种情况:引用的部分、括号之间的部分,以及能够匹配完整参数并在需要时使用前两个子模式的更通用的子模式。

请注意,括号子模式也需要引用通用子模式,因为它可以包含任何内容(也可以包含逗号)。

第二点可以使用\G 锚来解决,确保所有匹配都是连续的。但是您需要确保已到达字符串的末尾。为此,您可以在主模式末尾添加一个可选的空捕获组,该组仅在字符串 \z 结尾的锚点成功时创建。

$subject = <<<'EOD'
$arg1,$arg2='ABC,DEF',$arg3="GHI\",JKL",$arg4=array(1,'2)',"3\"),")
EOD;

$pattern = <<<'EOD'
~
  # named groups definitions
  (?(DEFINE) # this definition group allows to define the subpatterns you want
             # without matching anything
      (?<quotes>
          ' [^'\\]*+ (?s:\\.[^'\\]*)*+ ' | " [^"\\]*+ (?s:\\.[^"\\]*)*+ "
      )
      (?<brackets> \( \g<content> (?: ,+ \g<content> )*+ \) )
      (?<content> [^,'"()]*+        # ' # (<-- comment for SO syntax highlighting)
                  (?:
                      (?: \g<brackets> | \g<quotes> )
                      [^,'"()]*     # ' #
                  )*+
      )
  )
  # the main pattern
  (?: # two possible beginings
      \G(?!\A) , # a comma contiguous to a previous match
    |            #  OR
      \A         # the start of the string
  ) 
  (?<param> \g<content> )
  (?: \z (?<check>) )? # create an item "check" when the end is reached
~x
EOD;

$result = false;

if ( preg_match_all($pattern, $subject, $matches, PREG_SET_ORDER) &&
     isset(end($matches)['check']) )
    $result = array_map(function ($i) { return $i['param']; }, $matches);
else 
   echo 'bad format' . PHP_EOL;

var_dump($result);

demo

【讨论】:

  • 非常彻底的解决方案。如果 ,$ 分隔符落在引号内,这也有效。我将需要更多时间来消化,但我确实做了一点调整以适应逗号周围的潜在空白。我改变了这个: \G(?!\A) ,这个: \G(?!\A)\s*,\s* demo
  • @Matt:这种模式有点笼统,例如允许空参数,但在需要时可以更加明确。例如,您可以将(?&lt;param&gt; \g&lt;content&gt; ) 更改为类似(?&lt;param&gt; \$\w+ = \g&lt;content&gt; )
  • @Matt:关于参数的修剪,你也可以在array_map中执行它们,如下所示:return trim($i['param']);
  • @Matt:经过反思,改成\G(?!\A) \s*,\s*是个坏主意,因为\g&lt;content&gt;是贪婪的,所以不能这样修剪右边。
【解决方案2】:

如果你想使用正则表达式,你可以使用这样的东西:

(.+?)(?:,(?=\$)|$)

Working demo

php代码:

$re = '/(.+?)(?:,(?=\$)|$)/'; 
$str = "\$arg1,\$arg2='ABC,DEF',\$arg3=\"GHI\",JKL\",\$arg4=array(1,'2)',\"3\"),\")\n"; 

preg_match_all($re, $str, $matches);

比赛信息:

MATCH 1
1.  [0-5]   `$arg1`
MATCH 2
1.  [6-21]  `$arg2='ABC,DEF'`
MATCH 3
1.  [22-39] `$arg3="GHI\",JKL"`
MATCH 4
1.  [40-67] `$arg4=array(1,'2)',"3\"),")`

【讨论】:

  • 与 Ulver 提供的解决方案一样,如果 ,$ 模式落入带引号的配对中,则会失败:$arg="...,$..."
【解决方案3】:

您可以在,$ 处拆分参数字符串,然后将$ 附加到数组值后面:

$args_array = explode(',$', $arg_str);
foreach($args_array as $key => $arg_raw) {
    $args_array[$key] = '$'.ltrim($arg_raw, '$');
}
print_r($args_array);

输出:

(
    [0] => $arg1
    [1] => $arg2='ABC,DEF'
    [2] => $arg3="GHI\",JKL"
    [3] => $arg4=array(1,'2)',"3\"),")
)

【讨论】:

  • 不幸的是,如果分隔符在引号内,这将失败: $arg="...,$..." 会产生 [0] => $arg="... [1] => $...”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-15
  • 1970-01-01
  • 2012-10-10
  • 2020-08-30
  • 2022-01-13
  • 1970-01-01
相关资源
最近更新 更多