Daylogs/Javascript
ES6: RegExp.prototype.unicode 이해하기
ohgyun
2015. 11. 2. 21:34
발생일: 2015.11.02
키워드: unicode, ES6, regexp, u flag, 정규식, Regular Expression
문제:
JSConf EU 2015 컨퍼런스에서 Mathias Bynens가 발표한 ES6의 RegExp.prototype.unicode 동영상이 공유됐다.
발표 내용이 좋아서, 핵심적인 내용에 코드 샘플을 덧붙여 정리해봤다.
해결책:
JSConf EU 2015: RegExp.prototype.unicode
자바스크립트에서도 캐릭터의 symbol을 직접 사용할 필요 없이,
U+ 와 16진수 숫자(hexadecimal)로 코드 포인트를 직접 참조할 수 있다.
유니코드의 범위는 아래와 같이 크게 세 가지로 나눌 수 있다.
Unicode range
- U+000000 ~ U+10FFFF
BMP range (Basic Multilingual Plane)
- U+000000 ~ U+00FFFF
- 기본적인 다국어를 포함하는 범위
Astral planes range
- U+010000 ~ U+10FFFF (전체 범위의 95% 이상)
- 기본적인 다국어 외 이모지 등을 포함
자바스크립트는 Astral planes 를 두 개의 코드 포인트로 나눠 처리한다.
각각의 코드 포인트를 'surrogate half'라고 부르고, 두 코드 포인트의 조합을 'surrogate pair'라고 한다.
아래 코드를 보면 동작 방식을 이해할 수 있다.
예를 들어, 💩와 같이 Astral plane 레인지에 포함되어 있는 캐릭터가 있다고 가정해보자.
let poo = '💩';
한 글자이지만 실제로 length를 조회해보면 2 가 리턴된다.
poo.length; //-> 2
캐릭터의 각 인덱스에 있는 코드를 확인해보자.
poo.charCodeAt(0); //-> 55357
poo.charCodeAt(0).toString(16); //-> 'd83d'
poo.charCodeAt(1); //-> 56489
poo.charCodeAt(1).toString(16); //-> 'dca9'
실제로 💩 캐릭터는 아래처럼 두 개의 유니코드로 구성되어 있다. (서로게이트 페어)
let poo2 = '\uD83D\uDCA9';
물론, 두 값을 비교해도 같다.
poo === poo2; //-> true
코드 포인트를 브레이스({})로 감싸 표현하면, 자바스크립트의 기본 표현 범위(\uXXXX)보다 더 큰 값의 코드 포인트를 참조할 수 있다.
let poo3 = '\u{1F4A9}';
poo2 === poo3; //-> true
위처럼 Astral plane range에 있는 캐릭터가 서로게이트 페어로 나뉘어지는 구조 때문에,
ES5에서는 Astral 캐릭터들은 정규식에서 매칭하기 어려웠다.
ES6부터는 유니코드 처리를 위한 RegExp.prototype.unicode 를 추가했고,
지금부터 설명할 u 플래그로 손쉽게 처리할 수 있게 되었다.
u flag
ES6에서는 정규식에서 유니코드를 처리할 수 있는 u 플래그가 추가됐다.
/\u{61}/u.test('a'); //-> true
/\u{1F4A9}/u.test('💩'); //-> true
u 플래그를 생략하면 브레이스({})를 정규식의 한정 수량자로 인식한다.
/\u{61}/.test('a'); //-> false, 이 정규식 구문은 'u'가 61개 있는 패턴을 의미한다.
u 플래그가 추가되면, 정규식이 스트릭트 모드(strict mode)로 동작한다고 생각하면 된다.
/\abc/.test('abc'); //-> true, ES5에선 백슬래시(\) 뒤에 예약된 캐릭터가 오지 않는 경우 백슬래시를 무시했다.
/\abc/u.test('abc'); //-> throws error
dot(.) operator
u 플래그가 붙은 경우, 닷(.) 오퍼레이터는 astral plane 에 포함된 문자열까지 포함한다.
let poo = 'a💩b';
/a.b/.test(poo); //-> false
/a.b/u.test(poo); //-> true
quantifiers (수량자)
u 플래그는 astral 심볼도 한 글자로 가정해 매칭한다.
/a{2}/.test('aa'); //-> true
/💩{2}/.test('💩💩'); //-> false
ES5에서 위 코드는 실제로 아래와 같이 서로게이트 페어로 나뉘어져 동작한다.
/\uD83D\uDCA9{2}/.test('\uD83D\uDCA9\uD83D\uDCA9'); //-> false
u 플래그를 사용한다면 아래처럼 의도한 결과를 볼 수 있다.
/💩{2}/u.test('💩💩'); //-> true
Character classes
u 플래그는 캐릭터 클래스([])에 심볼이 포함된 경우도 한 글자로 처리한다.
let regex = /^[ab💩]$/;
regex.test('a'); //-> true
regex.test('b'); //-> true
regex.test('💩'); //-> false
마찬가지로 위 코드는 실제로 아래와 같이 서로게이트 페어로 나뉘어지기 때문이다.
let regex = /^[ab\uD83D\uDCA9]/;
u 플래그를 적용한 경우, 의도한 대로 유니코드도 매칭된다.
let regex = /^[ab💩]$/u;
regex.test('💩'); //-> true
u 플래그를 사용하면 astral 캐릭터로 레인지를 표현할 수도 있다.
let regex = /[💩-💫]/u; // U+1F4A9 부터 U+1F4AB 까지의 심볼을 매칭한다.
regex.test('💪'); //-> true (U+1F4AA)
regex.test('💬'); //-> true (U+1F4AC)
u 플래그를 생략하면 range 에러가 발생하는데,
실제로 아래처럼 첫 번째 글자의 마지막 서로게이트와 두 번째 글자의 첫 서로게이트의 범위를 설정하려고 하기 때문이다.
let regex = /[💩-💫]/; //-> throws error
let regex = /[\uD83D\uDCA9-\uD83D\uDCAB]/;
같은 맥락에서, ES5에서는 negate 캐릭터(^)도 astral 캐릭터를 매칭하지 못했다.
/^[^a]$/.test('💩'); //-> false
마찬가지로, 서로게이트 페어로 분리되어 매칭하기 때문이며,
/^[^a]$/u.test('💩'); //-> true
Character class escapes
몇 가지 예약된 클래스 이스케이프가 있는데, u 플래그에서도 위에서 언급한 예제와 비슷한 맥락으로 동작한다.
\d = [0-9]
\D = [^0-9]
\s = 공백 캐릭터
\S = 공백 캐릭터가 아닌 캐릭터
\w = [0-9a-zA-Z_]
\W = [^0-9a-zA-Z_]
/^[^\S]$/.test('💩'); //-> false
u 플래그가 없다면 마찬가지로 서로게이트 페어로 분리되어 매칭하지 못한다.
/^[^\S]$/u.test('💩'); //-> true
With i flag
i 플래그를 u 플래그와 함께 쓰는 경우, 기본 아스키 범위 외의 유니코드 문자열과 알파벳 대소문자를 매칭할 수 있다.
let es5regex = /[a-z]/i;
let es6regex = /[a-z]/iu;
let K = '\u212A'; // 기본 알파벳이 아닌 대문자 K
es5regex.test(K); //-> false
es6regex.test(K); //-> true
하지만 \W 와 함께 쓰는 경우엔 좀 헷갈리는 결과가 나온다.
let es5regex = /\W/i;
let es6regex = /\W/iu;
es5regex.test('S'); //-> false
es6regex.test('S'); //-> true
i 와 u 플래그가 동시에 쓰이면, \W 는 더이상 \w 의 반대값으로 동작하지 않는다.
HTML 문서 내에서의 패턴
HTML에서는 u 플래그가 항상 적용된 상태로 동작한다.
<style>
:invalid { background: red; }
:valid {background: green; }
</style>
<input pattern="a.b" value="aXXb"> //-> red background
<input pattern="a.b" value="a💩b"> //-> green background
u 플래그 사용 가능 환경
지원 브라우저
- Microsoft Edge/Chakra (HTML 패턴 제외)
- V8 (옵션으로 --harmony-unicode-regexps 전달)
예를 들어, 커맨드라인에서 테스트해보고 싶다면 아래와 같이 실행하면 된다.
$ node --harmony_unicode_regexps
빌드 도구
- Babel 같은 빌드 도구에서 u 플래그 적용 여부에 대한 플래그가 있음
다른 라이브러리 사용
- regexpu (https://github.com/mathiasbynens/regexpu)
- XRegExp (http://xregexp.com/)
반응형