• ベストアンサー

guess_encoding()の挙動が意味不明

以下のプログラムを作成しました。   #! /usr/bin/perl -   #   #文字コードが分からない文字列を、   #文字コードを判別した上で、UTF-8に変換して表示   #   #   #テストを行うファイル   my $fh="shift-JIS.txt";   use strict;   use CGI;   use CGI::Carp qw(fatalsToBrowser);   use Encode;   use Encode::Guess;   my @all_encodings=Encode->encodings(":all"); ・・・A   open IN,$fh;   our @data=<IN>;   close IN;   my $q= new CGI;   print $q->header(-charset=>'utf8'),    $q->start_html(-title=>'Encode::Guessの使い方',    -lang=>'ja-JP',    -charset=>'utf8'),    $q->p('Encodeモジュールがサポートしている文字コード形式を対象に、<br>        テストファイルの文字コードを判別した上で、UTF-8に変換してUTF-8のHTMLファイル中で表示するCGIです。');   our ($check,$decoder);   foreach my $i (@all_encodings) { ・・・B    if($i ne "UCS-2BE" and $i ne "UTF-16" and $i ne "UTF-16BE" and $i ne "UTF-32") { ・・・C     $check=1;     foreach my $j (@data) {     $decoder=guess_encoding($j,$i); ・・・D     $check=0 unless ref($decoder);     }     print "$check &nbsp; $i<br>\n";     if($check==1) {     foreach my $j (@data) {      $j=$decoder->decode($j);     $j=encode('utf-8', $j);     print "$j<br>\n";     }     }    }   }   print $q->end_html; D式の$iを"shiftjis"、"euc-jp"でハードコードしてテストをすると、前者ではファイルの中身が書き出され、後者ではタイトル以外表示されませんでした。意図された通りの動作です。 次に、A式でEncodeがサポートしている文字コードを全て@all_encodingsに格納し、その一つ一つをB式のforeach文でD式に代入しています。 想定した挙動は $i="shiftjis"の時のみ     $check=1で文字列を表示し、  それ以外では     $check=0で文字列は表示されない というものです。 ところが結果は、   0 7bit-jis   0 AdobeStandardEncoding   0 AdobeSymbol   0 AdobeZdingbat   0 ascii   0 ascii-ctrl   0 big5-eten   0 big5-hkscs   0 cp1006   1 cp1026   b£b2boE[NAE CnebEnK±ib§b\b§b¨aa   0 cp1047   1 cp1250   b£b2boE[NAE CnebEnK±ib§b\b§b¨aa   0 cp1251   0 cp1252   1 cp1253   b£b2boE[NAE CnebEnK±ib§b\b§b¨aa   (続く) というように$check=1を取る文字コードが幾つかあり、不思議なことには表示された文字列が全て全く同じ文字化けをおこしています。$i="shiftjis"の時でも同じです。ハードコートしていた時には、"shiftjis"の時には正しい表示が行われていました。 ましてや、C式ではじいた2種の文字コードに至っては、他の文字コードと同様に処理を行うとエラーが出てプログラムが止まるから、はじかざるを得ませんでした。 ちなみにそのエラーメッセージを表示すると、 UTF-16:Unrecognised BOM 62c2 at O:/usr/lib/Encode/Guess.pm line 139. といった感じです。 どうしてこうなるのでしょう。 というか、どうすればキチンときどうするようになるでしょう。 お手数をおかけします。

  • Perl
  • 回答数8
  • ありがとう数7

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

  • ベストアンサー
  • zxcv0000
  • ベストアンサー率56% (111/196)
回答No.2

まず、Encode::Guess を含む「文字コードの推測」というのは、確率的にしか正解を得られない事を理解してください。 例えば SJIS で『珈琲』と書かれていた場合、そのビットパターンは \xE0\xDB\xE0\xE8 です。 これは、EUC-JP として解釈すれば『獻琥』ですし GBK と言う中国国家規格コードとして解釈すればSJISには無い文字 2個になります。 #! /usr/bin/perl -w use Encode::Guess qw/shiftjis euc-jp 7bit-jis GBK Windows-31J/; my $code = guess_encoding("\xE0\xDB\xE0\xE8"); print "$code\n"; これを実行すると、 shiftjis or euc-jp or cp936 or cp932 という結果が得られます。 これと同じ事があなたのプログラムで起れば、間違った encoding でも ref($decoder) が true になるでしょう。 **** Encode::Guess の鉄則 1 推測候補の文字コードは手動で指定する shiftjis(業界規格のSJIS) と Windows-31J(cp932) の別名は良く似たSJIS系のコードなので、両方を候補にするのはやめましょう。 推測結果に日本語を期待するなら、GBK(cp936の別名) を入れるのはやめましょう。 Encode->encodings(":all") の結果を使うと、これらが全て入ってしまうのです。 手動で選りすぐりの encoding だけを指定しましょう。 **** Encode::Guess の鉄則 2 対象バイト列を分割して推測しない 鉄則1 を守って候補から GBK と Windows-31J を消せば、誤判定は EUC-JP だけになります。 この誤判定を確実に消す方法はありません。 しかし、同じファイル中の別のバイト列をも推測材料に加えれば確率的に誤判定を減らす事ができます。 これについては、makoji さんのプログラムは誤判定防止の点では大丈夫みたいですね。 しかし、最後の行が空行や ASCII 文字だけっだりすると だけだったりすると $decoder を 'ascii' にして後で問題になりそうです。 繰り返しますが、「絶対確実な文字コード推測」は存在しません。 それを踏まえて処理を考え直す事をお勧めします。

