小道具： String.normalize 結果の可視化

# 概要

文字列の変換メソッド normalize() によって変更される文字の背景色を変更して可視化します。

文字コード確認では「[小道具：rubyタグを使って文字の上にコードを出力](https://qiita.com/ikiuo/items/163e795dd8bcf82f9634)」を用意しています。

<p class="codepen" data-height="488" data-default-tab="result" data-slug-hash="LYJBJYZ" data-user="ikiuo" style="height: 488px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/ikiuo/pen/LYJBJYZ">
  String.normalize メソッドのテスト</a> by Ikiuo (<a href="https://codepen.io/ikiuo">@ikiuo</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

# ソースコード

```html:
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>String.normalize 結果の可視化</title>
  </head>
  <body>
    <script>

      const NormalizeTable = ['NFC', 'NFD', 'NFKC', 'NFKD'];

      function stringForHTML(inp) {
          const m = { '<':'&lt;', '&':'&amp;', '>':'&gt;', '\n':'<br/>' };
          return [...inp].map((c) => m[c] ?? c).join('');
      }

      function stringToChars(mode, inp) {
          return [...inp].map((c) => {
              const n  = c.normalize(mode);
              return { chk:(c == n), inp:c, out:n }
          });
      }

      function checkComposite(mode, inp, start, end) {
          let pos = start;
          if (!inp[pos].chk)
              return 1;

          let s = inp[pos].inp;
          let t = s;
          while (++pos < end) {
              const c = inp[pos];
              if (!c.chk)
                  break;
              s += c.inp;
              t = s.normalize(mode);
              if (s == t)
                  continue;
              const sc = [...s];
              if (sc.length == 1)
                  continue;
              const tc = [...t];
              if (sc.pop() != tc.pop())
                  continue;
              s = sc.join('');
              t = tc.join('');
              break;
          }

          const len = pos - start;
          {
              const sc = [...s];
              const tc = [...t];
              let scnt;
              for (scnt = 0; scnt < sc.length; scnt++)
                  if (sc[scnt] != tc[scnt])
                      break;
              start += scnt;
          }
          if (start < pos) {
              s = '';
              for (let p = start; p < pos; p++)
                  s += inp[p].inp;
              t = s.normalize(mode);

              inp[start].chk = false;
              inp[start].out = t;
              while (++start < pos) {
                  inp[start].chk = false;
                  inp[start].out = '';
              }
          }
          return len;
      }

      function checkNormalize(mode, inp) {
          if (!inp)
              return '';

          const chars = stringToChars(mode, inp);
          let pos = 0;
          let end = chars.length;
          while (pos < end)
              pos += checkComposite(mode, chars, pos, end);

          chars.forEach((c) => c.out = stringForHTML(c.out));
          return chars.map(
              (c) => c.chk ? c.out : !c.out.length ? '' :
                  `<span style="background-color: #fbb">${c.out}</span>`
          ).join('');
      }

      function onInput(text) {
          NormalizeTable.forEach((m) =>
              document.getElementById(m)
                  .innerHTML = checkNormalize(m, text));
      }

      // 要素生成は CodePen のトラブル対策.
      window.onload = function() {
          document.body.innerHTML = [
              '<table border="1">',
              '<tr><th>入力</th><td>',
              '<textarea rows="6" cols="80" oninput="onInput(this.value)"></textarea>',
              '</td></tr>',
              NormalizeTable.map((v, n) => `<tr><th>${v}</th><td id="${v}"></td></tr>`),
              '</table>',
          ].flat().join('');
      }

    </script>
  </body>
</html>
```

---

# 参考

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/normalize