簡體   English   中英

使用 fputcsv() / fgetcsv() 寫入 csv 時數據會出現亂碼

[英]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

2020 年 1 月更新

自 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()嗎? -> 是的。

更改分隔符有幫助嗎?

可以證明,這可以通過定界符、包圍和轉義字符的任意組合重現。

https://3v4l.org/a29kR

$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',
)

可以修復 fputcsv() 嗎?

為此,讓我們回到更常見和可讀的分隔符、外殼和轉義字符。

$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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM