AngularJS でngBindHtmlを使用すると、ngSanitize(angular-sanitize.js)の encodeEntities に行き着く(はず)。そこでは下記のような処理が行われています。
function encodeEntities(value) {
return value.
replace(/&/g, '&').
replace(NON_ALPHANUMERIC_REGEXP, function(value){
return '&#' + value.charCodeAt(0) + ';';
}).
replace(//g, '>');
}
NON_ALPHANUMERIC_REGEXP は、/([^\#-~| |!])/g;でマッチした値。文字列はこの処理を通る感じになる。charCodeAt の処理はcharCodeAt - MDNを参照。
Note that charCodeAt will always return a value that is less than 65,536. This is because the higher code points are represented by a pair of (lower valued) “surrogate” pseudo-characters which are used to comprise the real character.
charCodeAt はつねに 65,536 以下の値が返ってくるようになっていて、それ以上のコードポイントの場合は、サロゲートペアの状態になってくる。サロゲートペアについては、サロゲートペア入門の「サロゲートペアとは?」を参照。
なので、ngBindHtml で絵文字のように 65,356 以上のコードポイントを持ってそうな文字列を通すと、サロゲートペアをひとつずつ実体参照の状態にしてしまい、うまく表示できない。
ngBindHtml でそのような文字列を表示するためには、なんとかしないといけない。それでとりあえずeller86/surrogate-pair.jsを使って surrogate pair を処理するように対応。
function handleSurrogatePair(content) {
if (sp.findSurrogatePair(content)) {
content = $sce.getTrustedHtml(content);
var match = content.match(/\&\#5[5-6][0-9]{3};\&\#5[6-7][0-9]{3};/g),
i = 0,
len = match.length,
str;
for (; i < len; i++) {
str = match[i].match(/[0-9]+/g);
if (sp.checkHighSurrogate(str[0])) {
content = content.replace(
match[i],
"&#" + ((str[0] - 0xd800) * 0x400 + (str[1] - 0xdc00) + 0x10000) + ";"
);
}
}
return content;
} else {
return $sce.trustAsHtml($sce.getTrustedHtml(content));
}
}
$sceはStrict Contextual Escaping services to AngularJSを参照。対処療法に過ぎないので、あんまりいい感じではないけど、一応表示できるようになる。
そもそも、ngSanitize で surrogate pair を適切に処理するように pull request を作るべきなんだろうなあと思いつつ、そこまでいたってない。そして基本的には、ngBindHtml を使わずに ngBind だけでうまく設計する方がいい。ngSanitize の処理量半端ない。
というメモ。走り書き。
ngSanitize cant escape all unicode characters ? Issue #5088 ? angular/angular.jsに関連 issue があった。
fix(ngSanitize): encode surrogate pair properly by memolog ? Pull Request #6911 ? angular/angular.jsで Pull Request を送って、1.3.x ではサロゲートペアに対する処理が追加されました。ので、絵文字表示できない問題は起きない。