makoji
質問者

補足

レスをありがとうございます。 鉄則2の方は、データを細切れにした理由は、簡単にファイル全部をスカラー変数に格納するコードを知らなかったからです。Wernerさんのサンプルプログラムの中にそのコードがありましたので、早速活用させていただこうと思います。Wernerさん、ありがとうございました。 >**** Encode::Guess の鉄則 1 >推測候補の文字コードは手動で指定する 問題は鉄則1の方なのですが、実は手動で入力しようかとも思ったのです。今検討中のプログラムは中国語圏で用いる予定で、日本にいる中国人も使う手前、日本語環境のPC、サーバーを用いることも考えると中国語と日本語をサポートしていればそれで十分かと。 日本でよく用いられている文字コードは  shift-JIS、EUC-JP、JIS です。中国語は  GB2312、BIG5・・・ だけでいいのかな? ちょっとよく中国の事情が分からなかったのと、日本語にせよ中国語にせよ厳密にいえば文字コードは多数存在します。その殆んどを無視して構わないのかどうかが分かりませんでした。 挙句の果てに  big5-eten  big5-hkscs などと御親戚みたいな文字コードもあるから、先ほど述べた5つを手動で指定するにも、どの文字コードを指定すればいいのかすぐには分からない始末。 そこでものは試しに全ての文字コードをサポートしたコードを書いてみようと思ったら、ワラワラと誤判定が出てくる大騒ぎになった次第です。 そもそも私に分からないのは、手元の本によると、文字コードは、「通常はそのフォームのあるHTMLテキストの文字コードと同じです。しかしこれは保証されているわけではありません。」とあります。なんで保証されないのでしょう。保証されるなら、Encode.pmを組み込むcgiはUTF-8で書くし、そこにデータを渡すHPもUTF-8で構築するから、文字コードの問題など最初から存在しないわけです。 鉄則1の重要性が認識された今、問題は手動でどの文字コードを指定すれば良いかという、文字コードの選定にシフトするわけですが、その前に、なんでHPともCGIとも関係の無い文字コードがCGIに送り込まれてくるのか、もしご存じだったら教えていただけないでしょうか。 あるいはこの当たりのことが書かれている本とかがあったら教えてくださっても結構です。厚顔無恥のあつかましさで付け加えるならば、読みやすいものが良いです。 パソコン関係で大きなスペースのある本屋と言えば電気量販店などのフロアにあったりしますが、そういう処にある本とは毛並みが違うようで、疑問そのものを答えていただくにせよ、本を紹介していただくにせよ、どこかの大学の先生を頼って行くしかないのかな・・・なんて思っている次第です。

その他の回答 (7)

  • zxcv0000
  • ベストアンサー率56% (111/196)
回答No.8

遅くなりましたが、No.5 補足の前半についてです。 > 今後のことも考えて、テキストファイルのアップロードを前提とした話をさせていただきました。 了解です。 勉強されたい方は歓迎です。 > IMEで変換できる記号などが多くなって... これ、最近増えた文字はマイクロソフトの独自拡張かも知れませんが、Windows-31J と Shift_Jis の差異をマイクロソフトの独自拡張と言うのは実は語弊があります。 差異の中には IBM拡張文字もあって、IBM拡張文字はさらに NEC拡張文字を含んでたりするそうです。 その結果、一部の特殊文字は複数の表現ができたりします。 例えば小文字のローマ数字(i ii vi 等) は 92区にも 115区にもあります。 http://www2d.biglobe.ne.jp/~msyk/charcode/kisyuizon/msgotic.html > Encode::GuessのPODを書く立場からすれば、shift-JISを代表する文字コードとして本家本元のshiftjisを選ぶのは至極ごもっともです。でも実用的に考えて、一部メーカーが独自に拡張した文字コードの方を選択しておけば、 shift-JISに元からあった文字だけでなく、そのメーカーが独自に拡張した文字もフォローできるメリットがあるから、cp932を選ぶべきなのですね。 そのとおりだと思います。 しかし、Shift_Jis の替りに Windows-31J を使用する事には弊害もあります。 SJISでは無いテキストを「SJISにも解釈できる」と判定する危険が増えることです。 ISO-8859-1(latin1) なんかはバイナリ・ファイルですら解釈に成功する事がある位誤判定が多いそうですが、Windows-31J はそれにちょっぴり近い訳です。 だから、半角カナや機種依存文字を含まないと判っている状況では、候補に含めるのは Windows-31J より Shift_Jis の方が良いです。

makoji
質問者

お礼

>それは、 「$guess->decode($adddata)」で出ているのでしょう。 その通りでした。 No.6で  print $decoder->name; とやって出たエラーもこれと同じなんですね。 でも文字コードを唯一に特定出来なかったからといってプログラム処理が中断してしまうのは困りものです。 とりあえず、   my @list_encoding=split('or',$guess); として、@list_encodingを一覧表示する窓を別に開くようにしました。 親の窓がきちんと表示できるまで、その窓で文字コードを手動で切り替えるという寸法です。 永らくどうもありがとうございました。 本当に助かりました。 ありがとうございます。

  • zxcv0000
  • ベストアンサー率56% (111/196)
