Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2024年04月22日] [更新 2024年08月10日]* Delphi 一般・その他 [未分類 - OS の操作等]
JavaScript における文字列の文字数と文字単位操作
動作確認等 Windows 10 ビルド 19043 Pro 64 bit
Google Chrome 124, Microsoft Edge 124, Firefox 125
[整理番号 - 1916]







JavaScript における文字列の文字数と文字単位操作


JavaScript にえける文字列の「文字数」に関するオンライン形式のサンプルです。実際に文字を入力して動作を確認できます。

本ページのサンプルでは、以下のような <input> 要素の入力欄に文字列を入力します。入力した文字列を専用の関数で処理します。表示用のフォントには「游ゴシック」を指定しています。




  <input id="input-1" value="" type="text">



サンプルの JavasScript のコードは、CSS も含めて全て HTML のソースコード内にあります。







01_length プロパティの値はエレメント数


String オブジェクトの length プロパティは文字列のエレメント数を取得します。
1 エレメントは、UTF-16 で文字を扱う際のコードポイントの最小単位である 16 ビット ( 2 バイト) てず。本サンプルではこのことを確認します。JavaScript は 文字符号化方式として Unicode の UTF-16 を使用します。

String オブジェクトの length プロパティを使用して、文字列のエレメント数を取得する関数は以下のように作成しています。



  <script>
    function str_length () {
      // input の value 属性に入力した文字列を取得
      const srcstr = document.getElementById('input-1').value;

      // その文字列の長さ (エレメント数) を取得
      const strlen = srcstr.length;

      for (i = 0; i < strlen; i++) {
        // 文字列インデックスが I の文字を str に取り出して各種の処理
        // 本ページのサンプルでは結果の表の作成
        var str = srcstr[i];
      }
    }
  </script>


本ページを表示すると、文字「東」と Unicode Emoji の文字を表示して、その文字列の各エレメントの文字とコードポイントを右の表に表示します。
Emoji の文字のエレメント数は 15 ですが、文字が表示されるのは 7 番目 (インデックスは 6) だけです。この文字は、表示に使用するフォントを Segoe UI Emoji にするとカラー絵文字になります。これ以外のコードポイントには文字はありまません。

コードポイントが U+FFFF 以内の、エレメント数が 1 つだけの文字だけであれば、文字数とエレメント数は同じになります。









02_スプレッド構文による文字配列はサロゲートペアを格納する


スプレッド構文は、文字列の各文字を要素とした文字配列を作成します。この時、サロゲートペアの文字は、配列の 1 つの要素として格納されます。結合文字は各々の文字が別の配列要素に格納されます。異体字セレクタを使用する文字は、基底文字と異体字セレクタが別々に格納されます。
スプレッド構文は以下のように書きます。



    文字配列 = [...文字列];



本サンプルは、<input> 要素に文字列を入力すると、以下の関数を実行して、文字配列の各要素の文字とそのコードポイントを表として表示します。
赤の下線部分がスプレッド構文で文字列の文字を文字配列に格納するコードです。


<script>
  <script>
    function str_spread () {
      // input の value 属性に入力した文字列を取得 
      const srcstr = document.getElementById('input-1').value;

      // スブレット構文を使用して文字列の各文字を配列に格納
      // サロゲートペアは配列の 1 つの要素となる
      const strarray = [...srcstr];

      // 文字配列の長さ (要素数) を取得
      const arylen = strarray.length;

      for (i = 0; i < arylen; i++) {
        // 文字配列のインデックス I の文字を str に取り出して各種の処理
        // 本ページのサンプルでは結果の表の作成
        var str = strarray[i];
      }
    }
  </script>


本ページを表示すると、サロゲートペアの文字を含む 7 つの文字を入力部に表示します。文字配列の要素数も 7 ですから、配列の要素数が文字数となります。「𠮷」と「𠮟」がサロゲートペアです。サロゲートペアのコードポイントは 16 進数の 5 桁です。

Unicode Standard に登録されている文字は、2 バイト文字とサロゲートペアのもじだけです。したがって、Unicode Standard に登録されている文字だけであればこの方法で文字数を取得できます。









03_Intl.Segmenter オブジェクトによる文字単位操作


Intl.Segmenter オブジェクトを使用すると、文字列の各文字を「見た目」の文字単位で操作できるようになります。Firefox 119.0 (記事掲載時点) にはこの Intl.Segmenter は実装されていません。


最初に Intl.Segmenter オブジェクトのインスタンス (実体) を生成します。入力した文字列をこのインスタンスの文字列として、スブレット構文を使用して文字配列を作成します。
この文字配列には、サロゲートペア単位、結合文字は結合文字単位、異体字セレクタを使用している文字は異体字セレクタを含めた文字として各配列要素に格納されます。

