PHP - 如何使用逗号分割字符串,但排除逗号在引号内的情况?

3

我有以下文本:

$string='
            blah<br>
            @include (\'file_to_load\')
            <br>
            @include (\'file_to_load\',\'param1\',\'param2\',\'param3\')
    ';

我希望能够捕获所有带参数的"@include"出现,并使用preg_replace_callback函数进行替换 (例如:@include ('file_to_load','param1','param2','param3'))。

因此,我这样做:

$string='
 blah<br>
 @include (\'file_to_load\')
 <br>
 @include (\'file_to_load\',\'param1\',\'param2\')
';
$params=[];
$result = preg_replace_callback(
    '~@include \((,?.*?)\)~',//I catch @include, parenthesis and all between them
    function ($matches) {
        echo '---iteration---';
        $params=explode(',',$matches[1]);//exploding by a comma
        echo '<pre>';
        var_dump($params);
        echo '</pre>';
        return $matches[1];
    },
    $string
);

一切都很好,直到逗号出现在参数内部,就像这样:

$string='
    blah<br>
    @include (\'file_to_load\')
    <br>
    @include (\'file_to_load\',\'param1,something\',[\'elem\'=>\'also, a comma\']])
';

这里我们有一个逗号在“param1”参数内部,现在,在使用explode()函数分割后,它显然不能按照我想要的方式工作。

有没有一种方法可以通过正则表达式将字符串按逗号分割,但不在单引号内时进行分割?


使用所示示例,str_getcsv 比重新发明它要简单。 - mario
3个回答

2
请使用以下方法进行分割:
,(?=([^']*'[^']*')*[^']*$)

使用preg_split,因为explode不支持正则表达式:

代码:

$params = preg_split(',(?=([^']*'[^']*')*[^']*$)',$matches[1]);

1
使用 preg_split 的另一个想法是跳过引用的内容 '[^']*'(*SKIP)(*F)|, regex101 - Jonny 5

2
你需要的是“分词”。不要尝试在逗号上进行拆分。相反,要识别表达式的每个构建块。所以你需要“匹配”,而不是“拆分”。
例如,这个简单的正则表达式:
'[^']+'

将匹配这些元素:

@include ('file_to_load','param1,something',['elem'=>'also, a comma'])
          \____________/ \________________/  \____/  \_____________/

但这可能对您的情况不够充分,因为您在其中有一个数组,并且我假设您也必须解析它。

因此,单独识别每个参数:

'[^']+'|\[.+?\]

@include ('file_to_load','param1,something',['elem'=>'also, a comma'])
          \____________/ \________________/ \_______________________/

这种方法的问题在于它无法匹配嵌套数组。如果需要解析嵌套数组,则模式会更加复杂:

(?(DEFINE)
  (?<string>'[^']+')
  (?<array> \[ (?: (?&arrayitem) (?> , \s* (?&arrayitem) )* )? \] )
  (?<arrayitem> \s* (?&string) \s* => \s* (?&value) \s* )
  (?<value> (?&string) | (?&array) )
)
(?&value)

是的,这是一个递归正则表达式,但它实际上可以识别参数:

@include ('file_to_load','param1,something',['elem'=>'also, a comma','other'=>['nested' => 'array']])
          \___________/  \________________/ \______________________________________________________/

演示

由于我不知道您后续要对参数做什么操作,因此您可能需要编写解析器而不是使用正则表达式,但这取决于您在分割参数后要尝试做什么。

附注:如果您想在字符串中转义引号,则可能需要将'[^']+'字符串模式替换为更复杂的内容。

有两种被广泛接受的方法来实现这一点:

  • Use a backslash: 'abc\'def'

    '(?:[^\\']++|\\.)*'
    
  • Double the quote: 'abc''def'

    '(?:[^']++|'')*'
    

这是否涵盖了像 @include ('file_to_load','param1,something',['elem'=>'a, comma and a] brace and an escaped \' single quote']) 这样的实际情况?@Luc - mickmackusa
@mick 这本来应该的,但是答案是通用的,因此我没有在主要示例中包含转义。请参见侧注以了解所需更改。 - Lucas Trzesniewski

0

尝试使用这个:

"\@include[\s]*\([^\)]*\)"

这将匹配

@include (\'file_to_load\')

并且

@include (\'file_to_load\',\'param1,something\',[\'elem\'=>\'also, a comma\']])

希望这能有所帮助。


请勿发布“试试这个”(仅代码答案)。[\s]可以更简单地表示为\s。在此模式中括号不需要在字符类内外进行转义。@也不需要转义。 - mickmackusa
@mickmackusa 注意到了。谢谢你。不过这是一个旧的答案。 - Christian Ezeani
老的答案会被新的研究人员复制粘贴。这就是为什么我们需要确保我们始终发布代表我们最佳知识水平的建议的最佳版本。否则,不知情的人将学习次优的编程习惯。 - mickmackusa
@mickmackusa 好的。我会进行必要的更改。 - Christian Ezeani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接