- ベストアンサー
Shift-JISでのCSVファイル読み込みで「部」を含むデータが正常に取得できない問題
- PHP 5.3.3環境でShift-JISのCSVファイルを読み込むプログラムを作成しています。
- 「部」を含むデータは正常に取得できない問題が発生しており、対処方法がわかりません。
- 他のデータは正しく取得できており、ダブルクォーテーションの有無による違いも存在しています。
- みんなの回答 (7)
- 専門家の回答
質問者が選んだベストアンサー
はっきりしたことは分かりませんが、文字コードがShift_JISと認識されていないように思えます。 欧米製のソフトは大抵、デフォルトの文字コードがLatin-1です。 「部」をLatin-1で見ると「•”」となり、2文字目は「"」とは別ですが、何らかの変換で「"」になりえます。 この辺 「#tヴ鉛株渠券鵠飼諸数」 や、加えてこの辺 「%sン遠鞄挙兼酷雌藷嵩」 で同じ状況になればこれが関係していると考えられます。
その他の回答 (6)
- bm_hiro
- ベストアンサー率51% (200/388)
お礼してもらっていたのに、しばらくココ自体を見ていませんでした。 返事が遅くなってすみません。 昔 自分がやったのは結構乱暴な方法です。 CSVの一行の各文字列の中のカンマは事前に全角カンマに置換した上で、半角カンマでexplodeして、各文字列の前後にクォートがあった場合除去する。 という事を自前でやっただけです。 具体的に言うと↓みたいな感じです。 CSV : "てすと,てすと","abc","def" ↓ てすと,てすと abc def 文字列の中にクォートが入っていないのは前提でしたので使えた方法ですし、スマートなやり方ではないと思いますし、負荷的にどうなのかも分かりません。
- hogehoge78
- ベストアンサー率80% (433/539)
VMware Playerで、似たような環境を作って試してみました。 ■OS:CentOS 6.4 (32bit) ■PHP:5.3.3(yum install phpで取得したもの) 質問者さん同様の方法でlocaledefして、sjisを作り、質問にある文字列のみを記載したcsvファイルを作り、 fgetcsvを試してみましたが、同様の症状は発生しませんでした。 何か別の場所に問題がある気がします。 ・PHPのバージョン ・OSのバージョン ・コンパイルが正常にいっているのかどうか ・コンパイラのバージョン(C言語のmblenが本当に正しく動いているのか) 気になることは結構色々ありますが、バージョンや特定の組合せによって動かないということであるとどうしようもありません。 上記のことから、文字コードを変更してから取得するなり、自前でパーサを作りなりしたほうが良いかもしれません。 どうしても気になるということであれば、一度VMwareを使うなり、VertualPCやVertualBoxを使うなりして、同じ環境を作った時に同じ現象が起こるのか試してみるしか無いですね。
- hogehoge78
- ベストアンサー率80% (433/539)
少し調べた感じですと、C言語のmblen関数で、文字長を取っているというものに成っているようで、 mblen関数は、単純にシステムに設定されているlocale情報に依存して、動作するという挙動のため setlocaleしないと上手く動かないというもののようです。 つまるところ、setlocaleが上手く言っていないような気がするんですが、 setlocaleで一度ローケルをセットした後に、 <?php echo locale(LC_ALL, 0); ?> とすると設定したローケルを返しますが、コレはどの様な値が帰ってきますか? 不正な値をsetlocaleした後は、その設定が反映されず、setlocaleする前のローケルが帰ってくるようなのですが。 現在のPHP5.3.21あたりでいろいろ試していましたが、 ・Windows→エラーは出ない(そもそもローケル文字が全然違う) ・さくらサーバ(CentOS)→エラーは出ない という結果で、もしかしたらphpのバージョンを5.3の最新にしたら解消されるのではないかという気もします。 それと、fgetcsvは、囲い文字のエスケープ文字として、なぜかデフォルトで円マークが指定されています。 これを「"」に変更したらどうなりますか?
補足
setlocaleする前と後に echo setlocale(LC_ALL,0); してみました。(localeではなくsetlocalで出てくるようでしたので…。) echo setlocale(LC_ALL, 0); setlocale(LC_ALL, 'ja_JP.sjis'); echo setlocale(LC_ALL, 0); 前:C 後:ja_JP.sjis が出力されるという結果になりました。 なお、localedefコマンドは以下のようにやっていますのでsjisが全部小文字でも問題無いと考えています。 localedef -f SHIFT_JIS -i ja_JP ja_JP.sjis また、囲い文字のエスケープ文字を"に変更してみましたが、結果は変わりませんでした。 以下のようにfgetcsvの部分をしました。 fgetcsv($fileHandler, 0, ',','"','"')
- bm_hiro
- ベストアンサー率51% (200/388)
これは俺個人の意見で古い知識に基づいたものですので聞き流し程度でお願いします。 fgetcsvは その昔 挙動不審疑惑があり、そういうのを見たり自分で体験したりで、使わないようにしてます。 多分、適切に設定してやれば、問題ないのかもしれませんがー fgetcsv関数内の ある意味ブラックボックス内で処理されることであり、中身が不透明なのが嫌だったので、結局 CSVを自前の関数作って処理したことがあります。 ちゃんとPHPの中身読めば分かることでブラックボックスでも何でもないのですが、自分で読むほどの気力はありません。
お礼
例えば http://php-demo.e1blue.net/php/status/4 のようなものを自作してやっているということですよね。 やはり自分で作った方が良いのでしょうか?
- duke_kimura
- ベストアンサー率39% (53/134)
ちょっと今手元に試す環境がないのとソースが記載されてないので憶測ですが、SJISとCP932(sjis-win)を混同されていて、文字をSJISでエンコードしようとされていませんか? WindowsからのCSVであれば#1の方の様に'sjis-win'を使用しないと、色々とうまく動かなかった記憶があります。
補足
windowsの機種依存文字の部分でおかしい動作をしているわけではないですし、SJISからUTF-8への変換部分では(質問には書きませんでしたが)普通にmb_convert_encoding($data,"UTF-8","sjijs-win");としています。 そもそもそれをする前の段階で上手くカンマで区切れず配列がずれ込むという現象に悩んでいます。 良い解決方法がありましたらお願いします。
- hogehoge78
- ベストアンサー率80% (433/539)
ja_JP.sjisが、ご利用のOSに存在しない場合は、ソレを追記してやる必要があります。 詳しくは、 http://www.softel.co.jp/blogs/tech/archives/2331 ここで公開されてました。 ただ、環境の依存性が高い為、システムを移行する場合等で問題が発生する可能性があります。 そこで、別のテンポラリファイルに、まるごとutf-8に文字コードを変換したものを作ってそれから再度fgetcsvをする方法がアリます。 <?php $content = file_get_contents('sjis-no-csv.csv'); $tmp = tmpfile(); //テンポラリファイルの作成(ファイルポインタです) fwrite($tmp, mb_convert_encoding($content, 'utf-8', 'sjis-win')); rewind($tmp); while($row = fgetcsv($tmp, 4096)){ //読み込み処理 var_dump($row); } ?> と言った具合に。 ただ、大容量なファイルが読み込まれた時にかなりパフォーマンスが良くないので、逐次読み込み時にそもそも文字コードが変換されてくれば良いのではないか、ということで、 「php://filter」を使って、ストリームフィルタをかましてやれば良いのではないかという方法。 <?php class sjis_to_utf8 extends php_user_filter{ public function filter($in, $out, &$consumed, $closing){ while($bucket = stream_bucket_make_writeable($in)){ $bucket->data = mb_convert_encoding($bucket->data, 'utf-8', 'sjis-win'); $consumed += $bucket->datalen; stream_bucket_append($out, $bucket); } return PSFS_PASS_ON; } } stream_filter_register('convert.sjis_to_utf8', 'sjis_to_utf8'); $fp = fopen('php://filter/read=convert.sjis_to_utf8/resource=test.csv', 'r'); while($row = fgetcsv($fp, 4096)){ var_dump($row); } ?> と言った具合です。 filterに標準で文字コード変換が無いので、sjisをutf8に変換するラッパーを作ってやり、ソレを読み込ませるという方法。 他にも都合の良いやり方なんかは、適当に調べてみて下さい。 作成にあたって参考にしたサイトは、 http://au1.php.net/manual/ja/function.stream-filter-register.php http://d.hatena.ne.jp/hnw/20090317 http://www.revulo.com/blog/20080304.html ココらへんです。
補足
回答No.4から内部でlatin-1になっている関係で不具合が発生している可能性が高いことが分かりました。 UTF-8の場合も別の文字で同じような問題が発生することは有るのでしょうか? 内部的に問答無用でlatin-1にしているのか、一般的でない文字コードの場合(SJIS等)のみlatin-1でやって世界的に標準のコード(UTF-8等)はlatin-1でやっていないのでしょうか? 確認する何かいい方法は有りますか? よろしくお願いいたします。
補足
指定してもらったもの全部は試せてませんが、いくつか試してみたところ「部」と同じく分けられないという事象が発生しました。 "部"が"•”"と認識されてしまいダブルクォーテーション2つ並んでいるので何らかの変換が起こり""のエスケープ(CSVでは""は"を表すエスケープだったはず)になってげんざいの状況になっているという感じでしょうか…。 Linux CentOS 6.3 で localedef -f SHIFT_JIS -i ja_JP ja_JP.sjis をコマンドラインで実行 プログラムの中で setlocale(LC_ALL, 'ja_JP.sjis'); を書きました。 (書かないと、""で囲まないCSVを一切認識しないので、setlocale設定は生きているかと思います。) これ以外にしなければいけない設定があるのでしょうか?