[解決済] ExcelでUTF-8のCSVを吐いたらwith BOMだったせいでPHPでの取込に不具合が出た件

いやー、してやられました。1時間は持って行かれました。

ExcelでCSVを吐くといえば長らくSJISで、UTF-8がスタンダードなApache系Webサーバに取り込みたい一部の人にとっては面倒くさい代物の代表格でした。

それがExcel 2016のバージョン 1610(ビルド 7466.2038)からできるようになったんですよ、UTF-8でのCSV保存。

これに狂喜乱舞したり、Office 365のプラン変えたら先祖返りして使えなくなって阿鼻叫喚したりしていました。阿鼻叫喚は以下の記事に書いてます。

Office 365 ProPlusで最新バージョンのOfficeを使う方法

ところがこの新機能で吐いたUTF-8のCSV、UTF-8はUTF-8なんですけど、「UTF-8 with BOM」なんですね。そんなものの存在さえ知らなかった私は、意気揚々とPHPで取り込もうとしてドツボにハマったのでした。

元ファイルの修正、PHP側での配慮両方で解決できたので、メモします。


スポンサーリンク

1.現象:PHPで取り込んだCSVの先頭だけが文字列比較で一致しなかった

まずはそもそもの始まりから。

ちょくちょくCakePHPのTIPSを書いている通り、最近CakePHP3を使って当たるかどうかわからないWebサービスをちまちま作っています。

で、登録するデータをCSVでアップロードしたくて、いろいろ参考にして取り込もうとしたわけですよ。具体的にはSplFileObjectを使って、行ごとに読み込み→配列に変換という感じです。

その際にヘッダ行(行番号0)の各値を判定して「何列目に何のデータがあるか」というのを記録したかったんですね。

なので、こんなコードを書きました。取り込むCSVのヘッダ行が[“param1″,”param2″,”param3”]って規定されていると思ってください。

//$objFileにはCSV用にフラグをカスタマイズしたSplFileObjectが入ってる前提

foreach ($objFile as $line) :
    //1行目がヘッダ行かどうかを検証し、どの列にどの値が入っているか確認する
    $lineNo = $objFile->key();
    if ($lineNo == 0) {
        for ($i=0; $i<count($line); $i++){
            $buf = $line[$i];
            switch ($buf) {
                case 'param1' :
                    $ints['param1'] = $i;
                    break;
                case 'param2' :
                    $ints['param2'] = $i;
                    break;
                case 'param3' :
                    $ints['param3'] = $i;
                    break;
            }
        }
   } else {
        …以下略…

そしたらですね。param1だけがヒットしなかったんですよ。

このときテストに使ったCSVは最新のExcelでUTF-8保存したもの。「Excelで保存した」という事実がどうも怪しいよなあ…とアタリをつけ、テキストエディタ(Visual Studio Code)で開いたところ、こんな表示が。

with BOMってなんぞや。

param2、param3は問題なく判定できているし、比較している文字列は英数字だから、もうこれしか疑うところがない。そう思いました。

2.UTF-8 with BOMとは

下記サイトの記述がわかりやすかったです。

UTF-8とは:BOM付きとBOMなし(UTF-8N)の違い――Unicode関連の文字エンコード

BOMとは

Byte Order Markの略で、通常「ボム」と読む。
Unicodeのファイルでバイト順を示すために先頭につける数バイトのデータのこと。

他のサイトも参考にすると、「あ、私UTF-8で書いてあります、どうも」と宣言するバイトコードが入っているらしいんですね。

結論から申し上げますと、この先頭についているバイトコードが原因で、同じに見える文字列が一致しないと判断されていました。


スポンサーリンク

3.対応方法その1:ファイルをBOMなしにする

BOMがあるから読み込めない、ならばBOMなしにすればいい。という単純な論理ですね。

大抵のテキストエディタは文字コードを変更して保存ということができます。

Visual Studio Codeだとこんな感じ。

他のテキストエディタであれば、下記サイトに解説がありました。

UTF-8、BOMなしでテキストファイルを保存する方法 | 某氏の猫空

BOMなしUTF-8はExcelで開けない?

調べているうちに、「BOMなしのUTF-8なCSVはExcelで開けない」という諸説を何度か目にしました。

試してみたところ、当方の環境ではBOMなしでも無事にExcelで開けたので真偽のほどはわかりません。バージョンによるのでしょうか?

ただ、BOMがないとCSVを開くことのできないアプリケーションは一定数あるとのことです。逆にBOMがあると開けないアプリケーションもあって…もう!なんなんだよ!!

4.対応方法その2:PHP側で配慮を入れる

BOMがあると開けないとか、ないと開けないとか、お前らめんどくせぇんだよ!!!

ということで、ファイルを直には触らずに、読み込むPHP側で配慮を入れてみました。

ようはファイルの先頭に入っているバイトコードが原因なのだから、そいつを無視してしまえというやつです。先程掲載したコードの改造版です。

//$objFileにはCSV用にフラグをカスタマイズしたSplFileObjectが入ってる前提

foreach ($objFile as $line) :
    //1行目がヘッダ行かどうかを検証し、どの列にどの値が入っているか確認する
    $lineNo = $objFile->key();
    if ($lineNo == 0) {
        for ($i=0; $i<count($line); $i++){
            $buf = $line[$i];
            $buf = preg_replace('/^\xEF\xBB\xBF/', '', $buf); //これ
            switch ($buf) {
                case 'param1' :
                    $ints['param1'] = $i;
                    break;
                case 'param2' :
                    $ints['param2'] = $i;
                    break;
                case 'param3' :
                    $ints['param3'] = $i;
                    break;
            }
        }
   } else {
        …以下略…

コードは下記サイトを参考にさせて頂きました。

UTF-8 ファイルから読み込んだデータの BOM を取り除く

BOMを正規表現で空文字に変換してしまえ!というやつですね。

これで私のプログラムは正常に動くようになりました。

やっとUTF-8でファイルを作れるようになったと思ったのに、MicrosoftはやっぱりMicrosoftなのでした。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*