メモログ

ngBindHtmlとサロゲートペア

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 ではサロゲートペアに対する処理が追加されました。ので、絵文字表示できない問題は起きない。

私について

Yutaka Yamaguchi
東京在住。TypeScript, Node.js, Reactなどフロンエンドが主力。Perlも書く。SwiftやRubyも過去には使ってた。過去のTOEIC 860くらい。