PHP字符编码地狱:使用fgets读取CSV文件

4

我有一个网站,每个月通过FTP收到一个CSV文件。多年来,这是一个ASCII文件。现在我接收UTF-8一个月,然后是UTF-16BE,下个月是UTF-16LE。也许下个月我会得到UTF-32。fgets返回UTF文件开头的字节顺序标记。如何让PHP自动识别字符编码?我尝试了mb_detect_encoding方法,但无论文件类型都返回ASCII。我改变了我的代码以读取BOM并将字符编码明确地放入mb_convert_encoding中。这起作用,直到最新的文件,它是UTF-16LE。在这个文件中,它可以正确读取第一行,但所有后续行都显示为问号(“?”)。我做错了什么?

$fhandle = fopen( $file_in, "r" );
if ( fhandle === false )
    {
    echo "<p class=redbold>Error opening file $file_in.</p>";
    die();
    }

$i = 0;
while( ( $line = fgets( $fhandle ) ) !== false )
{
$i++;

// Detect encoding on first line. Actual text always begins with string "Document"
if ( $i == 1 )
    {
    $line_start = substr( $line, 0, 4 );
    $line_start_hex = bin2hex( $line_start );
    $utf16_start = 'fffe4400';
    $utf8_start = 'efbbbf44';
    if ( strcmp( $line_start, 'Docu' ) == 0 )
        { $char_encoding = 'ASCII'; }
    elseif ( strcmp( $line_start_hex, 'efbbbf44' ) == 0 )
        {
        $char_encoding = 'UTF-8';
        $line = substr( $line, 3 );
        }
    elseif ( strcmp( $line_start_hex, 'fffe4400' ) == 0 )
        {
        $char_encoding = 'UTF-16LE';
        $line = substr( $line, 2 );
        }
    elseif ( strcmp( $line_start_hex, 'feff4400' ) == 0 )
        {
        $char_encoding = 'UTF-16BE';
        $line = substr( $line, 2 );
        }
    else
        {
        echo "<p class=redbold>Error, unknown character encoding. Line =<br>", $line_start_hex, '</p>';
        require( '../footer.php' );
        die();
        }
    echo "<p>char_encoding = $char_encoding</p>";
    }

// Convert UTF
if ( $char_encoding != 'ASCII' )
    {
    $line = mb_convert_encoding( $line, 'ASCII', $char_encoding);
    }

echo '<p>'; var_dump( $line ); echo '</p>';
}

输出:

    char_encoding = UTF-16LE

string(101) "DocumentNumber,RecordedTS,Title,PageCount,City,TransTaxAccountCode,TotalTransferTax,Description,Name
"

string(83) "???????????????????????????????????????????????????????????????????????????????????"

string(88) "????????????????????????????????????????????????????????????????????????????????????????"

string(84) "????????????????????????????????????????????????????????????????????????????????????"

string(80) "????????????????????????????????????????????????????????????????????????????????"
2个回答

5

明确传递顺序和可能的编码以进行检测,并使用严格参数。此外,如果文件是UTF-16LE,请使用file_get_contents,否则会出错。

<?php
header( "Content-Type: text/html; charset=utf-8");
$input = file_get_contents( $file_in );

$encoding = mb_detect_encoding( $input, array(
    "UTF-8",
    "UTF-32",
    "UTF-32BE",
    "UTF-32LE",
    "UTF-16",
    "UTF-16BE",
    "UTF-16LE"
), TRUE );

if( $encoding !== "UTF-8" ) {
    $input = mb_convert_encoding( $input, "UTF-8", $encoding );
}
echo "<p>$encoding</p>";

foreach( explode( PHP_EOL, $input ) as $line ) {
    var_dump( $line );
}

顺序很重要,因为UTF-8和UTF-32更为严格,而UTF-16非常宽松; 几乎任何随机的偶数字节长度都是有效的UTF-16。

如果您想保留所有信息,则唯一的方法是将其转换为Unicode编码,而不是ASCII。


1
我的建议是将所有内容转换为UTF-8或ASCII(根据您发布的代码,我不确定您是否尝试将所有内容转换为UTF-8或ASCII)。
$utf8Line = iconv( mb_detect_encoding( $line ), 'UTF-8', $line );

或者...

$asciiLine = iconv( mb_detect_encoding( $line ), 'ASCII', $line );

你可以利用 mb_detect_encoding 来为你完成繁重的工作。

不幸的是,mb_detect_encoding 看起来会针对一些 UTF 文件返回“ASCII”。 - George
糟糕,错过了问题的那一部分...回到绘图板。 - Jeff Lambert
但是 ASCII 是 Unicode 的一个子集(前 255 个十进制数),所以它们应该很容易转换。只需将其转换为 ASCII,不使用多字节字符串即可。哦,你有没有考虑过对供应 FTP 数据的人大喊大叫? - Amelia
我曾试图对提供文件的人大喊大叫,但是对一个县级机构大喊大叫就像对一堵墙说话一样。他们只会按照自己的方式行事! - George
我尝试使用iconv替换mb_convert_encoding。对于UTF-16LE文件,iconv返回空字符串。 - George
显示剩余2条评论

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