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規格あるの?)、まだ未検証。