RSSの <content:encoded> の文字列が数値文字参照になっているので読める文字列に変換する

こんなやつ

www.vogue.co.jp

大昔にRSSでシステム間連携を実装したときにハマった気がしたけど一切覚えてない。

content:encoded にある &#x30E1;&#x30FC;&#x30AC;&#x30F3;&#x5983; のような文字列はどうやら 符号化文字集合の一つである Unicode におけるその文字のCode Point を16進数で表示する 数値文字参照 という形式で出力されてるらしい。

https://ja.wikipedia.org/wiki/%E6%96%87%E5%AD%97%E5%8F%82%E7%85%A7#%E6%95%B0%E5%80%A4%E6%96%87%E5%AD%97%E5%8F%82%E7%85%A7%EF%BC%88%E6%96%87%E5%AD%97%E5%8F%82%E7%85%A7%EF%BC%89

ISO/IEC 10646 - Wikipedia

この数値文字参照を実際の文字に戻すには

            文字符号化方式
         vvvvvvvvvvvvvvvvvvvvv
文字 <-> Code Point <-> byte列
^^^^^^^^^^^^^^^^^^^
   符号化文字集合

のうち Code Point > 文字 をすればよい。
Pythonでやる場合は unicodedata モジュールを使って

def unicode_test(codepoint):
    import unicodedata
    #コードポイントを対応する文字の名前に変換
    name = unicodedata.name(codepoint)
    #対応する名前に当てはまる文字を取得
    letter = unicodedata.lookup(name)
    result = """
        Unicode文字: {}
        対応する文字の名前: {}
             """.format(letter, name)
    print(result)

kanitamago5503.hatenablog.com

とすると変換できる。

試してみる

>>> import re
>>> pattern = '&#x([0-9A-F]{4});'
>>> res = re.findall(pattern, '&#x30E1;&#x30FC;&#x30AC;&#x30F3;&#x5983;')  # 数値文字参照からCode Pointの16進数文字列部分だけ抜き出す
>>> print(res)
['30E1', '30FC', '30AC', '30F3', '5983']

>>> m = map(lambda x: unicode_test(chr(int(x, 16))), res) #16進数文字列から16進数数値型に変換した上で、(Unicode)文字列に変換して上記の関数に渡してみる
>>> print(list(m))
        Unicode文字: メ
        対応する文字の名前: KATAKANA LETTER ME

        Unicode文字: ー
        対応する文字の名前: KATAKANA-HIRAGANA PROLONGED SOUND MARK

        Unicode文字: ガ
        対応する文字の名前: KATAKANA LETTER GA

        Unicode文字: ン
        対応する文字の名前: KATAKANA LETTER N

        Unicode文字: 妃
        対応する文字の名前: CJK UNIFIED IDEOGRAPH-5983

できた。

Pythonhtml.unescape() を使うと 数値文字参照からコードポイント16進数文字列への変換コードポイント16進数文字列から文字に変換 を一気にやってくれるから便利。

Presto/HiveにはUTF-16を操作する関数が用意されてないっぽい
あるにはあったが、数値文字参照からコードポイント16進数文字列への変換コードポイント16進数文字列から文字に変換SQLでやるのは相当気合がいりそう(できるかどうかわからない)

prestodb.io