【问题标题】:Splitting by a semicolon not surrounded by quote signs用不被引号包围的分号分割
【发布时间】:2017-09-11 15:03:25
【问题描述】:

好吧,你好社区。我正在用 PHP 编写一个 CSV 解码器(是的,我知道已经有一个,但对我来说是一个挑战,因为我在空闲时间学习它)。现在的问题是:好吧,行被PHP_EOL 分开。

在这一行:

foreach(explode($sep, $str) as $line) {

其中 sep 是拆分行的变量,而 str 是我要解码的字符串。

但如果我想用分号分隔列,可能会出现分号是一列内容的情况。正如我研究的那样,这个问题是通过用这样的引号包围整个列来解决的:

输入:

"0;0";1;2;3;4

预期输出:

0;0 | 1 | 2 | 3 | 4

我已经想到了前瞻/后瞻。但由于我过去没有使用它,也许这可能是一个很好的做法,我不知道如何将它包含在正则表达式中。我的解码函数返回一个二维数组(就像一个表......),我想像这样向数组添加行(是的,正则表达式被搞砸了......):

$res[] = preg_split("/(?<!\")". preg_quote($delim). "(?!\")/", $line);

最后是我的完整代码:

function csv_decode($str, $delim = ";", $sep = PHP_EOL) {
    if($delim == "\"") $delim = ";";
    $res = [];

    foreach(explode($sep, $str) as $line) {
        $res[] = preg_split("/(?<!\")". preg_quote($delim). "(?!\")/", $line);
    }

    return $res;
}

提前致谢!

【问题讨论】:

    标签: php regex csv pcre decoder


    【解决方案1】:

    这有点违反直觉,但通过正则表达式拆分字符串的最简单方法通常是使用preg_match_all 代替preg_split

    preg_match_all('~("[^"]*"|[^;"]*)(?:;|$)~A', $line, $m);
    $res[] = $m[1];
    

    A 修饰符确保从字符串开头连续匹配的连续性。

    如果您不希望结果中包含引号,可以使用branch reset feature (?|..(..)..|..(..)..)

    preg_match_all('~(?|"([^"]*)"|([^;"]*))(?:;|$)~A', $line, $m);
    

    其他解决方法,但这次针对preg_split:在分隔符之前包含您想要避免的部分,并使用\K 功能将其从整个匹配中丢弃:

    $res[] = preg_split('~(?:"[^"]*")?\K;~', $line);
    

    【讨论】:

    • 这对我有点帮助。也许对于(让我说)高级初学者(因为我不是基于 0 技能,但我显然只知道捕获组、字母类等基础知识,所以高级初学者和没有中级)我希望得到更多解释,因为其中很多功能对我来说是全新的。
    • 让我试试看:第一个正则表达式:第一个捕获组是一个引号,后跟许多非引号字符或许多非分号字符和非引号字符。然后是包含分号或美元符号的非捕获(这意味着字符串的结尾?我很困惑......)?
    • 第二个:提到的分支重置组,idk...捕获组1包括许多非引号字符,该组被两个引号包围。第 1 组也可以包含许多非分号字符或非引号符号字符。未捕获的组包括分号或美元符号。
    • 毕竟没有我想象的那么难,我不得不说正则表达式的最后让我感到困惑。
    • @iBinary:是的,不难,“美元符号”是一个特殊字符:$ 是字符串结尾的零宽度断言。
    【解决方案2】:

    您可以使用此函数str_getcsv,在此您也可以指定自定义分隔符(;)。

    Try this code snippet

    <?php
    
    $string='"0;0";1;2;3;4';
    print_r(str_getcsv($string,";"));
    

    输出:

    Array
    (
        [0] => 0;0
        [1] => 1
        [2] => 2
        [3] => 3
        [4] => 4
    )
    

    【讨论】:

    • 正在考虑使用相同的方法来避免正则表达式,数组遍历也很好,可以解析 0;0 而不是将其作为字符串而是作为数组。
    • @Mr.Phoenix 我没用过正则表达式,这里我用的是PHP的内置函数。
    • 它适用于 $string。但是当引号从双引号变为单引号时: $string="'0;0';1;2;3;4";它失败了。
    • @iBinary 这是“这样做的方法”。 str_getcsv() 专门为此任务而构建,特别方便。
    • @honglinzhang 您的要求与发布的问题略有不同。 See the documentation 默认的“附件”字符是"。如果您需要将其更改为单引号,请将 ' 添加到调用的第三个参数,如下所示:str_getcsv($string, ";", "'")
    【解决方案3】:

    对于 csv 类型的行,拆分不是一个好的选择。
    您可以使用经过验证的旧 \G 锚点和全局查找类型函数。

    Practical

    正则表达式:'~\G(?:(?:^|;)\s*)(?|"([^"]*)"|([^;]*?))(?:\s*(?:(?=;)|$))~'

    信息:

     \G                            # G anchor, start where last match left off
     (?:                           # leading BOL or ;
          (?: ^ | ; )
          \s*                           # optional whitespaces
     )
     (?|                           # branch reset
          " 
          ( [^"]* )                     # (1), double quoted string data
          "
       |                              # or
          ( [^;]*? )                    # (1), non-quoted field
     )
     (?:                           # trailing optional whitespaces
          \s* 
          (?:
               (?= ; )                       # lookahead for ;
            |  $                             # or EOL
          )
     )
    

    【讨论】:

      猜你喜欢
      • 2011-03-26
      • 2010-09-26
      • 1970-01-01
      • 2022-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多