📕

(整理用) unicodeにおいて、入力フォームやデータベースの文字列処理で考慮しておきたい文字群

2022/04/23に公開

サロゲートペアや合字など、通常の文字とは異なる挙動をする文字があることを聞いていた。しかし、実際にどんな種類の文字があるか曖昧だったのでまとめた。

概念一覧

サロゲートペア

  • 2つの文字コード値を使って、1文字を表現する
  • 𩸽, 𠮷, 😸(😄をベースに作成), 🌕など[1]

合字

  • ㍻、æ など
  • サロゲートペアとは異なり、単一の文字コード値で表現されている
    • ただし、一部環境では未サポートで文字化けすることあり

ゼロ幅接合子(ZWJ, Zero Width Joiner)

  • ゼロ幅接合子(ZWJ)を使って、複数の絵文字から1つの絵文字を表現する。
  • 👨‍👨‍👧‍👦など(環境によっては分割表示される)

異体字セレクタ

  • バイト文字の末尾にセレクタを付けて、異字体を表現する。末尾のバイトを無視すれば、汎用されている文字になる("邉󠄂"であれば、"邉"になる)
  • 邉󠄂 など

上記文字を扱うときの注意点

  • サロゲートペア、ゼロ幅接合子、異字体セレクタを含む文字列処理時には注意が必要
    • jsの場合、String.prototype.split()で分割すると正しく分割できない(String系の処理が1文字2バイト想定で作られているため)。
      • "邉󠄂"だと['邉', '\uDB40', '\uDD02'], "😸"だと['\uD83D', '\uDE38']と分割なる。
      • この場合、スプレッド構文使うか、Intl.Segmenterを使う[2]
    • バイト数計算のとき、1文字2バイトなど決め打ちにすると正しく計算できないことがある(appendix.2を参照のこと)。
  • SQLで文字列を格納するとき、文字列を最大長を決めて格納することがある。この場合、特にゼロ幅文字を含む文字列だとバイト数が膨れ上がりやすくなる。そのため、文字数によって格納するようにするなどの対処が必要。

appendix.1: ダメ文字

  • Shift-JISで、エスケープ文字を含む文字。
    • 表(0x95, 0x5c): 0x5cが""に相当する。ほか、"十"、"能"、"ポ"など色々ある。
    • 竹(0x92, 0x7c): 0x7cが"|"に相当する。ほか、"丹"、"単"、"谷"など色々ある。
  • ソースコードをShift-JISで記載する場合、コメントに"表"などがあるとコメント改行と解釈される可能性がある[3]

appendix.2: 文字列毎のバイト数の対応表

以下、Python3.9.11で検証。

入力文字列 utf-8 utf-16 utf-16le utf-16be utf-32 utf-32le utf-32be
空文字 0 2 (※1) 0 0 4 (※1) 0 0
abcde 5 12 10 10 24 20 20
あいう 9 (※2) 8 6 (※2) 6 16 12 12
𩸽 (※3) 4 6 4 4 8 4 4
👨‍👩‍👧‍👦 25 (※4) 24 22 22 (※5) 32 28 28 (※6)
邉󠄂 7 (※7) 8 6 6 (※8) 12 8 8
  • ※1: BOM(Byte Order Mark)が入るため、空文字でも2(utf-16の場合) or 4(utf-32の場合)バイト確保される
  • ※2: utf-8であ、い、うはそれぞれ0xE38182, 0xE38184, 0xE38186と3バイトで表現されるが、utf-16では0x3042, 0x3044, 0x3046と2バイトで表現されることに起因する。
  • ※3: 𩸽はutf-8では0xF0,0xA9,0xB8,0xBD、utf-16beでは0xD867,0xDE3D、utf-32では0x00029E3Dで表現される。したがって、BOMを抜くと常に4バイトになる。
  • ※4 👨(0xF0, 0x9F, 0x91, 0xA8)、👩(0xF0, 0x9F, 0x91, 0xA9)、👧(0xF0, 0x9F, 0x91, 0xA7)、👦(0xF0, 0x9F, 0x91, 0xA6)をゼロ幅接合子(0xE2, 0x80, 0x8D)でそれぞれ結合している。そのため、4*4 + 3*3 = 25バイトとなる
  • ※5 👨(0xD83D, 0xDC68)、👩(0xD83D, 0xDC69)、👧(0xD83D, 0xDC67)、👦(0xD83D, 0xDC66)をゼロ幅接合子(0x200D)でそれぞれ結合している。そのため、4*4 + 2*3 = 22バイトとなる
  • ※6 👨(0x0001F468)、👩(0x0001F469)、👧(0x0001F467)、👦(0x0001F466)をゼロ幅接合子(0x0000200D)でそれぞれ結合している。そのため、4*4 + 4*3 = 28バイトとなる
  • ※7 邉(0xE9, 0x82, 0x89)の末尾に0xF3, 0xA0, 0x84, 0x82が異字体セレクタとして付与されている。そのため7バイトとなる
  • ※8 邉(0x9089)の末尾に、0xDB60, 0xDD02が付与されている。そのため6バイトとなる
脚注
  1. 市場バグを引き起こした優秀なデータたち: https://teamomusoba.hatenablog.com/entry/2017/12/01/015200 ↩︎

  2. JavaScript: 文字数を正確にカウントするには? - https://qiita.com/suin/items/3da4fb016728c024eaca より。ただし、Intl.SegmenterについてはFirefoxが未対応 ↩︎

  3. Shift-JIS 0x5C問題 - https://www.chihayafuru.jp/tech/index.php/archives/2100 ↩︎

Discussion