fgetcsvで文字が消える

fgetcsvはシフトJISで正しく動作しない、というのは前に確認したことがあった(頭の片隅に残っていた)。再度調べたらダブルクォーテーションで値が囲われていない場合にエラーが出るらしい(自分で確認すればいいのだが、そもそもシフトJISのまま使うことがないので確認しない)。これはエクセルからエクスポートした際に問題になる。

しかし、じゃぁUTF-8やEUC-JPで問題が発生しないか、というとやっぱり発生する。サーバのロケールの設定に依存するため、使用する言語とロケールが合致していない場合、やはりダブルクォーテーションで囲われていない値があれば文字の欠落が発生する。

使用する言語が決まっているのであれば問題ない。setlocale関数で指定してやればいいから。

使用する言語が決まっていない場合(どんな言語で与えられるかわからない場合)、fgetcsvは使えない、という結論だ。

あー、やだやだ。

jQueryのブラウザ判別プラグイン

2012/01/17 | JavaScript/Ajax

以下のサイトで、jQueryプラグインで、UserAgentを判別する機能が提供されている。

http://tenderfeel.xsrv.jp/jquery/1176/jquery/1174/

インターフェイスがシンプルなので是非使いたいと思ったが、なぜかiPad、iPhoneで動いてくれない。でも他のブラウザでは動作しているように見える。初めてこのプラグインに出会ったときは「中途半端な出来なのかな(失礼)」と思い、使用を諦めたのだけれど、$.browserに値が組み込まれるというシンプルで美しい実装に心ひかれて、どうしても使いたくなった。

少なくとも「navigator.userAgent.indexOf」と書くよりは見栄えがいいと思って。

ちなみに配布されている状態では以下のようなエラーが出る(iPadで)。

TypeError: ‘null’ is not an object

「これって当たり前でしょ」ってエラー(nullがオブジェクトなわけがない)。検討してみてiOSで動かないことがわかった。でもiOSでも動作するような実装になっている。とくれば、きっと内部コードにあるiOSの場合のみ起動する部分があるはず。ということでコードを追うことにした。

でも分岐処理がわかりやすくて、コードもシンプルで、一目見て原因がわかった。UserAgentを抜き出すための正規表現の問題だ。iOS5になってUserAgentの書式が変更されたことで、本来配列として返される変数がnullになってしまっていたようだ。

112行目あたりを変更して動作した。

//var m = ua.match(/\((\w+); U;[\w\s]+? OS (\d_\d(?:_\d)*)/i);
var m = ua.match(/\((\w+); [U;]?[\w\s]+? OS (\d_\d(?:_\d)*)/i);

スマホ系に特化したUserAgentも動作しなかったので、おそらく似たような修正で動くんじゃなかろうか。

綺麗なプログラムを公開なさっている作者に感謝。

UTF-16LEをUTF-8に変換したら末尾の文字が消える

2012/01/09 | その他

ウェブアプリを作っていて、Excelからの出力ファイルを、設定ファイル等に使用したい時がある。そんな時にExcelからの出力形式でCSVやタブ区切りテキスト等を使うことも多いかと思うが、日本語の場合シフトJISで出力される。この場合、もちろんPHP側で読み込む際に文字コード変換すればいいのだが、日本語じゃなくて韓国語とか中国語とか含まれていた場合でも上手く対応できないか、と考えた。

Excelにはunicodeで出力するオプションが用意されていて、これを選ぶとタブ区切りで文字コードUTF-16LEのBOMつき、拡張子txtでファイルを出力してくれる(ちなみに拡張子をcsvに変更すると、当該ファイルのダブルクリックでExcelが自動起動してくれるので便利)。マルチバイトで使用する場合は一番便利がよさそう。

でもまぁ、UTF-8への変換は必要になる。そこで以下のようなコードを書いてみた。読み込み元ファイルのtest.txtにはtestと記述しておいた(上述のようにUTF-16LE、BOMつきのファイルとしてある)。

$str = file_get_contents(‘test.txt’);
var_dump($str);
$str = substr($str,2);
var_dump($str);
$str = trim($str);
var_dump($str);
$str=mb_convert_encoding($str,’UTF-8′,’UTF-16LE’);
var_dump($str);

見ての通り、(1)読み込んで、(2)BOMを取り除いて、(3)前後の空白文字を取り除いて、(4)文字コード変換して、というステップだ。出力された結果はこんな感じ。

string(10) “ÿþtest”
string(8) “test”
string(7) “test”
string(3) “tes”

最初のデータで、文字列の前についているゴミはBOM(UTF-16LEの場合は2バイトがBOM)。気になるのは(4)の処理後に最後の文字が欠落してしまっていることだ。で、調べてみた。

どうもUTF-16LEでは各文字の後ろに、何かはわからないけれど制御文字が入っているっぽい。(2)と(3)の結果を見ると、trimで明らかに1バイト減っている。UTF-16LEでは、この制御文字までを含めて一つの文字となっているようだ。(4)の処理後に最後の文字が欠落するのは、trimした結果制御文字がなくなってしまい、最後の文字である「t」が不完全となったため、欠落してしまったようだ。ちなみに制御コードをord関数を使って出力したら「0」となっていた。ヌル文字のようだ。

文字の構造を理解していなくて、上記の問題にぶつかったわけだけど、少し勉強になった。なんでもかんでもtrimすりゃいいってもんじゃないことを今頃理解した次第。


守谷市(まちの情報ポータル) 無料アンケートレンタルjpForm.net