PerlのXML::LibXMLモジュールでShift_JISのXMLのパース
PerlのXMLパーサーモジュール、XML::LibXMLで文字コードShift_JISのXMLをパースしようとしてしばらくハマったので将来の自分用にメモを残しておきます。
$doc = $parser->parse_file(〜)でエラー
XML::LibXMLは$doc = $parser->parse_file(〜)の書式で外部XMLファイルのパスを指定して直接読み込めるのですが、どうもうまくいきません。
用意した読み込み元のXMLは次のような感じ。
1 2 3 4 5 6 7 8 9 10 |
<?xml version="1.0" encoding="Shift_JIS"?> <TEST> <CONTENTS> <PARAGRAPH>吾輩は猫である。名前はまだ無い。</PARAGRAPH> <PARAGRAPH>どこで生れたかとんと見当がつかぬ。</PARAGRAPH> <PARAGRAPH>何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。</PARAGRAPH> <PARAGRAPH>吾輩はここで始めて人間というものを見た。</PARAGRAPH> <PARAGRAPH>しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。</PARAGRAPH> </CONTENTS> </TEST> |
これを以下のコードでパースしようとして
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#!/usr/bin/perl use utf8; use warnings; use XML::LibXML; use Encode qw/encode decode/; #変換するXMLファイルのパスを取得 my $convertXmlFilePath = $ARGV[0]; $convertXmlFilePath = decode('UTF-8', $convertXmlFilePath); #ファイルを読み込んでパース my $parser = XML::LibXML->new(); $parser->no_network(1); my $dom = $parser->parse_file($convertXmlFilePath); #<TEST>タグの中身取得 my $tags = $dom->findnodes('///TEST'); #テキスト化して配列収納 my @eachLines; foreach my $tag(@$tags){ push (@eachLines, $tag->serialize); } #テスト出力 my $joinedTxt = join("\x0A",@eachLines); $joinedTxt = encode('UTF-8', $joinedTxt); print $joinedTxt . "\n"; |
以下のようなエラーが返ります。
読み込み元のXMLの文字コード(と宣言文)をUTF-8に変えてやれば普通に読み込めるので、Shift_JIS由来の問題に間違いないようです。どうもXML::LibXMLの$doc = $parser->parse_file(〜)がShift_JISに対応していないのが原因のよう。
$doc = $parser->parse_string(〜)でもエラーになる
困ったなということでネットでいろいろ情報を集めたのですが、use utf8;を宣言していない例とかしか引っかからなくて困りました。Perlの内部コードをShit_JISにしてやりゃそりゃ読めるでしょうが、Unicodeにしかない文字とか扱う可能性があるのでそれじゃダメなのよ。
ということで作戦2として、一旦encodeモジュールを使って内部文字列として読み込んでやり、それを$doc = $parser->parse_string(〜)でパースしてみます。コードは以下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#!/usr/bin/perl use utf8; use warnings; use XML::LibXML; use Encode qw/encode decode/; #変換するXMLファイルのパスを取得 my $convertXmlFilePath = $ARGV[0]; $convertXmlFilePath = decode('UTF-8', $convertXmlFilePath); #内部文字列として一度展開 open(IN,"$convertXmlFilePath"); @eachLineTxts = <IN>; $xmlTxt = join("",@eachLineTxts); $xmlTxt = decode('Shift_JIS', $xmlTxt); close (IN); #ファイルを読み込んでパース my $parser = XML::LibXML->new(); $parser->no_network(1); my $dom = $parser->parse_string($xmlTxt); #<TEST>タグの中身取得 my $tags = $dom->findnodes('///TEST'); #テキスト化して配列収納 my @eachLines; foreach my $tag(@$tags){ push (@eachLines, $tag->serialize); } #テスト出力 my $joinedTxt = join("\x0A",@eachLines); $joinedTxt = encode('UTF-8', $joinedTxt); print $joinedTxt . "\n"; |
しかしこれでもエラー。
$doc = $parser->読み込んだ文字列内の文字コード宣言の部分を置換して読み込ませて解決
どうしたものかなとしばらくいろいろ($dom = XML::LibXML->load_xml();方面とか)試していたのですがうまくいかず。
もう一度エラー内容とコードを眺めていたら、もしかして読み込みXMLソース内の「encoding="Shift_JIS"」の宣言がイタズラしてるのでは?と思い、一行追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#!/usr/bin/perl use utf8; use warnings; use XML::LibXML; #use XML::LibXML::XPathContext; use Encode qw/encode decode/; #変換するXMLファイルのパスを取得 my $convertXmlFilePath = $ARGV[0]; $convertXmlFilePath = decode('UTF-8', $convertXmlFilePath); #内部文字列として一度展開 open(IN,"$convertXmlFilePath"); @eachLineTxts = <IN>; $xmlTxt = join("",@eachLineTxts); $xmlTxt = decode('Shift_JIS', $xmlTxt); close (IN); #エンコーディング宣言の部分を置換(↓この行を追記) $xmlTxt =~ s@encoding=\"Shift_JIS\"@encoding=\"UTF-8\"@; #ファイルを読み込んでパース my $parser = XML::LibXML->new(); $parser->no_network(1); my $dom = $parser->parse_string($xmlTxt); #<TEST>タグの中身取得 my $tags = $dom->findnodes('///TEST'); #テキスト化して配列収納 my @eachLines; foreach my $tag(@$tags){ push (@eachLines, $tag->serialize); } #テスト出力 my $joinedTxt = join("\x0A",@eachLines); $joinedTxt = encode('UTF-8', $joinedTxt); print $joinedTxt . "\n"; |
これでうまくパースできました。
◇
いやあ文字コードって本当に面倒ですね。
(2017.7.27)
タグ: Perl, Shift_JIS, XML, XML::LibXML, パース