回答No.7

No.5 です。 > ところで、文字コード判定のコードを >  my $guess = guess_encoding($adddata,qw/cp932 euc-jp iso-2022-jp big5-eten cp936/); > のように書いたところ >  Can't locate object method "decode" via package "cp936 or cp932" (perhaps you fo rgot to load "cp936 or cp932"?) at O:\public_html\test\encode\5.0.cgi line 163. > のようにエラーが出ました。 それは、 「$guess->decode($adddata)」で出ているのでしょう。 guess_encoding() は、推測候補中の複数が該当すればオブジェクトでは無く「cp936 or cp932」の様な文字列を返します。 No.2 の回答の最初の例を見てください。 ref($guess) が false なら、 メソッド($guess->○×△) は使えません。

  • Werner
  • ベストアンサー率53% (395/735)
回答No.6

> foreach文で全ての文字コードを検証したのですから、@dataはutf8に書き換えられているのだし、 > 全ての文字コードでutf8がヒットしてしまい、$check=0となることは最初のヒット以降ないはずです。 あれ? | print ref($decoder) ? $decoder->name : $decoder; の出力結果をみて気づかなかったですか? @dataがutf8に書き換えられた後は、実際に「全ての文字コードでutf8がヒット」しているはずですよ。 そして、 $check=1 となるのは「utf8だけがヒット」したときで、 それ以外の時は $check=0 になります。 なぜなら、guess_encodingは複数の候補にヒットしたときに "cp1026 or utf8" のような文字列を返すからです。

makoji
質問者

お礼

永らくどうもありがとうございました。 本当に助かりました。 お二方にはとても丁寧にご教授いただいたので、「良回答」をどちらに差し上げるか困ってしまいます。でもお一人にしか差し上げられないので、鉛筆転がしで決定させていただきました。 ありがとうございます。

makoji
質問者

補足

私の文法が拙かったんですね  print "decoder: $decoder->name<br>\n"; と書いたら  decoder: Encode::XS=SCALAR(0x3111af4)->name  decoder: Encode::utf8=HASH(0x30ffea4)->name  decoder: Encode::utf8=HASH(0x30ffea4)->name  (続く) となって、文法が間違えているのは分かったんですけど、確かに最初以外はutf8になっているから、それ以上追及しませんでした。 Wernerさんの書かれている通り  print ref($decoder) ? $decoder->name : $decoder;  print "\n"; とやるとこんな感じです。  No appropriate encodings found!  No appropriate encodings found!  No appropriate encodings found!  No appropriate encodings found!  No appropriate encodings found!  No appropriate encodings found!  No appropriate encodings found!  No appropriate encodings found!  cp1006  cp1026 or utf8  cp1047 or utf8  utf8  cp1251 or utf8  utf8  utf8  (続く) Wernerさんがprint文を二つに分けたのには意味があるのだろう、というかこの場合は最初のprint文は三項式になっているからコードを二つに分けざるを得ないのですが、それだけが理由ではないのだろうと思いまして、私のprint文も分けてみました。  print "decoder: "; print $decoder->name; print "<br>\n"; めでたく参照は出なくなりました。 でも表示内容がWernerさんと違う・・・ このprint文は$check=1の文字コードだけ表示したのですが、なるほど$check=1の場合は、本来の文字コードがヒットせず、結果utf8に一意に確定したから$check=1なんですね。本来の文字コードがヒットすると文字コードが一意に決まらずエラーが出て$check=0となってしまう。すると私の記述法では表示されない。 print文の位置を動かして、$checkの値に関係なく全ての文字コードを表示させてみました。すると$check=0ではエラーが出てしまいました。  Can't locate object method "name" via package "No appropriate encodings found!" (perhaps you forgot to load "No appropriate encodings found!"?) at O:\public_html\test\encode\test.cgi line 48. なるほど。Wernerさんが三項式を用いた理由が分かりました。 勉強になります。 ところでcp936に対してでたエラーメッセージとこのエラーメッセージは似てますね・・・

  • zxcv0000
  • ベストアンサー率56% (111/196)
回答No.5

