トップページ > com/net > [底]Perl で UTF-16 のファイルを読む

[底]Perl で UTF-16 のファイルを読む

 仕事で使う UTF-16 のファイルを Perl で処理するスクリプトを書き直していたのですが、文字コードの絡みで引っかかったことがあったのでメモしておきます。

 入力ファイルは SGML 形式で、文字コードが BOM 付き UTF-16 のリトルエンディアンと Shift_JIS です。出力形式は2通り考えていて、1つは UTF-8 のテキスト、もう1つは入力ファイルと同じ UTF-16 リトルエンディアンの SGML。このうち、今回は前者のみ実現できました。

 最初に引っかかったのがファイルの入力に使うエンコード処理をどうするかで、元々は以下のようになっていました。

use utf8;
use strict;
use Encode;
use Encode::Guess qw/ shiftjis /;
my $in = <>; #入力ファイルはスクリプトと別に手入力
open IN, '<:bytes', "$in" or die; #(Ⅰ) ファイルオープン while ($lineSot = <IN>) { my $decorder = Encode::Guess->guess($lineSot); #(Ⅱ) 文字コードを確認
if (ref($decorder)) {
$line = $decorder->decode($lineSot); #(Ⅲ) デコード
} else {
$decorder = find_encoding("utf8");
$line = $decorder->decode($lineSot); #(Ⅲ)UTF-8 としてデコード
}
#行ごとの処理
}
close IN;

 ファイルを開く時に (Ⅰ) バイト列として開いて、行ごとに (Ⅱ) その行の文字コードを確認し、(Ⅲ) Perl の内部コードにデコードしてから処理するという手段を取っていました。
 が、Encode::Guess ではなぜか UTF-16 をうまく判断してくれなかったため、元ファイルを一度エディターで開いて UTF-8 に保存し直してスクリプトに入力、というということを毎回やっていました。
 が、さすがに面倒過ぎたので改修した結果が以下のとおり。

use utf8;
use strict;
use Encode;
use Encode::Guess qw/ shiftjis /;
$in = <>; #入力ファイルはスクリプトと別に手入力
open TEST, '<:bytes', "$in" or die; #(1) 入力値をバイト列としてファイルを開く
my $le = 0;
my $testline = <TEST>;
$testline = unpack "H4", $testline; #(2) 先頭の4バイトを16進数の文字列として取得
$le = 1 if ($testline =~ m!fffe!); #(3) 先頭の4バイトが FFFE だったらフラグを1にする
close TEST;
if ($le) {
open IN, '<:encoding(utf16le)', "$in" or die; #(4) フラグが1なら UTF-16 で開く
} else {
open IN, '<:encoding(shiftjis)', "$in" or die; #(4) フラグが0なら Shift_JIS で開く
}
while ($line = <EXP>) {
$line =~ s!(\x0d\x0a|\x\d|\x\a)!\n!g; #(5) 改行コードを全部 \n に変換
#行ごとの処理
}
close IN;

 入力文字コードが2種類のどちらかなので、ループごとにいちいち確認するのは止めました。
 先頭の4バイトが FFFE であれば UTF-16、それ以外であれば Shift_JIS ということは確定なので、同じファイルをまず (1) バイト列として開いて、(2) BOM の有無で (3) フラグを立て、そのフラグに沿って (4) エンコード付きで開き直しています。
 元のコードのように Guess を使うと失敗しますが、encoding(utf16le) を指定すれば open でも decode でも適切にエンコードできますから。
 最初にコードを書いた時は先頭にあるはずの BOM を確認するという発想がなかったんですよね……。今考えれば不思議。

 もう1つは改行コードです。Shift_JIS や UTF-8 のファイルを入力していた時は特に考えもせずに「\n」で処理すればよかったんですが、UTF-16 の場合は「\n」が CRLF ではなく LF のみになってしまい、chomp でもどちらかしか削れないという事態が発生しました。
 最初は改行がほぼ倍になってしまうため、(5) の改行コードの変換のところで chomp $line; chop $line; とやっていたんですが(chomp 2回では何故か出力ファイルが生成されない)、行によっては chop の方で必要な文字が削除されてしまうことがしばらくしてから発覚。慌てて Web を探し歩いた結果、CR と LF を 16 進数で指定して \n に変換するという手法を発見して事なきを得たのでした。

 このうち、一番わからなかったのが改行コード。chomp→chop の部分をどうにか chomp→chomp にできないかとしばらくいじったのですが、chop を外すと途端に出力ファイルがまったく生成できなくなるのです。が、確認しようと思って $line をすべて表示させたりログファイルに書き出したりしても、特に不審な点が見当たらず……。
 最終的に (5) のコードで解決できたのでいいのですが、chop を消しただけで出力ファイルが0バイトになってた理由は未だにわかっていません。

 さて、上記のコードで UTF-8 に出力することはできるようになったのですが、UTF-16 の SGML、つまり元の形式を維持したまま出力することは未だにできていません。正確には、行ごとのループで文字列をほとんどいじらずに素通しするようなコードではきちんと出せるのですが、SGML のタグを一旦外して変数に格納し、出力のコードでまたタグを付け直して出力するとうまくいかないのです。
 こちらについては、たぶん SGML 中に含まれている変な文字(バイナリエディターで見ればわかると思うのですが、テキストエディター上では常に文字化けしている文字が使われているのです)が原因なので、たぶんもう少し頑張ればバラして組み直すコードでもうまく出力できるようになると思います。
 元の形式を維持して出力ができると、フィルタリングができたりしてもう少し便利になるので、暇を見つけてさらに改造していきます。

コメント:0

コメントフォーム
この情報を記録しますか?

トラックバック:0

この記事のトラックバック URL
http://third.system.cx/wordpress/wp-trackback.php?p=210
トラックバック元のサイトへのリンク
[底]Perl で UTF-16 のファイルを読む from the third place -5th take-

トップページ > com/net > [底]Perl で UTF-16 のファイルを読む

サイト内検索
紹介

管理人:春屋アロヅ(alohz)
連絡先:alohzhgmail.com
詳細、サイトについては諸々の紹介から。

最近の記事
カテゴリー
昔の記事
外部コンテンツ

ページの先頭に戻る