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