No.4への補足を読みましたが、そういう話の展開になると言うのは、テキストファイルのアップロードもアリという事ですね? > しかし文法的にはどちらも同じなので、書体の違いに慣れれば、書き言葉としてはどちらも同じ言葉と考えてよいとのことです。 いや、文法も字体も、今の話題には無関係です。どういう名前の文字コードを推測候補に加えるかだけの問題です。 台湾人か中国人かも無関係で、閲覧者がアップロードしたファイルの文字コードが何かというだけの問題です。 この話題を、文化的な観点から考えるべきではありません。 閲覧者はどういうセッティングのパソコンをどういう風に使うかで考えましょう。 SJISの混沌には私も閉口します。 SJISの基本形は、JISX0212 等で定められたものです。 これは、業界規格で Shift_Jis と命名されています。 Perlの Encode でも Shift_Jis はこの意味です。 これに独自拡張(半角カナや機種依存文字の追加)を行ったのが マイクロソフトの CodePage932 と マッキントッシュの日本語コードです。 Perlの Encode では、それらを cp932(別名Windows-31J)、MacJapanese という名前で使用可能です。 業界規格では、前者は Windows-31J と命名され、後者は命名されていません。(多分) だから、Shift_Jis は Windows-31J のサブセットです。 Encode::Guess では、マイクロソフトの機種依存文字を含むSJISを Windows-31J と解釈できますが、Shift_Jis とは解釈できません。 これが、私が前回日本語用コードとして Shift_Jis では無く Windows-31J を推薦した根拠です。 SJIS系の代表の意図は無く、正しい Shift_Jis の上位互換で MacJapanese よりも普及しているからです。 話をさらにややこしくしているのはマイクロソフトです。 マイクロソフト製のソフトが Content-Type の charset で Shift_Jis を名乗った場合、業界規格の Shift_Jis では無く Windows-31J を指すのです。 このせいで、理解するのをあきらめた人は数知れないと思います。 中国の中国語コードにも、似た事情があります。 基本は GB2312 です。 それを拡張した国家規格が GBK。 Windowsの中国向け文字コードは CodePage936 で、GBK と同じものらしいです。 これは、Perl で cp936 または GBK の名前で使用できます。 業界規格の名前は GBK(別名 cp936)。 マイクロソフト製のソフトが Content-Type の charset で GB2312 を名乗った場合、業界規格の GB2312 では無く GBK を指すのも日本語コードの事情に似てますね。 P.S. この書き込み中の「業界規格」とは、IANA のことです。 http://www.iana.org/assignments/character-sets 私は、文字コードの名前はなるべく IANA の実名または推奨別名を使う様にしています。 何が実名かが Perl と微妙に違ってますけどね。

makoji
質問者

補足

>No.4への補足を読みましたが、そういう話の展開になると言うのは、 >テキストファイルのアップロードもアリという事ですね? 実は今回はテキストファイルのアップロードはないですけど、でもせっかくここまで色々とご教授いただいていた内容を、いきなり実はCGIに使うんです、で話の腰を折ってしまうのも申し訳ないし、今後のことも考えて、テキストファイルのアップロードを前提とした話をさせていただきました。 >これに独自拡張(半角カナや機種依存文字の追加)を行ったのが >マイクロソフトの CodePage932 と マッキントッシュの日本語コードです。 昔と比べるとIMEで変換できる記号などが多くなって、ワープロを打つ際、とても便利になったと思っておりましたが、こういう裏事情があるのですね。 Encode::GuessのPODを書く立場からすれば、shift-JISを代表する文字コードとして本家本元のshiftjisを選ぶのは至極ごもっともです。でも実用的に考えて、一部メーカーが独自に拡張した文字コードの方を選択しておけば、shift-JISに元からあった文字だけでなく、そのメーカーが独自に拡張した文字もフォローできるメリットがあるから、cp932を選ぶべきなのですね。 ところで、文字コード判定のコードを  my $guess = guess_encoding($adddata,qw/cp932 euc-jp iso-2022-jp big5-eten cp936/); のように書いたところ  Can't locate object method "decode" via package "cp936 or cp932" (perhaps you fo rgot to load "cp936 or cp932"?) at O:\public_html\test\encode\5.0.cgi line 163. のようにエラーが出ました。 cp936が原因でこのエラーが出るようなのですが、前回、全ての文字コードでguess_encoing()を行った際は、cp936ではこのようなエラーは出ませんでした。 何が原因と思われるでしょう。

  • zxcv0000
  • ベストアンサー率56% (111/196)
回答No.4

No.2 です。 なるほど。 CGIとしての使用専用で、しかも入力ページが UTF-8 となると、少し事情が変るかも知れません。 GET にしろ POST にしろ、UTF-8 のページから UTF-8 以外のコードが渡されるというのは、通常は前提にして良いと思います。 もちろん、ブラウザのバグやヘボいマイナーブラウザはあきらめると言う条件は付きますし、アップロードされたテキストファイルも対象外です。 その方針で行くなら、やるべき事は「入力データは UTF-8 として解釈可能か」の確認だけで良くて、 $decoder=guess_encoding($data,'UTF-8'); で充分でしょう。 しかし、アップロードファイルがある場合にはそうも行きません。 とは言うものの、言語を日本語と中国の中国語に限定ならそう大変では無いでしょう。 (台湾の中国語を含むとどうなるかは私は知りません) 日本語向けに Windows-31J、EUC-JP、ISO-2022-JP 中国語向けに GBK 共通向けに UTF-8 位で始めて、いずれにも解釈できなけりゃエラー、要望されたら追加で良いんじゃ無いかと思います。 複数に解釈できる場合に備えてアップロード画面に文字コードの指定もある方が良いでしょう。

makoji
質問者

補足

