こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

shift-jisでfgetcsv

以下環境でCSVファイルの読み込プログラムを作っていますが、上手くいかないケースがあり困っています。

PHP 5.3.3
サーバー Linux
読み込むCSVの文字コード ShiftJIS
phpの文字コード UTF-8

当然ですが
setlocale(LC_ALL, 'ja_JP.sjis');
の設定はしています。

正常に動くケース
・"(ダブルクォーテーション)で囲んでない場合
・"(ダブルクォーターション)で囲んであるケースのほとんど

正常に動かないケース
・"(ダブルクォーテーション)で囲んであり最後の文字が「部」の場合(他にも有る可能性がありますが現状発見できているのはこれのみ)

$data = getcsv($fileHandler, 0, ',');

とした時に
元データが
"a","テニス部","12345"
の場合
$data[0]→a
$data[1]→テニス部",12345"
となります。
(何故か12345の頭の"はどこかに行きます。)

これが
"a","テニス社","12345"
の場合は
$data[0]→a
$data[1]→テニス社
$data[2]→12345
と求めた結果になります。

また、
a,テニス部,12345
とクォーテーション無しの場合は
$data[0]→a
$data[1]→テニス社
$data[2]→12345
想定通りとなります。

部と"の組み合わせがいけないということはわかりますが何をどうすればいいのか見当がつきません。
Shift-JISの所謂「駄目文字」に部は入っていないですし…。

読み込みCSVは残念ながら、ダブルクォーテーション有りのファイルと無しのファイルが混在しています。(同一ファイル内での混在は無い前提)

どのようにすれば部の文字を含んだファイルを正常に読み込めるでしょうか?
よろしくお願いします。

投稿日時 - 2013-07-09 22:56:47

QNo.8169744

困ってます

質問者が選んだベストアンサー

はっきりしたことは分かりませんが、文字コードがShift_JISと認識されていないように思えます。
欧米製のソフトは大抵、デフォルトの文字コードがLatin-1です。
「部」をLatin-1で見ると「•”」となり、2文字目は「"」とは別ですが、何らかの変換で「"」になりえます。
この辺
「#tヴ鉛株渠券鵠飼諸数」
や、加えてこの辺
「%sン遠鞄挙兼酷雌藷嵩」
で同じ状況になればこれが関係していると考えられます。

投稿日時 - 2013-07-10 23:31:58

補足

指定してもらったもの全部は試せてませんが、いくつか試してみたところ「部」と同じく分けられないという事象が発生しました。
"部"が"•”"と認識されてしまいダブルクォーテーション2つ並んでいるので何らかの変換が起こり""のエスケープ(CSVでは""は"を表すエスケープだったはず)になってげんざいの状況になっているという感じでしょうか…。

Linux CentOS 6.3 で
localedef -f SHIFT_JIS -i ja_JP ja_JP.sjis
をコマンドラインで実行

プログラムの中で
setlocale(LC_ALL, 'ja_JP.sjis');
を書きました。
(書かないと、""で囲まないCSVを一切認識しないので、setlocale設定は生きているかと思います。)

これ以外にしなければいけない設定があるのでしょうか?

投稿日時 - 2013-07-11 09:30:25

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(7)

ANo.7

お礼してもらっていたのに、しばらくココ自体を見ていませんでした。
返事が遅くなってすみません。

昔 自分がやったのは結構乱暴な方法です。
CSVの一行の各文字列の中のカンマは事前に全角カンマに置換した上で、半角カンマでexplodeして、各文字列の前後にクォートがあった場合除去する。
という事を自前でやっただけです。

具体的に言うと↓みたいな感じです。

CSV : "てすと,てすと","abc","def"

てすと,てすと
abc
def

文字列の中にクォートが入っていないのは前提でしたので使えた方法ですし、スマートなやり方ではないと思いますし、負荷的にどうなのかも分かりません。

投稿日時 - 2013-07-14 17:05:29

ANo.6

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を使うなりして、同じ環境を作った時に同じ現象が起こるのか試してみるしか無いですね。

投稿日時 - 2013-07-14 16:28:57

ANo.5

少し調べた感じですと、C言語のmblen関数で、文字長を取っているというものに成っているようで、

mblen関数は、単純にシステムに設定されているlocale情報に依存して、動作するという挙動のため
setlocaleしないと上手く動かないというもののようです。

つまるところ、setlocaleが上手く言っていないような気がするんですが、
setlocaleで一度ローケルをセットした後に、

<?php

echo locale(LC_ALL, 0);

?>
とすると設定したローケルを返しますが、コレはどの様な値が帰ってきますか?
不正な値をsetlocaleした後は、その設定が反映されず、setlocaleする前のローケルが帰ってくるようなのですが。

現在のPHP5.3.21あたりでいろいろ試していましたが、
・Windows→エラーは出ない(そもそもローケル文字が全然違う)
・さくらサーバ(CentOS)→エラーは出ない

という結果で、もしかしたらphpのバージョンを5.3の最新にしたら解消されるのではないかという気もします。

それと、fgetcsvは、囲い文字のエスケープ文字として、なぜかデフォルトで円マークが指定されています。
これを「"」に変更したらどうなりますか?

投稿日時 - 2013-07-12 02:41:36

補足

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, ',','"','"')

投稿日時 - 2013-07-12 12:00:46

ANo.3

これは俺個人の意見で古い知識に基づいたものですので聞き流し程度でお願いします。

fgetcsvは その昔 挙動不審疑惑があり、そういうのを見たり自分で体験したりで、使わないようにしてます。

多分、適切に設定してやれば、問題ないのかもしれませんがー

fgetcsv関数内の ある意味ブラックボックス内で処理されることであり、中身が不透明なのが嫌だったので、結局 CSVを自前の関数作って処理したことがあります。
ちゃんとPHPの中身読めば分かることでブラックボックスでも何でもないのですが、自分で読むほどの気力はありません。

投稿日時 - 2013-07-10 16:55:34

お礼

例えば
http://php-demo.e1blue.net/php/status/4
のようなものを自作してやっているということですよね。
やはり自分で作った方が良いのでしょうか?

投稿日時 - 2013-07-11 14:00:39

ANo.2

ちょっと今手元に試す環境がないのとソースが記載されてないので憶測ですが、SJISとCP932(sjis-win)を混同されていて、文字をSJISでエンコードしようとされていませんか?

WindowsからのCSVであれば#1の方の様に'sjis-win'を使用しないと、色々とうまく動かなかった記憶があります。

投稿日時 - 2013-07-10 09:59:27

補足

windowsの機種依存文字の部分でおかしい動作をしているわけではないですし、SJISからUTF-8への変換部分では(質問には書きませんでしたが)普通にmb_convert_encoding($data,"UTF-8","sjijs-win");としています。
そもそもそれをする前の段階で上手くカンマで区切れず配列がずれ込むという現象に悩んでいます。

良い解決方法がありましたらお願いします。

投稿日時 - 2013-07-12 12:07:34

ANo.1

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

ココらへんです。

投稿日時 - 2013-07-10 02:08:45

補足

回答No.4から内部でlatin-1になっている関係で不具合が発生している可能性が高いことが分かりました。

UTF-8の場合も別の文字で同じような問題が発生することは有るのでしょうか?

内部的に問答無用でlatin-1にしているのか、一般的でない文字コードの場合(SJIS等)のみlatin-1でやって世界的に標準のコード(UTF-8等)はlatin-1でやっていないのでしょうか?

確認する何かいい方法は有りますか?

よろしくお願いいたします。

投稿日時 - 2013-07-11 18:46:06

あなたにオススメの質問