ユニコード文字列の「長さ」

文書作成ツールの利便性のひとつは参照関係を自動解決してくれるところにあり、 sphinx だと :ref: などがそれに当たる。

この機能を表の中でも使いたいと思い、 csv-table で :ref: を含んだデータを 読み込ませたのだが、sphinx が :ref: を解釈してくれず、そのまま出力されてしまった。 この時、実は、:ref: の記述をミスしていて、そのために参照が解決されなかったのだが、 普段 csv-table をあまり使わないせいで、「csv-table だと :ref: を解釈してくれない」と 思い込んでしまい、csv を grid table に整形するスクリプトを自作しようと 思ったのが発端である。

大きな csv(精確にはssv)データを手作業で grid table 化するのは面倒なので、 自動変換スクリプトを書こうと思うのは当然だが、書き始めてすぐに、 文字列長に関して疑問が生じた。grid table は罫線を精確に揃えて記述しなければ ならない形式であり、文字列長を厳密に計算する必要があるが、 マルチバイト文字列が含まれている場合に 文字列長をどのように計算しているのか、ということだ。

私は普段 ターミナル + Vim で作業しており、grid table を書く場合には 画面の見た目通りに書く。すなわち、Ascii文字は 1文字分、全角文字は 2文字分として 計算すれば sphinx のビルドが通る。しかしこれは不思議なことで、 UTF-8 だと日本語文字は 3バイト以上になるし、unicode オブジェクトに len() を 取ると1文字扱いだし、UTF-16 にすると逆に Ascii 文字が 2バイトに計算され、 全角/半角概念とは綺麗には整合しない。

不思議に思って sphinx のコードを追ってみたら、 docutils の方で、 unicodedata.east_asian_width() を呼んでいることが判った (docutils/statemachine.py)。 これは Unicode Standard Annex #11 に基づくもので、文字の表示幅を返す函数らしい。 docutils ではこれを利用して、画面上の見た目に合うように内部データを 修正している。

こんな便利な函数があるとは知らなかった。と言うか unicodedata ライブラリ自体 知らなかった。”unicodedata.east_asian_width” でググっても 1500件くらいしか ヒットしないので、世間でもあまり知られていないのかも知れない。

なお、:ref: の記述ミスに気付いたのはスクリプトが完成してからで、 完全な時間の無駄だった。馬鹿馬鹿しいが、勉強にはなった。