> 言語を日本語と中国の中国語に限定ならそう大変では無いでしょう。 (台湾の中国語を含むとどうなるかは私は知りません) 中国語は方言が激しいため、話し言葉としては中国本土と台湾ではかなり異なります。加えて中国本土では簡体字が採用されたので、一見、中国本土と台湾では、中国語といってもまるで違う言語であるかのように感じられます。しかし文法的にはどちらも同じなので、書体の違いに慣れれば、書き言葉としてはどちらも同じ言葉と考えてよいとのことです。 そして日本国内にいる中国語圏の外国人は中国本土より台湾の人の方が多いです。中国本土はビザの基準がとても厳しいからです。 このため日本国内で中国語をサポートしたサイトを構築すると、そのサイトが簡体字で構築されていても、実際の利用状況は、中国本土からあるいは中国本土出身者からと同等かそれ以上に台湾からもしくは台湾出身者からの閲覧があると考えられます。 台湾の文字コードで有名なのはBIG5ですが、Encodeがサポートする114種類の文字コードの中にこの呼び名はありません。その代わりbig5-etenという文字コードがあります。wikipediaを見る限りこの文字コードがいわゆるBIG5なのではないかと思います。でも Windows-31Jがcp932と呼称されるように、実は全然違う名前でご本家のBIG5がサポートされていない保証はありません。確認してみようと思います。 > 日本語向けに Windows-31J、EUC-JP、ISO-2022-JP WernerさんによるとWindows-31とはshift-JISはとても似ていてしばしば混同されているとのことですが、shift-JISをご本家shiftjisではなくcp0932で代表させてしまうWindowsパワーに敬服します。 Encode::GuessのPODを読むと、この日本語の3大文字コードはshiftjis、euc-jp、7bit-jisで代表させています。こんな書き方をすると失礼になってしまいますが、この辺りはどう考えるべきでしょう。 > 中国語向けに GBK wikipediaを読む限りcp936のことだと思われますが、これでよろしいでしょうか。 お手数をおかけします。

  • Werner
  • ベストアンサー率53% (395/735)
回答No.3

> 毎回同じ文字化けを起こしてしまうのは何故なのでしょう。 「if($check==1)」の手前あたりにでも | print ref($decoder) ? $decoder->name : $decoder; | print "\n"; と書いてみてください。 1つめ以外は全てUTF8であると判定されていることが分かると思います。 (これはUTF8フラグを立てる以外何もしないdecoderです。) また、判定に失敗しているのは、UTF8かそれ以外のどちらかであるという 推測結果になっているからだと言うことも分かります。 guess_encodingの第2引数には、何も指定しなくても asciiとutf8をデフォルトで指定したのと同じ事になります。 つまり、1つだけをguessの候補にしたつもりが 実はそうなっていなかったという事です。 なので私がNo.1で触れたように、推定候補は全てまとめて渡すべきでしょう。 > 日本でよく用いられている文字コードは >  shift-JIS、EUC-JP、JIS > です。 中国の事情はさすがに知りませんが、 日本においてもShift-JISとそっくりな CP932(Microsoftコードページ932、Windows-31Jとも呼ばれる)という文字コードが存在します。 CP932はWindowsで日本語を扱うために使われてきた文字コードですが これが良くShift_JISと呼ばれているため、 一般にShift_JISと呼ばれている文字コードのほとんどは実際にはCP932だったりします。 Shift_JISとCP932ではUnicodeとのマッピングが異なるため、 両者を混同すると「WAVE DASH - FULLWIDTH TILDE問題」として知られる 文字化けの原因になったりするのですが、 その辺の事情を知らないとどっちを使ったらよいかなんてまず分からないですからね。 なので、日本語の事情でも結構面倒なところはあります。 > なんでHPともCGIとも関係の無い文字コードがCGIに送り込まれてくるのか 私は知らなかったのですが、検索したらこんなのが見つかりました。 (個人的には無視しても良い気もします。 広範なコードポイントを含むUTF8が別の文字コードにされるようなことがもしあれば 情報が欠落してしまうのは防ぎようがないですし。) 予期しない文字コードでPOSTされるケース http://www.shtml.jp/mojibake/mac_post.html 文字コード判定の話に戻りますが、 判定が確実に出来ないのは元の文字列が何か分からないことが大きいので、 隠しテキストボックスにでも適当な(既知となる)文字列を入れておいて 一緒にPOSTされるようにしておけば、 判定精度を高めることは出来ると思います。 (既知文字列を候補文字コードでencodeした結果とPOSTデータを比較してチェックする。)

makoji
質問者

補足

>1つめ以外は全てUTF8であると判定されていることが分かると思います。 ものの見事に最初にヒットした文字コード以外皆UTF-8になっていますね。 でもまだよく分からないんです。 >guess_encodingの第2引数には、何も指定しなくても >asciiとutf8をデフォルトで指定したのと同じ事になります。 >つまり、1つだけをguessの候補にしたつもりが >実はそうなっていなかったという事です。 元のプログラム中のD式   $decoder=guess_encoding($j,$i); は字面的には$iだけを文字コードの候補としていますが、この他にasciiとutf8がデフォルトで候補になっている。 これが本当なら、foreach文で全ての文字コードを検証したのですから、@dataはutf8に書き換えられているのだし、全ての文字コードでutf8がヒットしてしまい、$check=0となることは最初のヒット以降ないはずです。 でも実際には、最初のヒット以降も$check=0となる文字コードは出現していますから、guess_encoding()の第2引数を指定した場合、デフォルトのascii、utf8は候補から外れているのではないかと思えます。 その一方でascii、utf8が候補から外れているなら$decoderをprintした時にutf8が書かれるというのはおかしいです。 なんか釈然としません。 >隠しテキストボックスにでも適当な(既知となる)文字列を入れておいて >一緒にPOSTされるようにしておけば、 >判定精度を高めることは出来ると思います。 良いアイデアですね。ありがとうございます。

  • Werner
  • ベストアンサー率53% (395/735)
