用于在PHP中解析CSV的正则表达式

7

我已经使用以下正则表达式成功地拆分了CSV文件:

"/,(?=(?:[^\"]\"[^\"]\")(?![^\"]\"))/"

但是,我得到的是一个包含开头和结尾双引号的字符串数组。现在我需要一个可以去掉这些字符串中定界符双引号的正则表达式。

据我所知,CSV格式可以用双引号来封装字符串,并且所有作为字符串一部分的双引号都会被加倍。例如:

My "other" cat

变成

"My ""other"" cat"

我需要的基本上是一个正则表达式,将所有N个连续的双引号替换为(N/2-向下取整)个双引号序列。

还有更好的方法吗?

提前感谢您的帮助。

6个回答

20

有一种读取csv文件的函数:fgetcsv


10
如果有一个内置函数可以完全满足你的需求,那么在 PHP 中使用正则表达式处理 CSV 文件是不明智的。+1 - cletus
1
是的。当有已经经过充分测试且运作良好的解决方案时,为什么要重新发明轮子呢? - Rachel
2
因为你可能会从第三方获取CSV导出文件,该文件未正确引用文本字段,而fgetcsv错误地将字符串1.15解释为浮点数,其值为1.1499999999。然而,最终编写一个快速脚本来修复CSV文件,然后使用fgetcsv更容易 :o) - frak
fgetcsv在处理DBCS字符(如中文)时表现不佳,它会将前缀SBCS字符从DBCS字符中吞噬掉。因此,必须首先正确声明setlocale。因此,我更喜欢使用正则表达式解决方案。 - Scott Chu

4

当有fgetcsv函数可以为你完成所有繁重的工作时,为什么还要用正则表达式来拆分文件呢?

你只需要传入分隔符和定界符,它就会自动检测并处理。


是的,尽管CSV格式很简单,但使用正则表达式处理它仍然非常麻烦。如果您有一个专门的解析器可用,请务必使用它。 - Alan Moore

2
preg_split('/,(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/', $line,-1,PREG_SPLIT_DELIM_CAPTURE);

有关“Toys"R"Us”这样字符串内部的问题

所以您应该使用以下内容:

preg_split('/'.$seperator.'(?=(?:[^\"])*(?![^\"]))/', $line,-1, PREG_SPLIT_DELIM_CAPTURE);

这不会移除字符串周围的双引号或转换字符串内部的双引号(表示为 "" 或 ")。因此,我加入了这段代码: array_walk($m, create_function('&$item,$key','$item = str_replace(array(\'""\',\'\\"\'),\'"\',trim($item, \'"\'));'));,其中$m是 preg_split 语句的结果数组(注意:由于 PHP 版本可能 < 5.3,我使用了 create_function 函数)。 - Scott Chu
这对于包含逗号的csv行中的字符串无效。 - Scott Chu

2

对于那些想使用正则表达式而不是fgetcsv的人来说,这里有一个完整的示例,展示如何使用正则表达式从CSV创建HTML表格。

    $data = file_get_contents('test.csv');
    $pieces = explode("\n", $data);

    $html .= "<table border='1'>\n";
    foreach (array_filter($pieces) as $line) {

            $html .= "<tr>\n";
            $keywords = preg_split('/,(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/', $line,-1,PREG_SPLIT_DELIM_CAPTURE);

            foreach ($keywords as $col) {
                    $html .= "<td>".trim($col, '"')."</td>\n";
            }
            $html .= "</tr>\n";
    }
    $html .= "</table>\n";

2

我同意其他人的观点,建议您使用fgetcsv函数而不是正则表达式。正则表达式可能适用于格式良好的CSV数据,但如果CSV格式不正确或损坏,正则表达式将会默默失败,在此过程中可能返回虚假结果。

然而,问题具体是关于在初始分割后去除不需要的引号。目前提出的一个解决方案太过简单,只处理了字段内的转义引号,而没有处理实际的分隔符。(我知道OP没有问这些,但它们确实需要被删除,所以为什么不与其他内容一起处理呢?)以下是我的解决方案:

$csv_field = preg_replace('/"(.|$)/', '\1', $csv_field);

这个正则表达式匹配一个引号后面跟着任何字符或字符串的结尾,并用第二个字符替换匹配到的字符,如果匹配到的是$,则用空字符串替换。根据规范,CSV字段可以包含行分隔符;虽然这种情况不太常见,但如果需要,您可以在正则表达式中添加's'修饰符。


0

这是我快速尝试的代码,虽然它只能在词边界上工作。

preg_replace('/([\W]){2}\b/', '\1', $csv)

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