我正在构建一个 CSV 导出工具。几个月来它一直运行良好,但我们最近遇到了几个由单元格“爆炸”引起的额外线条的情况。
我已将问题缩小到 PHP(7.2.21 和其他版本)中的一个错误。我需要解决这个问题。下面是重现问题的 PHP 脚本。它在每个单元格中包含逗号和换行符:
<?php
$data = array(
array( 'ID', 'Name', 'Content', 'Date' ),
array( 34, 'Radley', 'This is <strong>bold text</strong>, and' . "\r\n" . 'a second line, the first time', '2019-08-23' ),
array( 47, 'John', 'This a <a href=\"http://example.org/\">link</a>' . "\r\n" . 'a second line, again', '2019-08-24' ),
array( 65, 'Bob', 'This plain text, with no html, and lots of commas'. "\r\n" . 'and a third extra row', '2019-08-25' ),
);
$fh = fopen('php://output', 'w');
foreach( $data as $row ) fputcsv( $fh, $row );
echo stream_get_contents( $fh );
fclose( $fh );
?>
Run Code Online (Sandbox Code Playgroud)
如果我们print_r的$data,我们可以看到这是只有四个值:
[2] => Array (
(
[0] => 47
[1] => John
[2] => This a <a href=\"http://example.org/\">link</a>
a second line, again
[3] => 2019-08-24
)
)
Run Code Online (Sandbox Code Playgroud)
所以我希望这段代码生成四行,每行四列,但“约翰”的第三项只有 3 列,并且还添加了一个三列的额外行。
问题不仅仅是\"已经逃脱了。将它放在字符串中会禁用 fputcsv 转义其他所有内容。
这种格式错误的数据可以在 Windows 和 Mac 的 Excel 中看到,也可以在 Windows Chrome 中的 Google 表格中看到,可能还有其他所有内容。但是如果你将它重新加载到 PHP 中,fgetcsv它可以工作,那么 PHP 必须故意这样做吗?
我需要更正这个错误,以便正确\"转义为\"",因此所有其他逗号和双引号也被转义。我正在构建的插件导出由其他插件生成的内容,那些可能已经转义的数据需要通过自己的机制保持转义。
有没有办法确保每个单元格都被转义,即使单元格有反斜杠和引号?
PHP手册指出
如果一个字段中包含一个封闭字符,它将通过加倍来转义,除非它前面紧跟着一个escape_char。
这就是你的情况!你使用fputcsv它的默认参数,"作为外壳和\作为escape_char,所以函数离开了\",没有把它变成\""
如果您使用的是 PHP >= 7.4.0,那么只需禁用专有转义机制。通过在 escape_char 参数中提供一个空字符串
fputcsv( $fh, $row, ',', '"' , '');
Run Code Online (Sandbox Code Playgroud)
如果您在 7.4.0 之前,那么搜索已向我展示了此解决方法:"\0"作为 escape_char 参数传递。(我测试了它,它适用于你的例子)
fputcsv( $fh, $row, ',', '"' , "\0");
Run Code Online (Sandbox Code Playgroud)
根据我的搜索,这是针对此问题的广泛使用的hack,但是 Christoph M. Becker 在他的提案“杀死 CSV 转义”中表示 (看起来您不是唯一一个对fputcsvCSV 转义感到愤怒的人:D)
虽然在许多情况下将“\0”作为 $escape 参数传递将产生所需的结果,但如果有人正在写入/读取二进制 CSV 文件,这将不起作用,可能会遇到一些非 ASCII 兼容编码的问题,并且通常被视为作为黑客。
关于 CSV 文件的RFC 4180已经声明
如果使用双引号将字段括起来,则出现在字段内的双引号必须通过在其前面加上另一个双引号来转义
在标准 CSV 中没有什么叫做转义的!!只是一个双引号作为外壳,如果它出现在带有另一个双引号的字符串中,则将其转义。可能是 PHP 想要支持非标准的 CSV 文件,我不知道!
经过一些搜索和测试,事实证明这是 CSV 文件 PHP 函数fgetcsv和fputcsv. 以下是我在搜索过程中发现的一些其他链接,您可能会觉得它很有趣:
fgetcsv/fputcsv $escape 参数从根本上被破坏
使用 fputcsv-fgetcsv 写入 csv 时数据会出现乱码