回答No.1

○うまくいかない直接の原因 > foreach my $j (@data) { >   $j=$decoder->decode($j); >   $j=encode('utf-8', $j); >   print "$j<br>\n"; > } ここで@dataを書き換えてしまっているので、 2回目以降のdecodeが全て失敗しているのだと思います。 なぜ、@dataが書き換わるのか分からなければ、以下のスクリプトを試して見れば分かります。 #--ここから-- my @data = (0,1,2,3,4,5,6,7,8,9); print join(",", @data) . "\n"; #=> 0,1,2,3,4,5,6,7,8,9 foreach my $j (@data) {  $j = $j + 10; } print join(",", @data) . "\n"; #=> 10,11,12,13,14,15,16,17,18,19 #--ここまで-- ○他の余り良くない点 > foreach my $j (@data) { >  $decoder=guess_encoding($j,$i); >  $check=0 unless ref($decoder); > } データを細切れにしてguess_encodingに渡すのは 判定精度が落ちるので良くありません。 guess_encodingには出来るだけ多くのデータを、 例えば今回の場合はファイルの内容を全て渡した方が良いでしょう。 もっとも、このコードだと1行でもguess出来なければ失敗としているので 精度に関しては問題ないかもしれません。 しかし、ループする分処理効率が落ちますし、 あまり良いことはないと思います。 また、guess_encodingの第二引数には、エンコーディング名のリストを渡せるので @all_encodingsを渡してしまって良いと思います。 #----------Sample---------- use utf8; #このスクリプトはUTF-8で記述されている。 use strict; use warnings; use Encode; use Encode::Guess; binmode(STDOUT, ":utf8"); # STDOUTに出力するとき、PerlIOレイヤで透過的にUTF-8に変換 # 全てのエンコード名取得 my @all_encodings = Encode->encodings(":all"); # BOMが必要な文字コードを除外 @all_encodings = grep {$_ ne "UTF-16" and $_ ne "UTF-32"} @all_encodings; # ファイルの内容を全て$dataに格納 my $fname="shift-JIS.txt"; open(my $in, "<", $fname); my $data = do {local $/ = undef; <$in> }; close($in); # 文字コードを判定 my $guess = guess_encoding($data, @all_encodings); my @guess_list = ref $guess ? ($guess->name) : split(" or ", $guess); # UTF-8で出力 foreach my $enc_name (@guess_list) {  my $decoder = find_encoding($enc_name);  my $decoded_data = $decoder->decode($data);  $decoded_data =~ s/\n/<br>\n/g;  print "[$enc_name]<br>\n";  print $decoded_data;  print "\n"; }

makoji
質問者

補足

わざわざサンプルプログラムまで組んでくださってありがとうございます。 短いプログラムにも関わらず、私が知らないコードが幾つか入っていて、昨日からフォローのために本を読んだり、とても勉強になりました。 zxcv0000さんも指摘されていますが、ファイルを細切れにするのは問題があるとは思っていました。スマートなファイルダンプの方法を知らなかったので、とりあえず後回しにしていたのですが、知っている方が書かれると、とても簡単ですね。 同じ文字化けが何度も出てきたのでとまどってしまいました。確かにファイルデータを取り込んだ@dataを最初のループで書き換えてしまうと、2回目以降は元のファイルデータで処理を行うことが出来ませんね。これはEncode.pmがどうこう以前のバグです。失礼いたしました。 ちょっと分からないのが、前回私のプログラムを作動させた際に、@dataに最初の書き込みを行った文字コードはcp1006であるようですが、この時Perlは@dataをcp1006で書かれていると判断してそれをutf8に変換しています。その次にcp1025で同様な作業をしているわけですが、一旦utf8に変換したデータをcp1025と判定してもう一度utf8に再変換をかけることになりますから、@dataは  $decoder=guess_encoding($j,$i); が、「この文字コードが使われています」と判断する度に書き換えられて違う値を取るようになってしまうのではないでしょうか。 毎回同じ文字化けを起こしてしまうのは何故なのでしょう。

