取得したHTMLファイルやXHTMLファイルの文字列を取り出した場合、それが文字参照や実体参照で書かれていると、表示上の文字と異なってしまう。そこで、文字列に文字参照や実体参照が含まれていた場合に、それを通常の文字に戻す処理が必要になる。


文字参照には16進数で書かれたものと10進数で書かれたものがある。

♪  (10進数)
♪ (16進数)

これらの数値を読み取れば、あとはunichr関数で数値から対応するUnicode文字へ変換することができる。

print unichr(29031)

実体参照には、よく知られている&lt;(<)や&nbsp;(半角スペース)以外にも、&copy;(著作権記号)や&divide;(除算記号)などもあり、これらの実体参照と通常の文字との対応を全て調べるとなるとなかなか大変だ。しかし、Pythonにはhtmlentitydefs.name2codepointという実体参照名とUnicodeの対応表があるため、これを使うことで簡単に変換できる。例えば、ハートマークを表示する&hearts;は以下のようにするだけでいい。

print unichr(htmlentitydefs.name2codepoint['hearts'])

この処理を関数にすると以下のようになる。

#!-*- coding:utf-8 -*-
import htmlentitydefs
import re

# 実体参照 & 文字参照を通常の文字に戻す
def htmlentity2unicode(text):
    # 正規表現のコンパイル
    reference_regex = re.compile(u'&(#x?[0-9a-f]+|[a-z]+);', re.IGNORECASE)
    num16_regex = re.compile(u'#x\d+', re.IGNORECASE)
    num10_regex = re.compile(u'#\d+', re.IGNORECASE)
    
    result = u''
    i = 0
    while True:
        # 実体参照 or 文字参照を見つける
        match = reference_regex.search(text, i)
        if match is None:
            result += text[i:]
            break
        
        result += text[i:match.start()]
        i = match.end()
        name = match.group(1)
        
        # 実体参照
        if name in htmlentitydefs.name2codepoint.keys():
            result += unichr(htmlentitydefs.name2codepoint[name])
        # 文字参照
        elif num16_regex.match(name):
            # 16進数
            result += unichr(int(u'0'+name[1:], 16))
        elif num10_regex.match(name):
            # 10進数
            result += unichr(int(name[1:]))

    return result

# テストコード
text = u"&#25991;&#23383;&#x53C2;&#x7167; &amp; &#x5B9F;&#x4F53;&#21442;&#29031; を通常の文字に戻します。";
print htmlentity2unicode(text)
文字参照 & 実体参照 を通常の文字に戻します。