本サンプルは、<input> 要素で入力した文字列を以下のような関数で処理しています。文字列の i + 1 番目の文字を、文字配列 strarray から var str = strarray[i].segment; というコードで取得しています。


  <script>
    function str_segmenter () {
      // input の value 属性に入力した文字列を取得
      const srcstr = document.getElementById('input-1').value;

      // Segmenter オブジェクトを生成
      // granularity を grapheme にすると文字単位
     var segments = new Intl.Segmenter("ja", { granularity: "grapheme" });

      // Segmenter オブジェクト形式の文字配列をスプレッド構文で生成
      // 配列の各要素に 1 つの文字が格納される
      const strarray = [...segments.segment(srcstr)];

      // 文字配列の長さ (文字数) を取得
      const arylen = strarray.length;

      for (i = 0; i < arylen; i++) {
        // 文字配列のインデックス I の文字を str に取り出して各種の処理
        // 本ページのサンプルでは結果の表の作成
        var str = strarray[i].segment;
      }
    }
  </script>


本ページを表示すると、左側の入力欄に Unicode Emoji の 3 文字を含む 9 文字の文字列を表示します。それらの文字のエレメント数を右の表に表示します。
「𠮷𩸽𠮟」はサロゲートペアの文字です。「葛󠄀」は異体字セレクタを使用している文字です。「あ゙か゚」は結合文字の例です。サロゲートペアの文字であるか、異体字セレクタを使用している文字であるか、結合文字であるかは、前項のサンプルで確認できます。

このサンプルでは、文字配列の要素数が文字数となります。







ユニコードコンソーシアムでは、文字とコードポイントの関係についての資料を公開しています。



JavaScript の汎用ライブラリである Node.js には Segmenter の機能が実装されているようですが、筆者は確認していません。
Node.js は、サーバサイドのライブラリです、サーバにモジュールをインストールして使用します。筆者が利用しているレンタルサーバではモジュールのインストールはできません (許可されていません)。



[備考]
Firefox は、Firefox 125 でこの Intl.Segmenter が実装されたようです。




04_サロゲートペア・結合文字・異体字セレクタ・Emoji


2 番目のスプレッド構文によって取得される各文字のコードポイントについての説明です。サロゲートペア、結合文字、異体字セレクタを使用する文字、Unicode Emoji に関する説明です。

一般的に使用されている文字の多くは、Unicode では 2 バイトの文字です。例えば、日本語を扱う際の基本的な文字である ASCII 文字と JIS X 0208 で制定されている文字は全て 2 バイトの文字です。2 バイトの文字は、16 進数で 4 桁の数値です。




[ サロゲートペア ]

コードポイントが 16 進数で 5 桁の文字はサロゲートペアの文字です。
最初に制定された Unicode の文字は 2 バイトの文字だけでした。その後、より多くの文字を扱えるように 2 バイトを追加したのがサロゲートペアの文字です。文字通り、2 つのエレメントのペアとなっています。サゲートペアの上位 2 バイトは U + D800 から U + DBFF の範囲の値です。下位 2 バイトは U + DC00 から U + DFFF の範囲の値です。これらに具体的な文字はありません。16 進数の 5 桁のコードポイントが Unicdoe Standard に登録されています。それに文字が割り当てられています。


文字 登録コードポイント (16 進数 5 桁) サロゲートペア
𠮷 U+20BB7 U+D842 U+DFB7
𩸽 U+29E3D U+D867 U+DE3D
𠮟 U+20B9F U+D842 U+DF9F



[ 結合文字 ]

結合文字は 2 つ以上のコードポイントで構成されている文字です。結合文字は、各々のコードポイントに具体的なグリフ (文字) があります。それらのグリフは Unicode Standard に登録されています。
以下の 2 番目は、基底文字「あ」に結合可能な濁点「゛」を結合した文字です。


文字 コードポイント 説明
あ゛ U+3042 U+309B 単純に基底文字と濁点を続けて書いただけ
あ゙ U+3042 U+3099 基底文字の後に、結合可能な濁点を結合した文字
あ ゙ U+3042 U+200A U+3099 結合文字をヘアスペース U+200A で分離



[ 異体字セレクタ ]

異体字セレクタを使用する文字は、2 つのコードポイントで構成されています。最初のコードポイントには具体的な文字があります。これを基底文字と言います。次のコードポイントは 、漢字の場合は U+E0100 ~ U+E01EF の範囲の値で、漢字以外は U+FE00 ~ U+FE0F です。これを異体字セレクタと言い、この値によって異体字を区別します。
異体字セレクタを使用する文字は、人名や地名などの異体字として登録されています。



