HTML中のユニコードエンティティをバイナリ文字列に変換する(Perl)


不特定のウェブサイトからページを取得して全文検索システムに放り込む開発をしていた時に、HTML中のユニコードエンティティをバイナリ文字列に変換する必要があった。その方法の紹介。Perl です。

概要

HTML::Parser をHTMLページからのテキスト抽出に使っていたのだが、HTML::Parser はHTMLエンティティが出てくるとこれをUTF-8フラグの付いたPerl 文字列に変換してしまう。
周辺の文字コードは UTF-8 とは限らず、諸事情で UTF-8 に変換してからの処理という方法も選択していなかったので、HTML::Parser に食わせる前にユニコードエンティティをバイナリ文字列に変換することにした。

ユニコードエンティティについて

ユニコードエンティティとは、下記のような「&#」で始まるHTML上の文字表現のことです。

<html>
<head>
<title>ユニコードエンティティテスト</title>
<meta http-equiv=”Content-Type” content=”text/html; charset=Shift_JIS”>
</head>
<body bgcolor=”#FFFFFF” text=”#000000″>
<p>バイナリ:α</p>
<p>エンティティ10進:&#945;</p>
<p>エンティティ16進 0なし:&#x3b1;</p>
<p>エンティティ16進 0付き:&#x03b1;</p>
</body>
</html>

上記3種類のエンティティはどれもブラウザ上で「α」を表示します。

多数のページを取ってくると、中には日本語がこのようなエンティティで記載されている場合があります。
全文検索を行う場合はこれをバイナリ文字列に戻して検索に掛かるようにしたいので、元の文字に変換したいと言うのが目的です。

ユニコードエンティティ変換の基本

use Encode qw(from_to encode);

#Widowsで、UTF-8でプログラミングしている想定
#普通の文字列の場合
$x = ‘α’;
from_to($x,”utf8″,”shiftjis”);
print $x,”\n”; #α

#コードからバイナリ文字列にするには
$x = pack(“U”, 0x3b1); #αのユニコード値
#from_to($x,”utf8″,”shiftjis”); #これはエラーになる
$x = encode(“shiftjis”, $x);#既に内部表現になっている場合はこっち
print $x,”\n”; #α

#この他の変換。以下の3つは同じ
$x = pack(“U”, 0x3b1);
$x = pack(“U”, 945); #上のコードを10進表記。
$x = pack(“U”, 01661); #上のコードを8進表記。Perl上はOKだが、このようなHTMLエンティティは無い

ユニコードエンティティ変換のコードサンプル

@str = (
‘この &#945; を周辺文字コードのバイナリ文字列にしたいわけです。’,
‘この &#x3b1; を周辺文字コードのバイナリ文字列にしたいわけです。’,
‘この &#x03b1; を周辺文字コードのバイナリ文字列にしたいわけです。’,
);

my $myenc = ‘shiftjis’; #予め調べておく
for (@str){
s/\&#(x?[\da-f]+);/&parse_u_entities($myenc, $1)/ge;
print “$_\n”;
}

sub parse_u_entities{
my ($enc, $str) = @_;
if ($str =~ /^\d+$/){ #10進エンティティ
$str = pack(“U”, 0+$str);
} elsif ($str =~ /^x([\da-f]+)$/i){ #16進エンティティ
$str = pack(“U”, hex($1));
} else {
return;
}
return encode($enc, $str);
}