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番号D800
〜DBFF
とDC00
〜DFFF
の組み合わせで表現した仕組み。
たとえば「𩸽(ほっけ)」のUnicode番号は16進数で29E3D
であるのに対して、
実際のコードとしてはD867
とDE3D
のふたつの文字から構成された文字になる。
'\uD867\uDE3D'; // => "𩸽"
サロゲートペア対象になる文字
漢字
10000
以降のUnicode番号の文字はすべてサロゲートペアでないと表現できないが、日本語に関わる文字として関係があるのは以下の範囲の文字
-
20000
〜2A6DF
「CJK統合漢字拡張B (CJK Unified Ideographs Extension B)」※JIS第3水準、JIS第4水準の文字を含む -
2A700
〜2B73F
「CJK統合漢字拡張C (CJK Unified Ideographs Extension C)」 -
2B740
〜2B81F
「CJK統合漢字拡張D (CJK Unified Ideographs Extension D)」 -
2F800
~2FA1F
「CJK互換漢字補助 (CJK Compatibility Ideographs Supplement)」
「CJK統合漢字拡張B (CJK Unified Ideographs Extension B)」の範囲はMac OS Xならほぼすべて表示できる。(2A6D7
〜2A6DF
は未割り当てっぽい)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2000 | 𠀀 | 𠀁 | 𠀂 | 𠀃 | 𠀄 | 𠀅 | 𠀆 | 𠀇 | 𠀈 | 𠀉 | 𠀊 | 𠀋 | 𠀌 | 𠀍 | 𠀎 | 𠀏 |
2001 | 𠀐 | 𠀑 | 𠀒 | 𠀓 | 𠀔 | 𠀕 | 𠀖 | 𠀗 | 𠀘 | 𠀙 | 𠀚 | 𠀛 | 𠀜 | 𠀝 | 𠀞 | 𠀟 |
: | ||||||||||||||||
2A6C | 𪛀 | 𪛁 | 𪛂 | 𪛃 | 𪛄 | 𪛅 | 𪛆 | 𪛇 | 𪛈 | 𪛉 | 𪛊 | 𪛋 | 𪛌 | 𪛍 | 𪛎 | 𪛏 |
2A6D | 𪛐 | 𪛑 | 𪛒 | 𪛓 | 𪛔 | 𪛕 | 𪛖 |
絵文字
絵文字もUnicodeで既にしっかりと定義されている。
-
1F600
〜1F64F
「絵文字 (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規格あるの?)、まだ未検証。