[英]Data gets garbled when writing to csv with fputcsv() / fgetcsv()
PHP 中的 fputcsv() 和 fgetcsv() 似乎存在編碼問題或錯誤。
以下 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或在這里,使用分隔符、外殼、escape_char 的顯式值: https ://3v4l.org/Svt7m
有什么方法可以在寫入 CSV 之前清理/轉義我的數據,以保證從文件中讀取的數據完全相同?
CSV 是完全可逆的格式嗎?
編輯:目標是一種將任何數據正確寫入和讀取為 csv 的機制,以便在一次往返后數據仍然相同。
編輯:我意識到我並不真正理解 $escape_char 參數。 另請參閱fgetcsv/fputcsv $escape 參數從根本上被破壞也許對此的答案也將使我們更接近解決方案。
罪魁禍首是 fputcsv() 使用轉義字符,這是 CSV 的非標准擴展。 (好吧,就 RFC 7111 而言可以視為標准。)基本上,必須禁用此轉義字符,但將空字符串作為 $escape 傳遞給 fputcsv() 不起作用。 通常,傳遞 NUL 字符應該會得到所需的結果,但是,請參閱https://3v4l.org/MlluN 。
自 PHP 7.4 起,將空字符串作為轉義字符傳遞可解決此問題! https://www.php.net/manual/en/function.fgetcsv.php
演示https://3v4l.org/33Wja - 查看 PHP 7.4 與舊版本的差異。 (這是與下面相同的代碼段,只是用空字符串作為轉義字符)
與其他人所說的相反,我聲稱這是一個 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 : fputcsv()
使用帶有特定分隔符的代碼但更改以下行將起作用...
$enclosure = "'";
我認為這可能與認為 \\ 正在逃避以下引用有關。
與在 php 中一樣, \\\\
用於轉義反斜杠( PHP 手動轉義序列的鏈接),因此要將其設為字符串,您需要再使用一個單引號(' ')。
所以你的輸入數組應該是......
$row_before = ['A', json_encode(['a', "'\\'", 'b']), 'B'];
這不是 PHP 錯誤。 似乎json_encode()
使用相同的分隔符 (,)、外殼 (") 和轉義符 (\\),這與fputcsv()
和fgetcsv()
默認分隔符、外殼和轉義符相同。您可以區分外殼或轉義, 並在必要時分隔符。
正如已經回答的那樣,在這種情況下,它將通過使用 (') 指定附件來工作:
$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);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.