【PHP】多言語のデータをCSVに出力する

多言語のテキストを含むデータをCSVファイルに出力して、さらにエクセルで開いて編集したいという要件があったのでその時の対応メモ

データのイメージとしては以下
$data = [
    array(
        'id' => 1,
        'name_ja-JP' => 'テスト',
        'name_en-US' => 'test',
        'name_zh-CN' => '测验',
        'name_ko-KR' => '테스트',
    ),
    array(
        'id' => 2,
        'name_ja-JP' => '茄子',
        'name_en-US' => 'eggplant',
        'name_zh-CN' => '雄狮',
        'name_ko-KR' => '가지',
    ),
];

ダメなケース

処理
最初は以下のようなコードだった。
エクセルで編集という要件から、Shift-JISに変換して出力している。
$handle = fopen('data.csv', 'w');
foreach($data as $row){
    array_walk($row, function(&$item){
        $item = mb_convert_encoding($item, 'Shift-JIS', 'UTF8');
    });
    fputcsv($handle, $row);
}
fclose($handle);

エクセルで開く
bom_1.png 日本語以外のマルチバイト文字列が文字化けします。
そりゃそうです。Shift-JISですから...

以下のように対応した

処理
$buff = pack('C*',0xEF,0xBB,0xBF);
foreach($data as $row){
    $buff .= implode(',', $row)."\n";
}
file_put_contents('data.csv', $buff);
ファイルの先頭にBOM(Byte Order Mark)と呼ばれるおまじないを出力する
これは、どの形式の符号化形式を採用しているかなどの情報をもつ16ビットの値
UTF-8の場合は先頭の3ビットがそれにあたり、0xEF 0xBB 0xBF というデータになる
したがって文字コードはUTF-8で出力するのが大前提で、それ以外だった場合はUTF-8への変換が必要

エクセルで開く
bom_2.png

ちなみにこの方法だとこんな文字も出力できる。
bom_3.png

BOM付きのCSVファイルを読み込む時

読み込むときは注意が必要で、BOMを除外しないと正しく読み込めない可能性がある
(先頭がヘッダー行とかだったら無視してもいいかもしれない)
  $count = 0;
  while (($data = fgetcsv($handle, 4096, ",")) !== FALSE) {
      if ($count == 0) {
          // BOM削除
          $data[0] = trim(preg_replace('/^\xEF\xBB\xBF/', '', $data[0]), '"');
      }
      $count++;
上記のコードは正規表現で先頭のBOMを削除している。
また、エンクロージャー付きのCSVファイルの場合、BOMを読み込んだカラムに限りエンクロージャーごと取り込んでしまうので、trimしてやると良い。
上記コードの例だと変数 $data[0] にこんな感じで入ってる

<BOM>"1"

Shift-JISとUTF-8の特徴

Shift-JIS --- 消費バイト数が比較的少ない。
表現できる文字種は日本語を含む9000種ほど
UTF-8 --- 文字範囲が広く、どんな言語の文字でも文字化けしない(世界標準)
容量はShift-JISの約1.5倍

この記事へのコメント

スポンサーリンク