从 mysql 数据库生成的可下载 csv 文件中的 Unicode 字符不可读

Unicode characters not readable in downloadable csv file generated from mysql database

我正在尝试使用 php 脚本生成从 mysql 数据库生成的 csv 可下载文件。它正在工作,但其中的 unicode 字符不可读。当我在 Notepad++ 中打开时,unicode 字符是可读的。我阅读了有关此问题的答案,但没有帮助。请帮忙。以下是我的代码 -

<?php
mb_internal_encoding("UTF-8");
mb_http_output( "UTF-8" );    
ob_start("mb_output_handler");

include("t/db_config.php");
$con=mysqli_connect($db_host,$db_user,$db_password,$db_name);

// Check connection
if (mysqli_connect_errno())
  {
  echo "Failed to connect to MySQL: " . mysqli_connect_error();
  }
  //set_charset when connecting with database
mysqli_set_charset( $con, 'utf8');

$data=array();

 $sql="SELECT s.t_id,s.t_text,p.user_name,p.description,s.time,p.place from 
 t 
 AS s INNER JOIN users AS p ON s.user_name=p.user_name order by s.time 
 desc";
 $result = mysqli_query($con,$sql);

  while($row = mysqli_fetch_array($result)) {
    $array=array("Link" => $row[0],"Text"=>$row[1] , "User Name" => $row[2] 
  , "User Profile" => $row[3], "Time" => $row[4] , "Place" => $row[5]);
     array_push($data,$array);
  }
  function cleanData(&$str)
  {
    if($str == 't') $str = 'TRUE';
    if($str == 'f') $str = 'FALSE';
    if(preg_match("/^0/", $str) || preg_match("/^\+?\d{8,}$/", $str) || 
  preg_match("/^\d{4}.\d{1,2}.\d{1,2}/", $str)) {
      $str = "$str";
    }
    if(strstr($str, '"')) $str = '"' . str_replace('"', '""', $str) . '"';
  }

  // filename for download
  $filename = "website_data_" . date('Ymd') . ".csv";

  header("Content-Disposition: attachment; filename=\"$filename\"");
  header("Content-Type: text/csv");

  $out = fopen("php://output", 'w');

  $flag = false;
  foreach($data as $row) {
    if(!$flag) {
      // display field/column names as first row
      fputcsv($out, array_keys($row), ',', '"');
      $flag = true;
    }
    array_walk($row, 'cleanData');
    fputcsv($out, array_values($row), ',', '"');
  }

  fclose($out);
  exit;
?>

这是一个示例输入,也是所需的输出 -

umeshdutt निर्दोष को सजा मिल रही है

但是在 excel -

中打开 csv 文件后得到以下输出

umeshdutt कमाल का कानून है जि मे नि ¤°à¤¹à¥€ हà¥^।

编辑-

Mysql table 具有样本数据的结构

Table t

1)t_id (初级.....|2).....t_text......|3).....时间... .....|4)..user_name........
===================|============================== ========================= 1)bigint(20)......| 2) 变量 (255) | 3)日期时间 | 4)字符(20)
...................| 2) utf8_general_ci | | 4)utf8_general_ci ===================|=================|============ ========================= 847589475442204000 | संविधान 'सुप्रीम' है | 2017 年 3 月 31 日 5:01:52 上午 |科蒂安人

Table 用户

1) user_id(小学) | 2)user_name | 3)地点 | 4)描述 ==================|================|============== =|============
1) 大整数(20) |2) 字符(20) | 3) 可变字符(30) |4) 可变字符(200) |2) utf8_general_ci |3) utf8_general_ci|4) utf8_general_ci ==================|================|============== =|=============

2883542694 |科蒂安人 |阿德莱德 |工程师

这是 运行 成功的完整 PHP 代码(诚然,我没有花时间系统地删除 encodingheader 函数以查看是否它仍然可以使用更少的代码):

if(!$con=mysqli_connect("host","user","pass","db")){
    echo "Failed to connect to MySQL: ",mysqli_connect_error();
}else{
    mysqli_set_charset($con,'utf8');
    $sql="SELECT
              CONCAT('=\"',t.t_id,'\"'),
              t.t_text,
              p.user_name,
              p.description,
              CONCAT('=\"',t.time,'\"'),
              p.place
          FROM `t`
          INNER JOIN `users` p ON t.user_name=p.user_name
          ORDER BY t.time DESC;";   
    if($result=mysqli_query($con,$sql)){
        header("Content-Disposition: attachment; filename=\"website_data_".date('Ymd').".csv\"");
        header("Content-Type: text/csv");
        header('Pragma: no-cache');    
        header('Expires: 0');
        $out=fopen('php://output','w');
        fputs($out,"\xEF\xBB\xBF");  // Byte Order Mark
        fputcsv($out,["Link","Text","User Name","User Profile","Time","Place"],',','"');
        while($row=mysqli_fetch_row($result)){
            fputcsv($out,$row,',','"');
        }
        fclose($out);
    }else{
        echo mysqli_error($con);    
    }
}

在Excel中默认[=​​15=]的大整数值将使用科学记数法(8.47589E+17)显示,t.time的格式将变为:n/j/Y g:i:s

为了掩盖这些默认调整,我将值用双引号 (") 括起来,并在每个值前面加上 =.

我建议在 sql 中执行任何数据库值 cleaning/modifying,因为您可以准备特定的列来处理已知问题,而不是迭代行中的所有值。

"Byte Order Mark" 是对原始代码的重要补充。

看来至少可以这样写,这三种写法效果一样:

fputs($out,chr(0xEF).chr(0xBB).chr(0xBF));
fputs($out,chr(239).chr(187).chr(191));
fputs($out,"\xEF\xBB\xBF");  // I chose the shortest one

参考资料和补充阅读:

我收录了几个 suggestions/refinements 比如:

  • 在继续生成 csv 之前检查非假 $result
  • 添加了几个额外的 header() 语句以确保新鲜度。
  • fputcsv() 在循环之前静态地编辑了键数组。
  • 简化了 while() 循环中的过程。

我使用了这些表中的数据:

CREATE TABLE `t` (
  `t_id` bigint(20) NOT NULL,
  `t_text` varchar(255) CHARACTER SET utf8 NOT NULL,
  `time` datetime NOT NULL,
  `user_name` char(20) CHARACTER SET utf8 NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

INSERT INTO `t` (`t_id`, `t_text`, `time`, `user_name`) VALUES
(847589475442204000, 'संविधान \'सुप्रीम\' है', '2017-03-01 05:01:52', 'kotians');

ALTER TABLE `t` ADD PRIMARY KEY (`t_id`);

CREATE TABLE `users` (
  `user_id` bigint(20) NOT NULL,
  `user_name` char(20) CHARACTER SET utf8 NOT NULL,
  `place` varchar(30) CHARACTER SET utf8 NOT NULL,
  `description` varchar(200) CHARACTER SET utf8 NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

INSERT INTO `users` (`user_id`, `user_name`, `place`, `description`) VALUES
(2883542694, 'kotians', 'Ade\'laide', 'Engi\"neer');

ALTER TABLE `users` ADD PRIMARY KEY (`user_id`);

这是生成的 CSV 文件中活动单元格的屏幕截图: