1

我正在尝试将 CSV 文件导入到使用 Drupal 构建的 PHP 应用程序中。我在导入从Mozilla Thunderbird导出的CSV文件时遇到了一个奇怪的情况(我正在导出联系人的通讯录)。如果我使用 Windows 版本的 Thunderbird 导出,任何多字节字符都不会呈现到屏幕上,并且在将提取的内容的内容转储到屏幕时显示为缺失字符。但是,当使用使用 Linux 版本的 Thunderbird 创建的相同文件时,不存在此问题。在这种情况下,一切都完美无缺。

为了测试这一点,我在 Linux 和 Windows 7 上安装了相同版本的 Thunderbird。然后我在地址簿中创建了相同的单个用户(姓:张,名字:利),然后将地址簿导出为 CSV 文件。如上所述,linux CSV 文件可以成功导入,但 Windows 不能。

如果我在 linux 中检查这两个文件,使用file --mime myfilename.csvis 得到以下输出:

LinuxTB14.csv:文本/纯文本;字符集=utf-8

WinTB14.csv:文本/纯文本;字符集=iso-8859-1

所以windows文件,即使它包含中文字符,也被编码为iso-8859-1。发现这一点后,我认为这是一个编码问题,我只需要告诉 PHP 将违规内容编码为 UTF-8。

问题是 PHP 似乎以另一种我无法理解的方式检测编码。

// Set correct locale to avoid any issues with multibyte characters.
$original_local_value = setlocale(LC_CTYPE, 0);
if ($original_local_value !== 'en_US.UTF-8') {
  setlocale(LC_CTYPE, 'en_US.UTF-8');
} 
$handle = fopen($file->uri, "r");
$cardinfo = array();
while (($data = fgetcsv($handle, 5000, ",")) !== FALSE) {
  $cardinfo[] = $data;
  // dsm() is a drupal function which prints the content of the argument to screen.
  dsm(mb_detect_encoding($data[0])); 
  dsm($data[0]);
}

如果我包含上面的代码,它显示了 CSV 文件每一行中第一个值的编码和内容,我会在屏幕上显示以下内容:

对于 Thunderbird 在 windows 中创建的 CSV

ASCII

UTF-8

对于 Thunderbird 在 Linux 中创建的 CSV

ASCII

UTF-8

利</p>

如您所见,PHP 报告两个文件的编码相同,即使 Windows 文件中的中文字符没有打印到屏幕上。

有人知道这里可能发生什么吗?

编辑

如果我在记事本中打开 Windows CSV 文件并另存为.. UTF-8 格式,则文件将正确导入。所以这显然是一个编码问题。如果文件编码尚未设置为 UTF-8,我添加了以下代码来转换文件编码。

  $file_contents = file_get_contents($file->uri);
  $file_encoding = mb_detect_encoding($file_contents, 'UTF-8, ISO-8859-1, WINDOWS-1252');
  if ($file_encoding  !== 'UTF-8') {
    $file_contents = iconv($file_encoding, 'UTF-8', $file_contents);
    $handle = fopen($file->uri, 'w');
    fwrite($handle, $file_contents);
    fclose($handle);
  }

这部分解决了问题。字符出现了,但它们是乱码(例如,张显示为 ÕÅ)。我检查了浏览器的页面编码和页面标题,两者都设置为 UTF-8,所以这不是浏览器问题。

有任何想法吗?

4

1 回答 1

1

我为这个问题提出的唯一解决方案是首先不尝试检测和转换上传文件的编码。经过大量研究,似乎并不存在可靠的编码检测。这样做有太多的错误空间。

最安全的选择是确保上传的文件以 UTF-8 编码,因为可以可靠地检测到 UTF-8 编码。以下代码是我如何进行 UTF-8 编码检测。

$file_content = file_get_contents($file->uri);
// Create regex pattern which detects UTF-8 encoding.
$regex = '%^(?:
  [\x09\x0A\x0D\x20-\x7E]              # ASCII
  | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
  | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
  | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
  | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
  | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
  | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
  | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
)*$%xs';
if (!preg_match($regex, $file_content)) {
  // Not valid UTF-8 encoding so flag an error.
}
于 2012-07-23T06:48:28.047 回答