- ベストアンサー
テキストファイルを読み込み出力で文字化け
まだパールを始めたばかりの初心者ですがよろしくお願いします。 現在cgiをPerlで記述します。 「|」区切りのテキストファイルを行ごとに読み込みsplitを利用して項目別に区切っています。 そしてそれをhtmlで項目別にテーブルに表示するというのをやっているのですがテキストファイル中に「鋼」という文字があると「・br>」という表示になり勝手にそこで区切られて以降違う項目になってしまいます。 それ以外はうまく表示出来ますし前後にスペースやメタ文字があったりとかではなく「鋼」を消すとうまく表示出来ます。 処理はこんな感じです。 ~テーブル内部~ open(IN, a.txt); @getline = <IN>; foreach $linedata (@getline) { chop $linedata; (@importdata)=split /\|/, $linedata; print "<tr>"; foreach(@importdata){ $_=~ s/\n//g; $_=~ s/\r//g; print "<td>".$_."<br></td>"; } print "</tr>"; 解決法方が分かる方、教えていただけないでしょうか? よろしくお願いします。
- みんなの回答 (5)
- 専門家の回答
質問者が選んだベストアンサー
やりかたはいろいろあると思いますが、とりあえず解りやすいものとして、 以下サンプルです。 ------------------------------- # SJIS文字 $character_sjis = "(?:[\x00-\x7F\xA1-\xDF]|(?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC]))"; foreach (@getline) { tr/\x0D\x0A//d; $_ .= '|'; print "<tr>\n", (map "<td>$_</td>\n", /\G($character_sjis*?)\|/g), "</tr>\n"; } ------------------------------- 前後に print "<table>\n"; と print "</table>\n"; があり、@getlineに全行が読み込まれている、という前提です。 また、質問文の改行処理をちょっと直してあります(後で全ての改行を削除しているのでchopは不要、また改行文字の削除は「tr/\x0D\x0A//d」が良い)。 簡単に説明しますと、まず「SJISの1文字」を正しく認識させる正規表現を$character_sjisにセットしておきます。 各項目の分割方法は、 【条件】 「SJISの1文字」が“0文字以上”続いた後に「|」があるもの という部分を探します。この条件にあったものを1つの項目と見なします。 しかしこれでは、一番右の項目だけは、「後に「|」があるもの」という条件を満たせませんから、まず探す前に末尾に「|」を付けています。 そして探すとき、とても重要なことが2つあります。 1.必ず先頭から探し始めなければいけません。なぜなら、たとえば「鋼」という文字の後半部分は 7C ですから、この部分だけで「SJISの1文字」という条件を満たしてしまいます(同じ問題を含む文字が他にもあります)。 ちゃんと前半部分も見る、そのためには先頭から順番に探していかなくてはなりません。それをやるのが「\G」です。 2.上記の【条件】では、1行全体が条件を満たしてしまいます。つまり、 「項目A|項目B|項目C|」という文字列“全体”が、条件を満たしています。間にいくつか「|」が入っているのに、です。 そこで、最短マッチを使います。条件を満たす「最短」のものを探す、という指定です。それをやるのが「*?」です($character_sjisを、“「|」以外のSJIS”という定義に変える方法もありますが、汎用性を考えるとこのほうがいいと思います)。 サンプルでは、分割してtdタグで囲んで出力、というのを1行に書いてしまっていますが、たとえば画像も表示するなどの場合は、わけて書いた方が読みやすいかもしれません。 ------------------------------- @cells = /\G($character_sjis*?)\|/g; print "<tr>\n"; foreach (@cells) { print "<td>$_</td>\n"; } print "</tr>\n"; ------------------------------- ******************************************* moon_piyoさんの方法について、大変恐縮なのですが、ちょっと気になる点があります。私が間違っていたら申し訳ありません。 1.書かれた正規表現は、 --------------------- A: SJISの2バイト文字である または、 B: 「|」以外の文字である --------------------- が連続している部分を探していると解釈したのですが、これではBがすでにAも含んでいて、Aが意味をなさないように思えるのですが…。つまり、 /((?:[^\|])+|)\|/g; と書いたのと同じなのでは…。 試してみたところ、一番右に「鋼」があると正しくマッチしませんでした。 2.上記Aの「SJISの2バイト文字である」という部分、「|」の前が1バイト文字だった場合はマッチしないのでは。 3.「鋼」という文字は「[^\|]」があるためマッチしなくなってしまうのでは。 4.一番右の項目がマッチしないのでは。
その他の回答 (4)
- taseki
- ベストアンサー率66% (155/233)
> 最後の「\|」を取れば~ > こんな感じになります。 > 項目1||項目3|項目4||項目6 > が > ('項目1','','','項目3','項目4','','','項目6') > と格納されます。 試していませんが、実際には以下のように格納されていませんか? “項目1”, “”, “”, “項目3”, “”, “項目4”, “”, “”, “項目6”, “” すべての項目の部分と、「何もないところ」もマッチするためだと思います。
- moon_piyo
- ベストアンサー率60% (88/146)
ANo.1のものです 空文字の正規表現= ←なにもない(長さ0)です それから 項目1||項目3|項目4||項目6| と項目のすぐあとに|がついているようですので (Shift-JIS定義文字 又は |以外の文字 又は 空文字)が1回以上続き それに|が続く、という正規表現ではどうでしょうか(■の部分を に置き換えて 最後に\|をつけました) (@importdata) = $linedata =~ /((?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC]|[^\|]|)+)\|/g; 同様に (@importdata) = $linedata =~ /((?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC]|[^\|])+|)\|/g; あるいは (@importdata) = $linedata =~ /((?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC]|[^\|])*)\|/g; とか...
お礼
回答ありがとうございます。 「何も無い」というのに何時間も悩まされており助かりました。 本当に申し訳ないのですが実は…今もう一度確認した所一番最後の項目だけ|が最後についていなかったのです。(度々すみません) 当たり前ですがこの場合だとヒットしないので最後の項目が配列に入りません。 最後の「\|」を取ればShift-JIS定義文字 又は |以外の文字 又は 空文字が1回以上続くになると思って実行すると空文字の所が2度ヒットしてる(?)のか一個の空文字で2つの空の要素が格納されてしまいます。 こんな感じになります。 項目1||項目3|項目4||項目6 が ('項目1','','','項目3','項目4','','','項目6') と格納されます。 私の考え方が甘すぎるのでしょうか。 親切の教えていただいてるのに何度もすみません。 よろしくお願いします。
補足
何度もサンプルをありがとうございました。 本当に勉強になったと思います。
- taseki
- ベストアンサー率66% (155/233)
まずは、なぜ元のスクリプトではおかしなところで区切られて、ANo.1の方の方法でうまく区切られたのかを理解すれば、自ずと答えは見えてくると思います。 「鋼」という文字は、Shift-JISで 8D7C です。そして「 | 」という文字は 7C です。 8D7C の後半部分を区切り文字と認識してしまった、ということです。 そしてANo.1の方の方法は、Shift-JIS定義文字に続く「 | 」、という判断にしたので、正しく区切られました。 この「Shift-JIS定義文字に続く~」という部分を、「Shift-JIS定義文字があるか、あるいは何もない、に続く~」というようにすればいいことになります。 perlで日本語を扱う場合は注意が必要です。以下が役に立つと思います(CSV区切りの方法も載っています)。 http://www.din.or.jp/~ohzaki/perl.htm
お礼
回答ありがとうございます。 正規表現がまだ知識が皆無の状態でしたのでtasekiさんに回答いただいた後、参考ページ等色々勉強しにいったのですがうまく出来ません。 /((?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC]|[^\|])+)/g; 私の勉強不足かもしれませんがShift-JIS定義文字又は|以外の文字が一回以上続くものというのは分かりましたが(間違っていたらすみません)空文字というのはどう表したらいいのでしょうか。 またイメージとしては /((?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC]|[^\|]|■)+)/g; の■に空文字を表すものを書けばいいのでしょうか? それとも /((?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC]|[^\|])+|■)/g; でしょうか? もの分かり悪くてすみません。
- moon_piyo
- ベストアンサー率60% (88/146)
こんちは まずは下記のように変更してみてどうでしょう.. (@importdata)=split /\|/, $linedata; ↓ (@importdata) = $linedata =~ /((?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC]|[^\|])+)/g;
お礼
回答ありがとうございます。 教えていただいた方法で表示出来ました。 本当にありがとうございます。 Shift-Jisが問題だったのでしょうか? 正規表現はまだ詳しく分からないのでこれから勉強していきたいと思います。
補足
お礼を言った後申し訳ないのですがもう一度確認すると項目が無い場合配列には空のデータを無視して次のデータが入っている状態でした。 例えばテキストファイル内で 項目1||項目3|項目4||項目6| となっていると配列には ('項目1','','項目3','項目4','','項目6') と空も入って欲しいのです。 教えていただいたのでは ('項目1','項目3','項目4','項目6') となってしまいます。 もう一度お願いできないでしょうか? よろしくお願いします
お礼
回答ありがとうございます。 このテキストファイル実は厄介でして何故行単位で処理していたのかというと1つのデータの項目が6行ありまして、1行目は「,」区切りで他5行は「|」区切りというものなのです。(しかも一行目の内容によっては下5行が無いパターンも存在するというものでして手を焼いておりました) tasekiさんのサンプルを参考にして何とか希望通りの表示が出来ました。本当にありがとうございます。 配列の中身はおっしゃる通りでした。