PHP导出带BOM的UTF-8格式CSV文件无法正常工作

6

我已经卡了几天,尝试用PHP导出UTF-8 CSV文件,并包含中文字符,在Windows Excel上显示的是乱码。我已经添加了BOM字节标记并尝试编码,但结果都不理想。

这些CSV文件在Notepad++、Google Spreadsheet和Mac Numbers上打开没有问题,但客户要求在Excel上打开。使用Notepad++打开时,编码显示为UTF-8。如果我手动更改为UTF-8并保存,文件就可以在Excel上正常打开。

看起来BOM字节标记没有被保存到输出中,因为Notepad++总是检测到UTF-8而没有BOM。

此外,CSV文件没有保存在服务器上。数据从数据库中检索,然后直接导出。

以下是我的代码:

// Setup headers
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Description: File Transfer');
header("Content-type: text/csv");
header("Content-disposition: filename=".$filename.".csv");
header("Pragma: no-cache");

// First Method
$fp = fopen('php://output', 'w');
// Add BOM to fix UTF-8 in Excel, but doesn't work
fputs($fp, chr(0xEF) . chr(0xBB) . chr(0xBF) );

if ($fp) {

    fputcsv($fp, array("Header"), ",");
    fputcsv($fp, array($string_with_chinese_chars), ",");
}

fclose($fp);
exit();

// Second Method
$csv = "";
$sep = ",";
$newline = "\n"; // Also tried with PHP_EOL

$csv .= "Header";
$csv .= $newline;
$csv .= $string_with_chinese_chars;
$csv .= $newline;

// Tried all the below ways but doesn't work.
// Method 2.1
print chr(255) . chr(254) . mb_convert_encoding($csv, 'UTF-16LE', 'UTF-8');

// Method 2.2
print chr(239) . chr(187) . chr(191) . $csv;

// Method 2.3
print chr(0xEF).chr(0xBB).chr(0xBF);
print $newline;
print $csv;

你能在将文件保存在Notepad++之前和之后使用十六进制编辑器打开文件,看看有什么区别吗?如果文件太短的话,甚至可以将文件的十六进制转储添加到你的问题中吗? - Ilmari Karonen
好的,我会尝试并再次更新。 - darnpunk
1
更新:下载的文件HEX以0A EF BB BF开头。而在使用Notepad++保存后,文件以EF BB BF 0A EF BB BF开头。0A看起来是一个新行。不知何故,即使没有任何代码部分执行该操作,似乎也会在文件开头添加它。这是一个共享托管服务器,我无法访问php.ini。 - darnpunk
4个回答

6
希望能对某些人有所帮助。 对我起作用的方法是需要同时添加以下内容:
...
echo chr(0xEF) . chr(0xBB) . chr(0xBF);
$file = fopen('php://output', 'w');
fputs($file, chr(0xEF) . chr(0xBB) . chr(0xBF));
...

我不是 PHP 的专家,所以无法解释为什么这个代码可以工作,但我希望这能帮助到其他人,因为我自己也曾经在解决这个问题时遇到了困难。


1
不知道为什么这个有效,但它确实有效。请注意,似乎只需要两次bom,它可以在同一行上重复。 - Scott
我在这上面花了一天时间,谢谢兄弟!这让我想到可能是与写入 php 输出有关而不是直接写入文件,因为在那里它可以避免重复。如果有人能解释一下就好了。 - zeykzso
它可以工作。在 Slim 应用程序中生成了文件。在响应正文和文件内部都添加了 bom。 - Victor Yan

4
以下代码适用于我。在csv内容之前输出utf-8-bom字符:
  echo "\xEF\xBB\xBF"; // utf-8 bom 
  echo $csv;

谢谢!我之前在输出时没有使用BOM,导致欧洲字符无法正常显示,这个解决方案很好用。现在在Excel中,像Reneé这样的名字可以正确地显示了。 - ManuelJE

3
根据您上面的评论,看起来您的脚本在 UTF-8 BOM 之前意外打印出一个换行符(十六进制 0A),导致 Excel 无法将输出识别为 UTF-8。
既然您正在使用 PHP,请确保在脚本中的 <?php 标记之前没有空行,或者在任何其他包含它的 PHP 文件中也是如此。此外,请确保您包含的任何文件都没有在闭合 ?> 标记后有任何空格。
实际上,这可能很难做到,因为许多文本编辑器坚持始终将换行符附加到最后一行的末尾。因此,最安全、最简单的解决方案是从您的 PHP 文件中省略 ?> 标记,除非您想要打印出其后面的任何内容。PHP 不需要存在 ?>,在不打算混合 PHP 和字面模板 HTML(或其他文本)的文件中使用它只会引发这样的错误。

我可能忘记了添加一个重要的点,那就是这些代码是我为WordPress开发的插件的一部分。我非常确定我的插件在<?php标记之前没有空格或空行。我还尝试过使用ob_start()打印出我上面的所有内容并使用ob_flush()。但0A字符仍然存在。这是否意味着我需要调查WordPress中的每个文件? - darnpunk
听起来需要做很多工作。我会尝试一下。这也可能是php.ini中的配置引起的问题吗?我可能需要编写一个独立的PHP脚本,看看是否会发生同样的事情。 - darnpunk
如果您已经安装了perl,perl -0777 -nE 'say $ARGV if /^\s+<\?/ or /\?>\s+$/' *.php 可以列出当前目录中任何具有空格(且仅有空格)在第一个 <? 之前或最后一个 ?> 之后的 .php 文件。要搜索子目录,请尝试例如 find . -name '*.php' -exec perl -0777 -nE 'say $ARGV if /^\s+<\?/ or /\?>\s+$/' '{}' ';'。(这应该适用于bash、perl和GNU find,在Linux上默认安装,但也可在Windows上使用,例如通过Git for Windows。) - Ilmari Karonen
我将尝试运行Perl脚本。同时,我已经使用了ob_start()函数,但似乎字符仍然存在。我该如何使用ob_start()函数清除缓冲区中的任何内容并重新开始? - darnpunk
哦我的天啊,我花了很多时间来玩编码,问题出在脚本开头的换行符。 - user3514052
显示剩余2条评论

3

我通常是这样做的:

header('Content-Type: application/csv');
header('Content-Disposition: attachment; filename="filename.csv"');
header('Cache-Control: max-age=0');

// BOM header UTF-8
echo "\xEF\xBB\xBF";

$fh = @fopen('php://output', 'w');

...

我使用分号;作为分隔符,因为Excel很可能不会自动格式化逗号,


我也尝试过那个方法,但它不起作用。另外,补充一点,如果我将文件保存到服务器并直接从服务器下载,它就可以工作。 - darnpunk

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