LoginSignup
263

More than 5 years have passed since last update.

JavaScriptでのサロゲートペア文字列のメモ

Last updated at Posted at 2015-07-07

I. UnicodeとJavaScript文字列の前提知識

I-I. Unicodeのエスケープシークエンスで文字列を表す

"\uXXXX"形式の4桁の16進数で表す

// シングルクォートとダブルクォートの差はない
"\u3042"; // => "あ"
'\u3042'; // => "あ"

// 正規表現リテラルでも表現可能
/\u3042/.test(''); // => true

I-II. 文字列からUnicode番号を参照する

// 10進数の参照 (戻り値型: number)
''.charCodeAt(0); // => 12354

// 16進数の参照 (戻り値型: string)
''.charCodeAt(0).toString(16); // => "3042"

ちなみにcharCodeAtメソッドの引数は参照したい文字列のインデックス番号を渡す。
1個目の文字なら0、2個目なら1を渡す。

I-III. Unicode番号から文字列を参照する

// 10進数から参照
String.fromCharCode(12354); // => "あ"

// 16進数から参照 数値の16進数リテラルを利用すれば可能
String.fromCharCode(0x3042); // => "あ"

II. サロゲートペア文字列

II-I. サロゲートペアとは

Unicode番号が16進数で10000以上の文字を、UTF-8(およびUTF-16)では表現できないため、
Unicode番号D800DBFFDC00DFFFの組み合わせで表現した仕組み。

たとえば「𩸽(ほっけ)」のUnicode番号は16進数で29E3Dであるのに対して、
実際のコードとしてはD867DE3Dのふたつの文字から構成された文字になる。

'\uD867\uDE3D'; // => "𩸽"

サロゲートペア対象になる文字

漢字

10000以降のUnicode番号の文字はすべてサロゲートペアでないと表現できないが、日本語に関わる文字として関係があるのは以下の範囲の文字

  • 200002A6DF「CJK統合漢字拡張B (CJK Unified Ideographs Extension B)」※JIS第3水準、JIS第4水準の文字を含む
  • 2A7002B73F「CJK統合漢字拡張C (CJK Unified Ideographs Extension C)」
  • 2B7402B81F「CJK統合漢字拡張D (CJK Unified Ideographs Extension D)」
  • 2F8002FA1F 「CJK互換漢字補助 (CJK Compatibility Ideographs Supplement)」

「CJK統合漢字拡張B (CJK Unified Ideographs Extension B)」の範囲はMac OS Xならほぼすべて表示できる。(2A6D72A6DFは未割り当てっぽい)

0 1 2 3 4 5 6 7 8 9 A B C D E F
2000 𠀀 𠀁 𠀂 𠀃 𠀄 𠀅 𠀆 𠀇 𠀈 𠀉 𠀊 𠀋 𠀌 𠀍 𠀎 𠀏
2001 𠀐 𠀑 𠀒 𠀓 𠀔 𠀕 𠀖 𠀗 𠀘 𠀙 𠀚 𠀛 𠀜 𠀝 𠀞 𠀟
:
2A6C 𪛀 𪛁 𪛂 𪛃 𪛄 𪛅 𪛆 𪛇 𪛈 𪛉 𪛊 𪛋 𪛌 𪛍 𪛎 𪛏
2A6D 𪛐 𪛑 𪛒 𪛓 𪛔 𪛕 𪛖
絵文字

絵文字もUnicodeで既にしっかりと定義されている。

  • 1F6001F64F 「絵文字 (Emoticons)」

以下のような絵文字がある。(直訳は感情アイコンだと思うんだけど…)

0 1 2 3 4 5 6 7 8 9 A B C D E F
1F60 😀 😁 😂 😃 😄 😅 😆 😇 😈 😉 😊 😋 😌 😍 😎 😏
1F61 😐 😑 😒 😓 😔 😕 😖 😗 😘 😙 😚 😛 😜 😝 😞 😟
1F62 😠 😡 😢 😣 😤 😥 😦 😧 😨 😩 😪 😫 😬 😭 😮 😯
1F63 😰 😱 😲 😳 😴 😵 😶 😷 😸 😹 😺 😻 😼 😽 😾 😿
1F64 🙀 🙁 🙂 🙅 🙆 🙇 🙈 🙉 🙊 🙋 🙌 🙍 🙎 🙏

他にも色々な範囲の文字定義の中のものが所謂「Emoji」として割り当てられていたりする。


III. サロゲートペア文字列の問題

III-I. 5桁以上のUnicode番号から参照できない

'\u29E3D'; // => "⧣D"

\u29E3でひとつの文字として解釈されてしまう。

III-II. 文字列長を正しく取得できない

実際はふたつの文字から構成されているので2文字分取得されてしまう。

'𩸽'.length; // => 2

III-III. 文字列を分割するとサロゲートで分割される

console.log('𩸽'.split('')); // => ["�", "�"]

つまり反復処理も難しい。

III-IV. 文字列から正しく文字を抜き取れない

'𩸽のひらき'.charAt(1); // => "�"
'𩸽のひらき'[2]; // => "の"
'𩸽のひらき'.slice(1, 4); // => "�のひ" ※表示媒体によっては下位サロゲート以降の文字が表示されない

つまりまともに文字列として扱えない!


IV. 解決方法

IV-I. Unicode番号から文字を生成する (fromCharCodeの代替)

ECMAScript6では String.formCodePoint が使えるのでそれを利用する。

String.fromCodePoint(0x29E3D); // => "𩸽"

もしくは新しいUnicodeリテラルを利用する。
ただしこの記述はES5以前では、Syntax Errorとなるので注意。

"\u{29E3D}"; // => "𩸽"

ECMAScript5以前に対応する (自前で作る)

// シンプルに書くとこう
// ※String.fromCodePointに似せるなら可変長引数に対応する必要あり
function stringFromCodePoint (codeNum) {
    var cp = codeNum - 0x10000;
    var high = 0xD800 | (cp >> 10);
    var low = 0xDC00 | (cp & 0x3FF);
    return String.fromCharCode(high, low);
}

stringFromCodePoint(0x29E3D); // => "𩸽"

IV-II. サロゲートペアに対応した配列化

function stringToArray (str) {
    return str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF]/g) || [];
}

stringToArray('𩸽のひらき'); // => ["𩸽", "の", "ひ", "ら", "き"]

ひとまずこれを作れば他にも応用できる

IV-III. 文字列長を取得する

stringToArray('𩸽のひらき').length; // => 5

IV-IV. 文字を抜き取る

stringToArray('𩸽のひらき')[0]; // => "𩸽"
stringToArray('𩸽のひらき')[1]; // => "の"

あとはよしなに関数化するなりそのまま扱うなりすればよい


V. その他

V-I. HTMLのmaxlength

input要素やtextarea要素のmaxlength属性に関する挙動は、JavaScriptのString.prototype.lengthと全く同じ長さで挙動をとるので、実際問題正確な長さは測れない。

※ドキュメントのcharsetをUTF-32とかにしたら直るのかどうかわからないけど(そもそもutf-32規格あるの?)、まだ未検証。

参考リンク

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
263