【问题标题】:How to produce iso-8859-1 output on some requests in PHP?如何在 PHP 中的某些请求上生成 iso-8859-1 输出?
【发布时间】:2022-01-11 15:37:24
【问题描述】:

我有一个 PHP 应用程序,它使用 league/csv 生成一个简单的 CSV 文件。某些列包含可能具有非 ANSI 值的名称/地址。我的客户要求将输出 CSV 文件编码为iso-8859-1,而不是当前的utf-8

我相信我的问题可以归结为以下问题(response 来自 laravel):


        $headers = [
            'Content-type' => "text/csv; charset=iso-8859-1",
            'Content-Disposition' => 'attachment; filename="CLI.csv"'
        ];
        return response()->stream(function() {

            $fh = fopen('php://output', 'wb');
            fwrite($fh, "Vià Cittè\n");
            fwrite($fh, mb_convert_encoding("Vià Cittè\n", 'iso-8859-1'));
            fwrite($fh, mb_convert_encoding("Vià Cittè\n", 'iso-8859-1', 'utf-8'));
            fwrite($fh, iconv('utf-8', 'iso-8859-1', "Vià Cittè\n"));
            fwrite($fh, utf8_decode("Vià Cittè\n"));
            fwrite($fh, utf8_encode("Vià Cittè\n"));
            fclose($fh);

        }, 200, $headers);

我希望至少 一些 行是Vià Cittè\n 在 iso-8859-1 中编码的,但它们最终都错了。这是我使用 iso-8859-1 作为编码打开输出文件时看到的:

由于某种原因,输出似乎被重新编码为 utf-8。

谁能告诉我如何避免出现这个重新编码问题?


在我的真实代码中,我不是直接使用fopen 编写的,而是使用league/csv 及其WriterCharsetConverter。我做了各种尝试,但结果和上面描述的一样。

注意:我目前在 linux 上使用 PHP 7.3。 php 服务器位于 nginx 代理后面的 docker 容器中(位于不同的 docker 容器中)。

【问题讨论】:

  • 当(有意)混合编码时,您不能信任编辑器的渲染。我建议您只使用几个字符和一个hexadecimal editor 进行测试。另外,我建议阅读手册以了解这些功能的作用,因为某些名称和签名非常具有误导性。
  • 2022:阅读并关注UTF-8 EverywhereUTF-8 all the way through
  • @JosefZ 不幸的是,问题是使用我们导出的文件的工具无法处理 utf-8,所以这不是一个选项。

标签: php csv utf-8 character-encoding thephpleague


【解决方案1】:

您有几个有效的转换,以及明显的随机尝试。只需进行一些适当的测试即可。

Raw Unicode UTF-8 ISO-8859-1
à U+00E0 LATIN SMALL LETTER A WITH GRAVE C3 A0 E0
è U+00E8 LATIN SMALL LETTER E WITH GRAVE C3 A8 E8
$utf8 = "\u{00E0}\u{00E8}";
var_dump($utf8, bin2hex($utf8));

$latin1 = [
    utf8_decode($utf8),
    iconv('UTF-8', 'ISO-8859-1', $utf8),
    mb_convert_encoding($utf8, 'ISO-8859-1', 'UTF-8'),
];
var_dump(array_map('bin2hex', $latin1));

假设一切都配置为使用 UTF-8(我们不是生活在 1995 年的穴居人),您会看到:

string(4) "àè"
string(8) "c3a0c3a8"
array(3) {
  [0]=>
  string(4) "e0e8"
  [1]=>
  string(4) "e0e8"
  [2]=>
  string(4) "e0e8"
}

我会跳过utf8_decode(),因为它的名字非常令人困惑(没有人查看手册来了解它的实际作用)。其他的主要在处理缺失字符的方式上有所不同:

$utf8 = "€";
var_dump($utf8);

$latin1 = [
    iconv('UTF-8', 'ISO-8859-1', $utf8), # Notice: iconv(): Detected an illegal character in input string
    iconv('UTF-8', 'ISO-8859-1//IGNORE', $utf8),
    iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $utf8),
    mb_convert_encoding($utf8, 'ISO-8859-1', 'UTF-8'),
];
var_dump(array_map('bin2hex', $latin1));
string(3) "€"
array(4) {
  [0]=>
  string(0) ""
  [1]=>
  string(0) ""
  [2]=>
  string(6) "455552" ------> EUR
  [3]=>
  string(2) "3f" ----------> ?
}

【讨论】:

  • 根本不起作用。 $utf8 = "Vià Cittè"; fwrite($fh, utf8_decode($utf8)) 仍然返回 utf-8 编码的文本,同样使用 iconvmb_convert_encoding 描述。使用 python 打开下载的文件显示它是 utf-8 而不是 latin1: Vi\xc3\xa0 Citt\xc3\xa8\n 。我希望生成的文件是Vi\xe0 Citt\xe8\n
  • 使用bin2hex 记录此问题,您提出的3 个解决方案仅utf8_decode 一个会产生正确的字节序列:5669e02043697474e80a 但这并不重要,因为下载时编码更改为utf-8 .这就是我要解决的问题。我的 nginx 没有为服务器定义任何 charset 选项,因此它不应该触及响应的编码,标头说 Content-Type: text/csv; charset=iso-8859-1 并且在其他标头中没有指定其他字符编码
  • 让我们一次解决一件事。 1) $utf8 = "Vià Cittè" 需要将您的 IDE 配置为保存为 UTF-8,检查十六进制转储以防万一或使用 \u{00E0} 表示法。 2) 你能在独立的 sn-p 中重现 utf8_decode() / mb_convert_encoding() / iconv() 不匹配,并验证输入和十六进制输出吗?
猜你喜欢
  • 2012-09-01
  • 2010-09-09
  • 1970-01-01
  • 2015-10-11
  • 1970-01-01
  • 2012-05-13
  • 2014-04-26
  • 2011-06-10
  • 2016-05-06
相关资源
最近更新 更多