文字 コードポイント
朝󠄂 U+671D U+E0102
朝󠄃 U+671D U+E0103
朝󠄄 U+671D U+E0104


この表の異体字セレクタ付きの異体字をアプリで利用するには、それらの異体字を収録した IPAmj明朝 フォントが必要です。


基底文字 異体字セレクタ付きの文字
IPAmj明朝 Yu Gothic UI
朝󠄂 朝󠄃 朝󠄄 朝󠄂 朝󠄃 朝󠄄



[ Unicode Emoji (絵文字) ]

以上の他、Unicode Emoji として登録されている文字があります。Unicode Emoji の文字は 1 つ以上のコードポイントで構成されています。
以下は Emoji の例です。構ゼロ幅接合子 U+200D を、ゼロ幅スペース U+200B、またはヘアスペース U+200A に変更したり、文字の間にそれらを挿入すると、Emoji を分解表示できます。
以下の表の Emoji は、Noto Color Emoji フォントを指定しています。



文字 コードポイント
🕵🏼‍♂ U+1F575 U+1F3FC U+200D U+2642
🕵🏼​♂ U+1F575 U+1F3FC U+200B U+2642
🕵​🏼​♂ U+1F575 U+200B U+1F3FC U+200B U+2642
🕵 🏼 ♂ U+1F575 U+200A U+1F3FC U+200A U+2642


国旗の Emoji の例です。


文字 コードポイント
🇯🇵 U+1F1EF U+1F1F52
🇯​🇵 U+1F1EF U+200B U+1F1F5
🇯 🇵 U+1F1EF U+200A U+1F1F5


[備考 1]
ゼロ幅スペースとヘアスペースの挙動は、アプリ (ウェブブラウザもアプリの一種) によって異なります。使用する場合は確認が必要です。

[備考 2]
結合文字もアプリによって挙動が異なる場合があります。結合状態にはなっても 1 つの文字ではなく、分離状態になっていることがあります。




05_文字単位と書記素クラスタ ( 書記素 )


結合文字は 2 つ以上の文字が結合して 1 つの文字となっています。通常、結合した結果の文字を 1 文字と言っています。Emoji は、1 つ以上のコードポイントの組み合わせとして別の文字を描画します。これも 1 文字と言っています。このような文字単位を、書記素クラスタ、あるいは単に書記素と言います。1 つの書記素が、我々が言う「見た目」の 1 文字です。

ワープロソフトやメモ帳などのテキストエディタで文字を扱う場合、文字の入力位置にキャレットが表示されます。このキャレットの移動単位は書記素クラスタ (書記素) 単位です。
Intl.Segmenter オブジェクトを使用すると、書記素で文字を扱えます。

Intl.Segmenter オブジェクトを使用した場合、サロゲートペアの文字のコードポイントは以下のような処理で取得できます。


  <script>              
    var segments = new Intl.Segmenter("ja", { granularity: "grapheme" });

  //--------------------------------------------------------------------------
  //  文字のコードポイントを HTML コードとして取得する関数
  //  サロゲートペア対応
  //--------------------------------------------------------------------------
    function getCodepointHex (srcstr) { 
      var strarray = [...segments.segment(srcstr)]; 
      var hextext = '';     
    
      var strlen = strarray.length;
      for (i = 0; i < strlen; i++) {     
        var str = strarray[i].segment;
        // str[0].codePointAt(0); だと、サロゲートペアの最初の 2 バイトの取得
        var code = str.codePointAt(0);
        hextext += 'U+' + code.toString(16).padStart(4, '0').toUpperCase() + '
'; } return hextext; } </script>


上のサンプルは、2 バイト文字とサロゲートペアの文字だけの場合に有効です。
文字列のコードポイントの取得だけが目的の場合は、2 番目のスプレッド構文を使用します。見た目の文字ごと、つまり、書記素単位でコードポイントを取得したい場合は、各々の書記素に対してスプレッド構文を使用します。





06_テキスト ( 文字列 ) のバイナリデータ


文字列のメモリ上やディスク内のデータ、つまり、文字列のバイナリデータは、1 番目の処理のように、元の文字列の各エレメント srcstr[i] から、srcstr[i].codePointAt(0) で所得していけます。
この場合、1 エレメントは 2 バイトですから、文字列が使用するバイト数は、UTF-16 では制御文字も含めてエレメント数の 2 倍ということになります。


[備考]
メモ帳などのテキストエディタでテキストを保存すると、メモリ上のデータが保存されます。つまり、このようなバイナリデータが保存されます。画面に見えている文字そのものが保存されるわけではありません。
メモ帳などのテキストエディタで保存したファイルを開くと、メモ帳などのテキストエディタは、それらのバイナリデータを文字コードの値と解釈して処理します。その結果、文字として画面に表示されます。