初めに
もともと文字列(String)のメソッド整理したいと思い色々と調べると正しく処理するためにUnicodeと正規表現などの背景知識が必要だと感じました。これを機にさらにプログラミングへの認識を深めたらいいと、膨大な知識から今の自分に必要なだけの部分を切り取ってかみ砕いていきたいと思います。
今回の参考文章です。
前置き
(あくまでも自分の認識を書いたのですが、間違ってる、至らなかったところがあればご了承ください。)
Unicode について
参考文章はこちらです。
Unicode
Character encoding
-
Unicode とは、コンピュータが人間の文字を処理させるために創られたスタンダードです。スタンダードでは、文字をコンピュータが読めるようにどのように符号化(encoding)する、そしてどのように文字コードの集合を作るのを規定している。(符号化文字集合と文字符号方式)
- ここの符号化というのは Character encoding、人間の文字を図形(最初はBMP、Basic Multilingual Plane)にするため、図形文字にコードをアサインするプロセスです。これで付与されたコードで図形を呼び出すことができ、また図形からコードへ転換する処理もできる。
-
符号化文字集合(CCS、Corded Character Set)は文字と一意に振られた番号のペアの集合。
-
文字符号方式(CES、Character Encoding Scheme)は文字に振られた番号をバイト表現に転換する方法。(CEF vs. CES)
- 自分の理解ですが、文字コードに一意のバイトを割り当てられるので、実際の処理は、図形文字⇔文字コード⇔一意のバイト、に近いと感じています。
- 図形文字は画面上の表現、文字コードは標準化した文字と番号の集合、一意のバイトはコンパイル処理のために定められた変換方式。
UTF について
-
UTF は Unicode Transformation Format/UCS Transformation Format、 UCS-2 (Universal multi-octet Character Set 2)/BMP または UCS-4 という標準規格で文字を収録するバイト列としての表現方式です。
-
UCS-2 は Universal Character Set coded in 2 octers、2つのオクテットでコードされたユニバーサル文字セット(文字集合)。
-
UCS-2 は各文字に 2 バイト( 2 オクテット、16 ビット)の符号点を割り当て、0 番から 65,535 番( 2^16 )の番号を
U+0000
~U+10FFFF
までに対応する文字が定められている。これはBMP、基本多言語面と呼ぶ。例えば'A'
は UCS-2 で U+0041 と表記している。- 符号点(code point)または符号位置(code position) は、文字を割り当てる個々の点です。
- ここのオクテットは文字長(character length)の単位としての 8 ビットのことです。
-
UCS-2 で最大 65,536 文字まで表示するので徐々に対応できず、UCS-2 を拡張して、UCS-4 という Unicode の 4 バイト符号化形式が創られた。それを用いた UTF-32 という文字符号化形式(CEF)は100万以上を超えた固有の文字を定義できる。
- UCS-2 は 各文字を 1~ 4 オクテットで表現する。番号の範囲は
U+0000
~U+FFFF
。 - UCS-4 は 各文字を固定した 4 オクテットを使って表現する。番号の範囲が
U+00000000
~U+7FFFFFFF
、その中のU+00000000
~U+0000FFFF
は UCS-2 のU+0000
~U+10FFFF
と同じである。
- UCS-2 は 各文字を 1~ 4 オクテットで表現する。番号の範囲は
UTF-8、UTF-16、UTF-32 について
- UTF-8、UTF-16、UTF-32 は文字符号化形式。
- それぞれ 8 ビット(1 バイト)整数、16 ビット(2 バイト)整数、32 ビット(4 バイト)整数の符号単位(または符号点、code point)を使って文字を符号化する。
- UTF-8 は 1~6 バイトの可変長符号、5~6 バイトの表現は不正なシーケンスになるため、実際 1~4 バイトで表現している。
- UTF-16 では UTF-16、UTF-16BE、UTF-16LE がある。UTF-16 は 16 ビットで符号単位1つとして文字を符号化する。それが名前の由来です。UTF-16 は符号単位1つで BMP 内の文字を完全に表現し、BMP 以外の文字は符号単位2つで表現する。
- BE(Big Endian)とLE(Little Endian)はCPUがコードを解析するとき、バイトの並べる順序(endianness)という。
- BE は上位 ⇐ 下位、LE は上位 ⇒ 下位。
- UTF-16はBOM(Byte Order Mark、U+FEFF)が先頭に
FE FF
の場合は BE、FF FE
の場合は LE と判断できる。 - 今のJavaScriptの内部では UTF-16 を採用して文字列を扱っているが、外部でコード書くとき UTF-8、またはUTF-16以外の文字でも問題ないです。
簡潔に言うと、Unicode は世界の文字を統一した文字コードの集合です。文字コードの格納とバイト表現の仕方は UCS-2、UCS-4 によって定められる。そしてUnicodeと、UCS-2、UCS-4 を実現したのは UTF-8、UTF-16、UTF-32 という文字符号化方式です。JavaScriptは UTF-16 を採用している。
Memo
このメモの取り方は、
カテゴリ→MDN、機能別→とほほの正規表現入門に従って書いています。
特殊文字(Special characters)について
文字クラス
\
, .
, \cX
, \d
, \D
, \f
, \n
, \r
, \s
, \S
, \t
, \v
, \w
, \W
, \0
, \xhh
, \uhhhh
, \uhhhhh
, [\b]
Character classes
メタ文字
-
.
:任意文字にマッチする。
定義済み表現
-
\d
:digits 数字文字[0-9] -
\D
:\d
以外の文字 -
\w
:word アンダーバーを含む英数文字[a-zA-Z0-9_] -
\W
:\w
以外の文字 -
\s
:space 空白文字\t\f\r\n\v
、-
\t
:horizontal tab 水平タブ(U+0009
HT/TAB:\x09) -
\f
:form feed 改ページ(U+000C
FF:\x0C) -
\r
:Carriage Return (CR) 復帰(U+000D
CR:\x0D) -
\n
:newline 改行(U+000A
LF:\x0A)
-\n
(0x0A) vs.\r
(0x0D) -
\v
:vertical tab 垂直タブ(U+000B
VT:\x0B) -
U+0020
SP:\x20) -
U+00A0
SP:\xA0) -
U+3000
) -
U+2028
) -
U+2029
)
-
-
\S
:\s
以外の文字
制御文字
-
\0
:ヌル(U+0000
NUL:\x00) -
[\b]
:バックスペース(U+0008
BS:\x08)。-
\b
と同じ表現であるため、[]
で囲むのみバックスペースという意味を表す。 -
\b
:単語の区切り
-
-
\t
:horizontal tab 水平タブ(U+0009
HT/TAB:\x09) -
\f
:form feed 改ページ(U+000C
FF:\x0C) -
\r
:Carriage Return (CR) 復帰(U+000D
CR:\x0D) -
\n
:newline 改行(U+000A
LF:\x0A) -
\v
:vertical tab 垂直タブ(U+000B
VT:\x0B) -
\e
:escape エスケープ(U+001B
ESC:\x1B) -
\cX
:Ctrl-*-
\cM
:\r
-
\cI
:\t
-
\cJ
:\n
-
\cK
:\x0B
(\v
) -
\cL
:\f
-
文字コード
-
\xhh
:hh(2 桁の 16 進数)にマッチする。 -
\uhhhh
:hhhh(4 桁の 16 進数)の UTF-16 コードユニット(code unit)にマッチする。
ユニコード
-
\u{hhhh}
:ユニコードからなる文字にマッチする。 -
\u{hhhhh}
:サロゲートペア文字列。ES6からString.fromCodePoint()
を使えば正しく対応してくれる。- サロゲートペア(Surrogate pair、代用対)
- 前半
U+D800
-U+DBFF
- 後半
U+DC00
-U+DFFF
- 前半
-
CJK統合漢字(CJK Unified Ideographs)
- CJK:
U+4E00
-U+9FFF
(U+F900
-U+FAFF
はブロック) - Extension A:
U+3400
-U+4DBF
- Extension B:
U+20000
-U+2A6DF
- Extension C:
U+2A700
-U+2B738
- Extension D:
U+2B740
-U+2B81D
- Extension E:
U+2B820
-U+2CEA1
- Extension F:
U+2CEB0
-U+2EBE0
- Extension G:
U+30000
-U+3134A
- CJK:
- サロゲートペア(Surrogate pair、代用対)
識別子(フラグ)について
-
i(ignoreCasing)
:検索は大文字・小文字を区別しないマッチングする。 -
g(global)
:グローバルモード。先頭から最後までマッチングを連続的に行う。- フラグ
g
で使わない場合は最初のもののみ探す。
- フラグ
-
s(dotAll)
:シングルモード。ピリオド(.)は改行文字にもマッチするようになる。 -
m(multiline)
:マルチラインモード。複数行に検索する。 -
y(sticky)
:lastIndexで指定した位置からのみ検索する。 -
u(unicode)
:Unicodeのサロゲートペア文字も1文字として扱う。 -
d(hasIndices)
:マッチ文字列の先頭・終了インデックスを返す。- プロパティ
indices
で配列として示す。
- プロパティ
Character classes
.
:任意文字(改行文字を除く)
.
は行末(空白)文字\t\f\r\n\v
を除いて、任意の文字にマッチする。
\s
で行末文字もマッチするようになる。
// `.` any single character (exclude newlines)
console.log('AxyzA'.match(/A...A/));
// [ 'AxyzA', index: 0, input: 'AxyzA', groups: undefined ]
console.log('A\nA'.match(/A.A/));
// null // newline is not included
console.log('A\nA'.match(/A\sA/));
// [ 'A\nA', index: 0, input: 'A\nA', groups: undefined ]
\d
:数字文字[0-9]
\d
は数字にマッチする。
console.log('1a2b'.match(/\d/));
// [ '1', index: 0, input: '1a2b', groups: undefined ]
console.log('1a2b'.match(/\d/g));
// [ '1', '2' ]
console.log('123'.match(/^\d+$/));
// [ '123', index: 0, input: '123', groups: undefined ]
console.log('1 2 3'.match(/^\d+$/));
// null
console.log('1aa2'.match(/^\d..\d$/));
// [ '1aa2', index: 0, input: '1aa2', groups: undefined ]
g(Global)
:フラグ。グローバルモード。先頭から最後までマッチングを連続的に行う。
^
:行頭にマッチする。(改行後の行頭を含まない)
$
:行末にマッチする。(改行後の行末を含まない)
+
:直前の文字が1個以上連続するものにマッチする。
\D
:\d
以外の文字
console.log('1a2b'.match(/\D/));
// [ 'a', index: 1, input: '1a2b', groups: undefined ]
console.log('1a2b'.match(/\D/g));
// [ 'a', 'b' ]
\w
:_を含む英数文字[a-zA-Z0-9 _ ]
console.log('a!b@c#A$B%C^0&1*2(){}[]'.match(/\w/g));
// [
// 'a', 'b', 'c',
// 'A', 'B', 'C',
// '0', '1', '2'
// ]
\W
:\w
以外の文字
console.log('a!b@c#A$B%C^0&1*2(){}[]'.match(/\W/g));
// [
// '!', '@', '#', '$',
// '%', '^', '&', '*',
// '(', ')', '{', '}',
// '[', ']'
// ]
\s
:空白文字\t\f\r\n\v
、
\t
:horizontal tab 水平タブ(U+0009
HT/TAB:\x09)
\f
:form feed 改ページ(U+000C
FF:\x0C)
\r
:Carriage Return (CR) 復帰(U+000D
CR:\x0D)
\n
:newline 改行(U+000A
LF:\x0A)
\n
(0x0A) vs. \r
(0x0D)
\v
:vertical tab 垂直タブ(U+000B
VT:\x0B)
:通常の空白(U+0020
SP:\x20)
:NBSP(Non-Breaking Space)(U+00A0
SP:\xA0)
:ideographic space 全角スペース(U+3000
)
:Line separator 行区切り(U+2028
)
:Paragraph separator 段落区切り(U+2029
)
console.log('abc\nABC\r012\t()\f{}\v[]'.match(/\s/g));
// [ '\n', '\r', '\t', '\f', '\x0B' ]
// SP
console.log('a b'.match(/\x20/));
// [ ' ', index: 1, input: 'a b', groups: undefined ]
console.log('a b'.match(/\u0020/));
// [ ' ', index: 1, input: 'a b', groups: undefined ]
console.log('a b'.match(/\s/));
// [ ' ', index: 1, input: 'a b', groups: undefined ]
console.log('a b'.match(/ /));
// [ ' ', index: 1, input: 'a b', groups: undefined ]
// NBSP
console.log('a b'.match(/\u00A0/));
// [ ' ', index: 1, input: 'a b', groups: undefined ]
console.log('\u00A0'.match(/\s/));
// [ ' ', index: 0, input: ' ', groups: undefined ]
// note: $nbsp(html)
// Ideographic space(全角スペース)
console.log(' '.match(/\u3000/));
// [ ' ', index: 0, input: ' ', groups: undefined ]
console.log(' '.match(/\s/));
// [ ' ', index: 0, input: ' ', groups: undefined ]
// Line separator
console.log('\u2028'.match(/\s/));
// [ ' ', index: 0, input: ' ', groups: undefined ]
// Paragraph separator
console.log('\u2029'.match(/\s/));
// [ ' ', index: 0, input: ' ', groups: undefined ]
\S
:\s
以外の文字
console.log('abc\nABC\r012\t()\f{}\v[]'.match(/\S/g));
// [
// 'a', 'b', 'c', 'A',
// 'B', 'C', '0', '1',
// '2', '(', ')', '{',
// '}', '[', ']'
// ]
[\b]
:バックスペース(U+0008
BS:\x08)
console.log('\x08'.match(/[\b]/));
// [ '\b', index: 0, input: '\b', groups: undefined ]
console.log('abc\bdef'.match(/[\b]/));
// [ '\b', index: 3, input: 'abc\bdef', groups: undefined ]
console.log('\b'.match(/\b/));
// null
\0
:ヌル(U+0000
NUL:\x00)
let n = String.fromCharCode('\u0000');
console.log(n.match(/\0/));
// [ '\x00', index: 0, input: '\x00', groups: undefined ]
console.log('\x00'.match(/\0/));
// [ '\x00', index: 0, input: '\x00', groups: undefined ]
console.log('\x00'.match(/\x00/));
// [ '\x00', index: 0, input: '\x00', groups: undefined ]
console.log('\x00'.match(/\u0000/));
// [ '\x00', index: 0, input: '\x00', groups: undefined ]
console.log(''.match(/\0/));
// null
\cX
:Ctrl-*
// (CR LF)
let n = '\u000D\u000A';
console.log(n.match(/\cM/));
// [ '\r', index: 0, input: '\r\n', groups: undefined ]
n = '\x0D\x0A';
console.log(n.match(/\cM/));
// [ '\r', index: 0, input: '\r\n', groups: undefined ]
console.log('abc\nABC\r012\t()\f{}\v[]'.match(/\cM/g));
// [ '\r' ]
\xhh
// (NULL B)
console.log('\x00\x08'.match(/\0/));
// [ '\x00', index: 0, input: '\x00\b', groups: undefined ]
console.log('\x00\x08'.match(/[\b]/));
// [ '\b', index: 1, input: '\x00\b', groups: undefined ]
console.log('\x08' === '\b'); // true
\uhhhh
ここからは16進数Unicodeの書き方です。
第0面(基本多言語面)ならcharCodeAt()
、codePointAt()
もUnicodeを正しく返してくれます。
// あ(U+3042)
console.log('あ'.charCodeAt()); // 12354
console.log('あ'.codePointAt()); // 12354
そしたら.toString(16)
で16進数に変えばいいのですが。
console.log('あ'.charCodeAt().toString(16)); // 3042
console.log('\u3042'); // あ
\u{hhhh}
& \u{hhhhh}
\u{hhhh}
はES6から、第0面のほか、サロゲートペアも正しく変換できるようにする書き方です。
// あ(U+3042)
console.log('\u{3042}'); // あ
例えば、𩸽はサロゲートペアで表す文字です。
サロゲートペアは前半(上位)と後半(下位)から各々1つずつからなるペアです。
charCodeAt()
は 0 から 65535 までのユニコードを返すが、𩸽は二つのユニコードペアで出来たので、charCodeAt()
を使うと最初のインデックス[0]だけ返します。
console.log('𩸽'.charCodeAt()); // 55399
console.log('𩸽'[0].charCodeAt()); // 55399
console.log('𩸽'[1].charCodeAt()); // 56893
codePointAt()
なら正しく変換できるユニコードを返してくれます。
console.log('𩸽'.codePointAt()); // 171581
console.log('𩸽'.codePointAt().toString(16)); // 29e3d
console.log('\u{29e3d}'); // 𩸽
サロゲートペアの前半と後半とのユニコードを設定してマッチさせるのもできます。
function isSurrogate(str) {
return str.match(/[\uD800-\uDBFF]|[\uDC00-\uDFFF]/g);
}
console.log(isSurrogate('𩸽'));
// [ '\ud867', '\ude3d' ]