PHPでCSVの読み取り不具合
事象
PHP7.4.29環境で、以下のUTF-8エンコードされたCSVファイルを読み込んで処理させると、得られる解析結果に異常が見られます。
ng.csv
)"","尖","5C16","0101","1100","0001","0110" "","答","7B54","0111","1011","0101","0100" "","五","4E94","0100","1110","1001","0100" "","近","8FD1","1000","1111","1101","0001"
<?php $csvFile = fopen("ng.csv", "r"); fseek($csvFile, 3); //BOM無視 while($line = fgetcsv($csvFile)) { for($idx = 0, $size = count($line); $idx < $size; $idx++ ){ echo "[". $line[$idx] ."]"; } echo "<br>"; } fclose($csvFile); ?>
[][尖",5C16"][0101][1100][0001][0110] [][答",7B54"][0111][1011][0101][0100] [][五",4E94"][0100][1110][1001][0100] [][近",8FD1"][1000][1111][1101][0001]
漢字で終わっているCSVデータセルを囲っていたダブルクォーテーションマークがデータ文字列として残ってしまっています。
本事象はPHP7.4.29で再現します
発生条件
解析で不具合が発生する条件は、閉じの"
の直前のマルチバイト文字にあります。
文字コードのビット数値列の後ろから5-6桁目、16進数表記3文字目に対応するバイト列の下2桁bitが01
であること、がトリガーです。
対策
PHPのバージョンを8.xへアップデートしてください。事象が解消されています。
7.4.29のままで事象に対してフォローアップするような処理は申し訳ありませんが見当つきませんでした
以下は本件の発見と事象発生条件の探求記録です
事象の発見と検証
PHP7.4.29環境でCSVファイルをHTMLの<table>表示に変換する処理を書いていました。
710レコードのデータのうち、いくつもの漢字表記のセルで上記事象に示したような変換エラーが生じていました。
特定の漢字の場合は必ずエラーが生じているようだったので、探求を開始。
どんな文字で事象が起きているのか、文字コードを容疑者として追及することにし、UTF-8文字コードと突合しました。
実データの正常パターンとエラーパターンを拾っていると、ある程度法則性が見つかり、
「0000 0000 0011 0000
」の11
部分のビット並びが「01
」の時、事象が発生するようだ、と判断しました。
"","言","8A00","1000","1010","0000","0000" "","訐","8A10","1000","1010","0001","0000" "","訠","8A20","1000","1010","0010","0000" "","訰","8A30","1000","1010","0011","0000" "","詀","8A40","1000","1010","0100","0000" "","詐","8A50","1000","1010","0101","0000" "","詠","8A60","1000","1010","0110","0000" "","詰","8A70","1000","1010","0111","0000" "","誀","8A80","1000","1010","1000","0000" "","誐","8A90","1000","1010","1001","0000" "","誠","8AA0","1000","1010","1010","0000" "","誰","8AB0","1000","1010","1011","0000" "","諀","8AB0","1000","1010","1100","0000" "","諐","8AD0","1000","1010","1101","0000" "","諠","8AE0","1000","1010","1110","0000" "","諰","8AF0","1000","1010","1111","0000"
[][言][8A00][1000][1010][0000][0000] [][訐",8A10"][1000][1010][0001][0000] [][訠][8A20][1000][1010][0010][0000] [][訰][8A30][1000][1010][0011][0000] [][詀][8A40][1000][1010][0100][0000] [][詐",8A50"][1000][1010][0101][0000] [][詠][8A60][1000][1010][0110][0000] [][詰][8A70][1000][1010][0111][0000] [][誀][8A80][1000][1010][1000][0000] [][誐",8A90"][1000][1010][1001][0000] [][誠][8AA0][1000][1010][1010][0000] [][誰][8AB0][1000][1010][1011][0000] [][諀][8AB0][1000][1010][1100][0000] [][諐",8AD0"][1000][1010][1101][0000] [][諠][8AE0][1000][1010][1110][0000] [][諰][8AF0][1000][1010][1111][0000]
一部パターンに沿わない文字も存在していました。
「儚」(511A
=0101 0001 0001 1010
)エラーにならない
「久」(4E45
=0100 1110 0100 0101
)エラーになってしまう
規則性に外れたものはよく分からないままです。