使用fputcsv()和fgetcsv()在PHP中似乎存在编码问题或错误。
以下PHP代码:
$row_before = ['A', json_encode(['a', '\\', 'b']), 'B'];
print "\nBEFORE:\n";
var_export($row_before);
print "\n";
$fh = fopen($file = 'php://temp', 'rb+');
fputcsv($fh, $row_before);
rewind($fh);
$row_after = fgetcsv($fh);
print "\nAFTER:\n";
var_export($row_after);
print "\n\n";
fclose($fh);
给我这个输出:
BEFORE:
array (
0 => 'A',
1 => '["a","\\\\","b"]',
2 => 'B',
)
AFTER:
array (
0 => 'A',
1 => '["a","\\\\',
2 => 'b""]"',
3 => 'B',
)
很明显,数据在途中受损。最初行中只有3个单元格,之后行中有4个单元格。由于反斜杠也被用作转义字符,因此中间单元格被拆分。
另见 https://3v4l.org/nc1oE 或者在这里,使用明确的分隔符值,enclosure,escape_char:https://3v4l.org/Svt7m
在写入CSV之前,有什么方法可以清理/转义我的数据,以保证从文件中读取的数据完全相同?
CSV是完全可逆的格式吗?
编辑:目标是一种正确编写和读取任何数据为csv的机制,这样在一次往返之后数据仍然是相同的。
编辑:我意识到我并不真正理解$ escape_char参数。另请参阅fgetcsv/fputcsv $escape parameter fundamentally broken也许对此的回答也会使我们更接近解决方案。答案 0 :(得分:3)
罪魁祸首是fputcsv()使用转义字符,这是CSV的非标准扩展名。 (好吧,就RFC 7111而言,可以视为标准。)基本上,必须禁用此转义字符,但将空字符串作为$ escape传递给fputcsv()不起作用。通常,传递NUL字符应该会产生所需的结果,但请参阅https://3v4l.org/MlluN。
答案 1 :(得分:0)
将代码用于特定的分隔符,但更改以下行将有效...
$enclosure = "'";
我认为这可能与认为\正在逃避以下引用有关。
答案 2 :(得分:0)
就像在php中一样,\\
用来逃避反斜杠(link for PHP manual escape sequence),所以为了使它成为字符串你需要再使用一个单引号('')。
所以你的输入数组应该是......
$row_before = ['A', json_encode(['a', "'\\'", 'b']), 'B'];
答案 3 :(得分:0)
这不是PHP错误。似乎json_encode()
使用相同的分隔符(,),enclosure(“)和escape(\),它与fputcsv()
和fgetcsv()
的默认分隔符,enclosure和escape相同。您可以区分机箱或转义,并在必要时分隔。
正如已经回答的那样,在这种情况下,它可以通过使用(')而不是:
指定enclosure$row_before = ['A', json_encode(['a', '\\', 'b']), 'B'];
print "\nBEFORE:\n";
var_export($row_before);
print "\n";
$fh = fopen($file = 'php://temp', 'rb+');
fputcsv($fh, $row_before, ',', "'");
rewind($fh);
$row_after = fgetcsv($fh, 0, ',', "'");
print "\nAFTER:\n";
var_export($row_after);
print "\n\n";
fclose($fh);
答案 4 :(得分:0)
与其他人的说法相反,我声称这是一个PHP错误。我要报告,并更新这个答案。
编辑:现在报告https://bugs.php.net/bug.php?id=74713
在这个答案中讨论:
fputcsv()
吗? - >是。可以证明,这可以通过分隔符,封闭和转义字符的任意组合来重现。
$delimiter = 'X';
$enclosure = 'Y';
$escape_char = "Z";
$row_before = [
'A',
"[{$enclosure}a{$enclosure}{$delimiter}{$enclosure}{$escape_char}{$escape_char}{$enclosure}{$delimiter}{$enclosure}b{$enclosure}]",
'B',
];
print "\nBEFORE:\n";
var_export($row_before);
print "\n";
$fh = fopen($file = 'php://temp', 'rb+');
fputcsv($fh,$row_before,$delimiter,$enclosure, $escape_char);
rewind($fh);
$row_plain = fread($fh, 1000);
print "\nPLAIN:\n";
var_export($row_plain);
print "\n";
rewind($fh);
$row_after = fgetcsv($fh, 500,$delimiter,$enclosure, $escape_char);
print "\nAFTER:\n";
var_export($row_after);
print "\n\n";
fclose($fh);
输出:
BEFORE:
array (
0 => 'A',
1 => '[YaYXYZZYXYbY]',
2 => 'B',
)
PLAIN:
'AXY[YYaYYXYYZZYXYYbYY]YXB
'
AFTER:
array (
0 => 'A',
1 => '[YaYXYZZ',
2 => 'bYY]Y',
3 => 'B',
)
为此,让我们回到更常见和可读的分隔符,封闭和转义字符。
$delimiter = ',';
$enclosure = '"';
$escape_char = "@";
结果如下:
BEFORE:
array (
0 => 'A',
1 => '["a","@@","b"]',
2 => 'B',
)
PLAIN:
'A,"[""a"",""@@",""b""]",B
'
AFTER:
array (
0 => 'A',
1 => '["a","@@',
2 => 'b""]"',
3 => 'B',
)
我们发现'"@@"'
部分导出为'""@@"'
,而它应该已导出为'""@@""'
。
事实上,使用fwrite()
代替fputcsv()
手动执行此操作可以解决问题:https://3v4l.org/4U1CQ