Python
python3

Å(おんぐすとろーむ)とNFC @ Python

Near Field Communicationじゃないよ、Normalization Form Canonical Compositionだよ。

Unicode正規化 @ Wikipedia

UnicodeではÅとÅは違う文字です。

  • Å (ANGSTROM SIGN) ... U+212B
  • Å (LATIN CAPITAL LETTER A WITH RING ABOVE) ... U+00C5

後者は数値から見てLatin-1の匂いがします。事実その通りみたいです。

NFC正規化するとオングストロームは上リング付きAになります。これをPythonで確認してみます。

>>> import unicodedata
>>> ord(unicodedata.normalize('NFC', '\N{ANGSTROM SIGN}'))
197
>>> unicodedata.name(unicodedata.normalize('NFC', '\N{ANGSTROM SIGN}'))
'LATIN CAPITAL LETTER A WITH RING ABOVE'

unicodedataは標準ライブラリのモジュールです。オマケですが、macOSで時折話題になるNFD正規化だと2文字です。

>>> len(unicodedata.normalize('NFD', '\N{ANGSTROM SIGN}'))
2
>>> [ord(ch) for ch in unicodedata.normalize('NFD', '\N{ANGSTROM SIGN}')]
[65, 778]
>>> [unicodedata.name(ch) for ch in unicodedata.normalize('NFD', '\N{ANGSTROM SIGN}')]
['LATIN CAPITAL LETTER A', 'COMBINING RING ABOVE']

理屈上はこの変換で困ることが当然あり得ます。例えばShift_JISでは「オングストローム」は表現できますが「上リング付きA」は表現できません。Shift_JIS形式で保存されたテキストファイルから文字を読み込んで、NFC正規化後に再度Shift_JIS形式で保存しようとすると、問題が起きることがあります。

>>> with open('from.txt', encoding='shift_jis') as fr:
...    with open('to.txt', 'w', encoding='shift_jis') as fw:
...        fw.write(unicodedata.normalize('NFC', fr.read()))
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
UnicodeEncodeError: 'shift_jis' codec can't encode character '\xc5' in position 0: illegal multibyte sequence

本質的でないファイル読み込みを省略しますと

>>> '\N{ANGSTROM SIGN}'.encode('shift_jis')
b'\x81\xf0'
>>> unicodedata.normalize('NFC', '\N{ANGSTROM SIGN}').encode('shift_jis')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'shift_jis' codec can't encode character '\xc5' in position 0: illegal multibyte sequence

もっと率直には次の通り

>>> '\N{LATIN CAPITAL LETTER A WITH RING ABOVE}'.encode('shift_jis')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'shift_jis' codec can't encode character '\xc5' in position 0: illegal multibyte sequence

この例は『プログラマのための文字コード技術入門』で知ったのですが、同著では理由については不明(「なぜかそうはなっていません」p353)になっています。

で、偶然Wikipediaに以下の記述を見かけました。

オングストロームの単位記号はこの文字だが、UnicodeやJIS X 0213 では本来の文字とは別の文字として定義されている。ただし、Unicodeのオングストローム記号 U+212B は古い規格との後方互換性維持のためだけに使える互換文字であり、使用は推奨されない。
(Wikipediaより)

「後方互換性のみに使えよ?」という理由が一応あるのかー、と理解しました。

とはいえUnicode正規化はどれも結構心配な感じ。文字は難しいのです。

おまけですが、ブラウザでどちらか片方で検索をかけると双方ひっかかります。多分4種類のどれかの正規化後に検索してる気がします。検索動作にブラウザ共通の仕様があるかはよく分かりません。

「この文字、化けるんだけど」というエンドユーザさんの素朴なクレームがこのあたりに出てくると「ひぃっ」てなります。Windows上でCP932とshift_jisとUTF-8が入り乱れるシステムに付き合わされたりするので、他人事ではないのです。