関連するQ&A

  • デコード処理について

    いつもお世話になっております。 Perlのデコード処理で分からない事があります。 大変申し訳ございませんが、 ご存知の方がいらっしゃれば、教えて頂けますでしょうか。 以下のプログラムを実施すると以下のエラーが発生しまい 正しくデコードされた結果が表示されません。 この場合、どのようにして$sの文字をUTF-8と判断させて shiftjisに変換すればよいのでしょうか? (プログラム) #!/usr/local/bin/perl use Encode qw/from_to/; use Encode::Guess; Encode::Guess->set_suspects(qw/shift-jis euc-jp 7bit-jis/); $s = '%E7%A7%BB%E8%BB%A2'; # UTF-8 $s =~ tr/+/ /; $s =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("H2", $1)/eg; my $decoder = Encode::Guess->guess($s); die $decoder unless (ref($decoder)); &from_to( $s, $decoder->name, "shiftjis" ); print $s; (エラー内容) shiftjis or euc-jp or utf8 at test.pl line 12.

  • WWW::Mechanizeの文字コードについて質問

    WWW::Mechanizeの文字コードについて質問 以下のようなソースコードを書いて実行してみたのですが どのサイトを取得しても、すべて文字コードがUTF8だと認識されます。 ------------------------------------------------------- #!/usr/bin/perl #WWW:MechanizeでHTMLの取得 use WWW::Mechanize; $w = WWW::Mechanize->new(); $w->get("URL"); $html=$w->content; #文字コードの判別 use Encode; use Encode::Guess qw/ascii utf8 euc-jp shiftjis 7bit-jis/; my $dec = Encode::Guess->guess($html); print$dec->name; ------------------------------------------------------- 例えば以下のサイトは文字コードがEUC-JPですがこれもprintされるのはUTF8となってしまいます。 http://barukanlog.blog31.fc2.com/blog-entry-541.html WWW:Mechanizeでサイトを取得し、サイトの文字コードを調べてすべてsjisにする ということがしたいのですが、すべてutf8に判断されて先へ進めないんです。 何か設定やメソッドを追加しなくてはいけないのでしょうか? わかりにくい質問かとは思いますがご回答お願いします。

  • Perl 文字コードについて

    From: $from To: $mailto CC: $mailcc Subject: $subject Mime-Version: 1.0 Content-type: text/plain;charset=\"UTF-8\" Content-Transfer-Encoding: 8bit この時点で、UTF-8を指定しました。 use Encode; use Encode::Guess; Encode::Guess->set_suspects( qw/ euc-jp shiftjis 7bit-jis / ); $name = encode('UTF-8', decode('Guess', $name)); で本文を、UTF-8に変換して送るようなコードを書きました。 これで一応、パソコン、スマホ共に「本文」は文字化けせず送れるのですが、 今度、別の問題が発生してしまったようで、 「件名」が文字化けしてしまうようになりました。 $subject = encode('UTF-8', decode('cp932', $subject)); 件名も、本文と同じように変換コードをしてみたところ、パソコンでは文字化けしませんでしたが「件名」で文字化けしてしまいます。 調べたところ本文と件名では、内容が違い MIMEエンコードを使用するとのことだったのですが、 実際どのように使うのかわからないです。 $subject = encode('ISO-2022-JP', decode('cp932', $subject)); encode('MIME-Header-ISO_2022_JP', $subject) このように記述するとスマホでは文字化けしないのですが パソコンで文字化けしてしまいます。 そこで、件名がパソコンとスマホで文字化けしないようにし、本文はUTF-8で送るようにするにはどのようにすればいいのでしょうか?

    • ベストアンサー
    • Perl
  • JcodeモジュールとEncodeモジュール

    以下、Perl5.8でJcodeモジュールを使った場合とEncodeモジュールを使った場合の違いについて、知りたいです。 ※そもそもこのモジュールは同時に使ったらだめなのでしょうか? use strict; use utf8; use Jcode; use Encode; my $dat1 = "あイ卯(1)Iⅰ"; Jcode::convert(\$dat1, "utf8"); my $dat2 = "あイ卯(1)Iⅰ"; $dat2 = Encode::encode("utf8", $dat2); 文字コード変換の正しい使い方が知りたいです。

    • ベストアンサー
    • Perl
  • Encode と encoding の同時使用で ISO-2022-JP に encode できない

    CentOS を 5.1 から 5.2 にアップデートした頃から PerlCGI からのメール送信が出来なくなって、調べていたら「ISO-2022-JP への encode がおかいぞ問題」に辿り着きました。 以下のコードで、euc-jp が吐かれてしまいます。 #! /usr/bin/perl -w use encoding('UTF8'); use Encode; binmode(STDOUT); my $text = "<全角文字ですよぉ。>"; print encode('ISO-2022-JP', $text), "\n"; 以下のいずれかで正常に jisコードを吐く様になるのですが、こんなものなんでしょうか? 1 「use encoding('UTF8');」 を 「use utf8;」に替える 2 print の直前に "no encoding;" を入れる CentOS 5.1 では多分正常に ISO-2022-JP への変換ができていたのだと思います。 私の使用するバージョンの Cygwin の Perl でも正常です。 問題のある CentOS5.2 と 問題の無い Cygwin版で、関係しそうなバージョンの違いはありません。 CentOS 5.2: Perl 5.008008 Encode 2.12 Encode::JP 2.01 encoding 2.02 Cygwin: CYGWIN_NT-5.1 **** 1.5.25(0.156/4/2) 2008-04-17 12:11 i686 Cygwin Perl 5.008008 Encode 2.12 Encode::JP 2.01 encoding 2.02 できれば、すでに動いているCGIの use encoding('UTF8'); を直す事なく動く様にしたいのです。

  • 文字コードの変換(Shift-JISからUTF8)

    文字コードがShift-JISのCSVファイルを読み込み、UTF-8のテキストファイルに出力するのに プログラムの中で変更しようとしているのですが、うまくいきません。出力ファイルの文字コードを 確認するとShift-JISのままです。 どなたか教えていただけないでしょうか? ActivePerl v5.16.0を使用し、Encodeモジュールのfrom_toを使用しています。 #!/usr/bin/perl use strict; use warnings; use utf8; use Encode; my $input_file="input.csv"; my $output_file="output.txt"; open (IN, $input_file) or die "$!"; open (OUT, ">$output_file") or die "$!"; while (<IN>){ chomp ($_); my @data=split(/,/,$_); for(my $i=0;$i<@data;$i++){ $data[$i]=Encode::from_to($data[$i],'shiftjis','utf8'); #Shift-JISからUTF-8に変換 $data[$i]=~s/\s+//g; print OUT $_; } print OUT "\n"; } close (IN); close (OUT);

    • ベストアンサー
    • Perl
  • Perlの文字コード変換についての質問です。

    Perlの文字コード変換についての質問です。 ホームページ全体は、UTF-8で作成されています。 そのため、$qsは、どうも、S-JISのようなので、UTF-8に変換して URLデコードさせたいのですがうまくいきません。 文字化けしないで、UTF-8で作成されたページに表示させたいのですがどうすればよいでしょうか? 宜しくお願い致します。 ------------------------------------ $qs = $ENV{'QUERY_STRING'}; use Encode::Guess qw/ shiftjis /; use Encode qw/ decode /; $enc = guess_encoding ( $qs ); if ( ref $enc ) { $utf8 = decode ( $enc->name , $qs ); } $qs =~ tr/+/ /; $qs =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('H2', $1)/eg; print "$qs";

    • ベストアンサー
    • Perl
  • Encodeについて

    いつもお世話になっております。 下記の構文で分からないところがございます。 use Encode; use Encode::Guess qw/euc-jp shiftjis 7bit-jis/; use Encode qw/decode/; $enc=guess_encoding($x); if(ref $enc){$x=decode($enc->name,$x);} 実はあるテキストに載っていたコードなのですが、解説には 文字データのコードが分からない場合は、Encode::Guessを使います としか書いてありません。 2行目は、文字コードのリストをqwで囲んであると分かりますが 3行目は、なぜdecodeをqwで囲む必要があるのでしょうか。 decodeメソッドを使うと意味だとすると、必要ないように思ってしまい ました。大きな勘違いをしているかもしれません。 最後の2行は、文字コードを推測して、そのあとが分かりません。 いつも初心者質問で申し訳ありませんが、よろしくお願いいたします。

  • [Perl]Shift-JISのXMLを解析する場

    行き詰まってしまったので教えて下さい。 <やりたいこと> とあるAPIからXMLファイルを取得し、解析して出力する、ということをやっているのですが、元のXMLがShift-JISでエンコーディングされており、これをUTF-8に変換して出力しようとしています。 <問題> XMLを取得して解析、取り出したいパラメータが出力できるようにはなったのですが、文字のエンコーディングが上手く行っていないためか、文字化けしてしまいます。 <元のXML> <?xml version="1.0" encoding="Shift_JIS"?>  <test>   <prod count=3>    <record>     <code>アイウエ</code>    </record>    <record>     <code>カキクケ-</code>    </record>    <record>     <code>ABC</code>    </record>   </prod>  </test> <XML解析用のコード> #!usr/bin/perl use utf8; use Encode qw/ from_to encode decode /; use Encode::Guess qw/ euc-jp shiftjis 7bit-jis /; use LWP::UserAgent; use XML::Simple; use Data::Dumper; #--XML取得部分省略 #--XMLはgetで$xmlに格納 $from = guess_encoding($xml)->name; &from_to($xml,$from,"utf8"); $XML::Simple::PREFFERRED_PARSER = 'XML::SAX::PurePerl'; $xs = new XML::Simple(); $ref = $xs->XMLin($xml); $xml =~ s/<\?.*\?>//; for($i=0;$i<=$#{$ref->{'test'}->{'prod'}->{'record'}};$i++){  $name = $ref->{'test'}->{'prod'}->{'record'}[$i]->{'code'}; $name = encode('utf-8',$name); print "$i : $name\n"; } <結果> 黒ダイヤに?文字で文字化けして出力される。 どなたか原因がお分かりになりますでしょうか。 よろしくお願いいたします。

  • UTF-8で書かれたHTMLファイルをShift-JISのファイルに変換できない

    #!/usr/bin/perl -w =begin comment OS: Windows XP Perl: Active Perl v5.8.8 スクリプトは「Shift-JIS」で書いています。 日本語処理関係で参考にしているのはもっぱらオライリージャパンの「Spidering Hacks」の付録の翻訳者 による日本語処理の解説です。 http://oshiete1.goo.ne.jp/qa3716434.html の回答に従い、use encoding 'shiftjis'; から use encoding 'cp932'; へ変更している以外は そこに書かれているやり方に従っていると思います。 UTF-8で書かれたHTMLファイルを「LWP::UserAgent」で取得し、それを Shift-JISコードで出力したいと思い以下のコードを実行したのですが、 以下のエラーが出てしまいました。 Parsing of undecoded UTF-8 will give garbage when decoding entities at C:/usr/local/site/lib/LWP/Protocol.pm line 114. このエラーは何が原因なのでしょうか? =end comment =cut use strict; use LWP 5.64; use Encode; use encoding 'cp932'; # http://oshiete1.goo.ne.jp/qa3716434.html の回答に従い、'shiftjis'から'cp932'へ変更。 #use encoding 'shiftjis'; binmode(STDERR, ':raw :encoding(shiftjis)'); my $url = "http://www.audiounion.jp/bin/products/used/A0/-/-/"; my $browser = LWP::UserAgent->new; my $response = $browser->get( $url ); die "cannot get $url:", $response->status_line unless $response->is_success; my $content = Encode::decode('utf8', $response->content); print $content;

専門家に質問してみよう