Perlで数値文字参照を文字列に変換

リンクされて気付いたのですが、Perlで文字列をHTML数値文字参照に変換とは逆の、数値文字参照を文字列に変換する方法が述べられてました。しかしこの方法だと動作がややオーバーになるような気がします。

perl -MEncode -p -i -e '
     s/\&#(x)?([a-f0-9]{1,5});/
        my $tmpstr = ($1)
                   ? pack( "H*", sprintf( "%08s", "$2" ) )
                   : pack( "N*", $2 );
        Encode::encode( "iso-2022-jp",
                        Encode::decode( "UTF-32BE", $tmpstr )
        );
     /eig;
' < engadget.xml

&#28988; とか &#x713c; のような文字参照からUnicodeのコードポイントを得るのは容易ですが、そこからPerl内部文字列を得るには chr関数を使えばよいので、こちらも簡単にできます(Perl 5.8以降であれば)。

$str = chr(28988);       # 十進数の場合
$str = chr(hex '713c');  # 十六進数の場合

その他気付いた点として、正規表現を見ると、

というのにもマッチしそうなので、十進と十六進とではべつべつに置換した方がよさそうな気がします。

また、1文字置換するごとにEncode::encode関数を呼び出すのも大変なので、文字参照をすべて内部文字列に置換した後で、まとめて求める文字エンコードに変換した方がよさそうです。

というわけで、

# chref2str.pl
use Encode;
while (<>) {
  s/&#([0-9]+);/chr($1)/ge;
  s/&#[xX]([0-9A-Fa-f]+);/chr(hex $1)/ge;
  print encode('iso-2022-jp', $_);
}
$ perl -i.bak chref2str.pl rss.xml

という風にしてみるのはいかがでしょう。

* * *

前の記事でもそうでしたが、PerlでUnicodeを扱う場合にはPerl 5.8.x Unicode関連のページがとても参考になります。