首页 > 解决方案 > 字符集转换:PHP 的 iconv 不起作用

问题描述

我们有一些带有 PHP 后端的内部仪表板,用于上传 CSV 文件。最近我们发现一些 CSV 无法解析:fgetcsv函数返回false,这是非常讨厌的,因为我们无法确定 CSV 中的实际问题(例如,在哪一行不是经验问题,哪些字符无法消化等。 )


我们将问题缩小到字符集编码:从 Windows 机器生成的 CSV 失败。Linux 的iconv命令能够为我们修复 CSV

iconv -c --from-code=UTF-8 --to-code=ASCII path/to/uncleaned.csv > path/to/cleaned.csv

虽然它的PHP 等效项不起作用(尝试使用这两个//IGNORE//TRANSLIT选项)。

$uncleaned_csv_text = file_get_contents($source_data_csv_filename);
$cleaned_csv_text = iconv('UTF-8', 'ASCII/IGNORE//TRANSLIT', $uncleaned_csv_text);
file_put_contents($source_data_csv_filename, $cleaned_csv_text);
..
$headers = fgetcsv($source_data_csv_filename)

虽然我们可以使用 PHP 的exec函数来运行 shell 命令

有没有其他方法可以实现这种 CSV“清理”?


更新-1

我们探索了其他几个选项,但没有一个对我们有用


更新-2

标签: phpcharacter-encodingiconv

解决方案


我一直在研究一个处理 csv 文件的插件(在 Wordpress 中)一段时间(5/5 星评级),我一直在使用mb_convert_encoding()没有问题。我知道我的用户同时使用 Windows 和 Linux。

基本上:(转为UTF-8,来自:Windows-1252)

$cleaned_csv_text = mb_convert_encoding($uncleaned_csv_text, 'UTF-8', 'Windows-1252');

如果您不知道原件的编码(在您的情况下可能更好):

UTF-8)

$cleaned_csv_text = mb_convert_encoding($uncleaned_csv_text, 'UTF-8');

更新: 这是一个更完整的答案,我希望你会觉得有用:我已经与etcfile()一起使用:str_getcsv()

<?php
$file = "csvfiles/pricelist.csv"; //This is Windows-1252 encoded

//Load a csv file into an array $content_arr
$content_arr = array_map(function($v) {
    $delimiter = ';';
    return str_getcsv($v, $delimiter);},
    file( $file )); 

//Do encoding row by row
//and include end of line based on the item in the array $content_arr
$csv = array_map(function($v) {
    return mb_convert_encoding($v[0], 'UTF8','Windows-1252') . detect_eol($v[0]);},
    $content_arr);

//Save modified file in UTF8
file_put_contents('csvfiles/pricelist_modified.csv', $csv);

//Detects the end-of-line character of a string.
//
//function from 
//https://stackoverflow.com/questions/11066857/detect-eol-type-using-php/11066858#11066858
function detect_eol( $str )
{
    static $eols = array(
        "\0x000D000A", // [UNICODE] CR+LF: CR (U+000D) followed by LF (U+000A)
        "\0x000A",     // [UNICODE] LF: Line Feed, U+000A
        "\0x000B",     // [UNICODE] VT: Vertical Tab, U+000B
        "\0x000C",     // [UNICODE] FF: Form Feed, U+000C
        "\0x000D",     // [UNICODE] CR: Carriage Return, U+000D
        "\0x0085",     // [UNICODE] NEL: Next Line, U+0085
        "\0x2028",     // [UNICODE] LS: Line Separator, U+2028
        "\0x2029",     // [UNICODE] PS: Paragraph Separator, U+2029
        "\0x0D0A",     // [ASCII] CR+LF: Windows, TOPS-10, RT-11, CP/M, MP/M, DOS, Atari TOS, OS/2, Symbian OS, Palm OS
        "\0x0A0D",     // [ASCII] LF+CR: BBC Acorn, RISC OS spooled text output.
        "\0x0A",       // [ASCII] LF: Multics, Unix, Unix-like, BeOS, Amiga, RISC OS
        "\0x0D",       // [ASCII] CR: Commodore 8-bit, BBC Acorn, TRS-80, Apple II, Mac OS <=v9, OS-9
        "\0x1E",       // [ASCII] RS: QNX (pre-POSIX)
        "\0x15",       // [EBCDEIC] NEL: OS/390, OS/400
        "\r\n",
        "\r",
        "\n"
    );
    $cur_cnt = 0;
    $cur_eol = "\r\n"; //default

    //Check if eols in array above exists in string
    foreach($eols as $eol){     
        $char_cnt = mb_substr_count($str, $eol);

        if($char_cnt > $cur_cnt)
        {
            $cur_cnt = $char_cnt;
            $cur_eol = $eol;
        }
    }
    return $cur_eol;
}

推荐阅读