[ 新規に投稿する ]

文字列値の長さの取得方法についてNo.10795
Fzok4234 さん 26/01/23 18:53 [ コメントを投稿する ]
  毎度お世話になっております。Fzok4234 です。


さて、マクロ上での文字列変数値の文字の長さを取得する際に、
・"a" などの ASCII 文字 : 1 文字
・"あ" などの Shift-JIS 文字 : 2 文字
・"ꀀ" などの Unicode 文字 ( UTF-16 でサロゲートペアではない ) : 4 文字
・"👩" などの Unicode 文字 ( UTF-16 でサロゲートペアに該当 )  : 8 文字
として取得するにはどうすればよいのでしょうか ?

要は、「秀丸単位(座標) のプロポーショナルフォント扱い」での文字数の取得です。この単位に
対応した strlen() 系の関数が存在しないため、なるべくオーバーヘッドの少ない方法で取得する
方法が思いつかない状態です。

実際の用途としては、.hilight ファイルの自動編集において、ツリー定義の「内容」のフィールドに
記述する文字列の長さが最大文字数を超えていないかどうかを判定することが挙げられます。この
文字数制限がまさに「秀丸単位(座標) のプロポーショナルフォント扱い」で 250 文字と規定されて
いるためです。


[ ]
RE:10795 文字列値の長さの取得方法についてNo.10796
igus さん 26/01/27 06:22 [ コメントを投稿する ]
  // chatgptに聞いてみて提示してくれたマクロをちょっと手直ししてみました。
//文字数カウント.mac
$s=getselectedtext();

js{
  var s = getVar("$s");
  var bytes = 0;
  for (var i = 0; i < s.length; i++) {
    var c = s.charCodeAt(i);
    if (c <= 0x7F) {
      bytes += 1;
    } else if (c <= 0x7FF) {
      bytes += 2;
    } else if (c >= 0xD800 && c <= 0xDBFF) {
      // サロゲートペア
      i++;
      bytes += 8;
    } else {
      bytes += 4;
    }
  }
  setVar("$b", bytes);
}

message("選択範囲の文字数: " + $b);
endmacro;
[ ]
RE:10795 文字列値の長さの取得方法についてNo.10797
秀丸担当 さん 26/01/27 09:14 [ コメントを投稿する ]
  以前もそういう話がって、プロポーショナルフォントのx相当でするか、igusさんのやり方の似た方法で何らかの計算をするしかないと思っていたのですが、それ専用の関数があるのを忘れていました。
charindex_to_byteindexという関数で、数え方の変換が可能です。
[ ]
RE:10796 文字列値の長さの取得方法についてNo.10798
Fzok4234 さん 26/01/27 10:02 [ コメントを投稿する ]
  やはり 1 文字ずつ文字種でふるい分けする必要があるみたいですね。

当方でも Gemini 3 で JavaScript のサンプルコードを生成してみると、以下のようになりました。

/**
 * UnicodeコードポイントからShift-JIS(CP932)のバイト数、
 * またはサロゲートペアの有無を判定する
 * * @param {number} cp - Unicodeコードポイント (例: 0x3042)
 * @returns {number} 1, 2, 4, 8
 */
function getSjisByteType(cp) {
    // 1. Shift-JIS (CP932) 1byte文字
    // ASCII (00-7F) + 半角カタカナ (FF61-FF9F)
    if ((cp >= 0x0000 && cp <= 0x007F) || (cp >= 0xFF61 && cp <= 0xFF9F)) {
        return 1;
    }

    // 2. UTF-16 サロゲートペアに該当するか (U+10000 以上)
    // CP932は基本多言語面(BMP)のみをサポートするため、これらは自動的に「含まれない」かつ「サロゲートペア」
    if (cp >= 0x10000) {
        return 8;
    }

    // 3. Shift-JIS (CP932) 2byte文字の判定
    // CP932に含まれるBMP内の主要な範囲を網羅
    const isSjis2Byte = (
        (cp >= 0x4E00 && cp <= 0x9FFF) || // CJK統合漢字 (第1・第2水準)
        (cp >= 0x3000 && cp <= 0x30FF) || // ひらがな・カタカナ・和文記号
        (cp >= 0xFF01 && cp <= 0xFF60) || // 全角英数・記号 (一部)
        (cp >= 0xFFE0 && cp <= 0xFFE6) || // 全角記号 (円記号・文字など)
        (cp >= 0x2116 && cp <= 0x266F) || // NEC特殊文字・記号・罫線
        (cp >= 0xF900 && cp <= 0xFAFF) || // CJK互換漢字 (IBM拡張)
        (cp >= 0xE000 && cp <= 0xE757) || // 外字領域 (PUA)
        // 個別の記号やギリシャ・ロシア文字など
        (cp >= 0x0370 && cp <= 0x04FF) || 
        (cp === 0x00A2 || cp === 0x00A3 || cp === 0x00A5 || cp === 0x00AC || cp === 0x00B0 || 
         cp === 0x00B1 || cp === 0x00B4 || cp === 0x00B6 || cp === 0x00D7 || cp === 0x00F7)
    );

    if (isSjis2Byte) {
        return 2;
    }

    // 4. BMP内(サロゲートペアではない)かつ CP932に含まれない文字
    return 4;
}

ただ、この判定方法は完全なものではなく、漢字領域の U+4E00 〜 U+9FFF の範囲内に「虫食い」の
ように存在する
・第 3・第 4 水準漢字など BMP 内における、Unicode には存在するが CP932 には存在しない漢字。
は考慮されていません。

厳密な判定を行うためには、JavaScript のハッシュテーブルである Set オブジェクトに
・CP932 の全 2 バイト文字の Unicode コードポイント。
を登録して、has() メソッドで照合を行う、という手法を取らないといけません。一度生成した Set オブジェクトへの
照合は O(1) の計算量でオーバーヘッドは非常に小さいのですが、 Set オブジェクトに約 7500 文字ある 2 バイト文字の
コードポイントを登録することは、明らかに大きなオーバーヘッドとなります。


[ ]
RE:10797 文字列値の長さの取得方法についてNo.10799
Fzok4234 さん 26/01/27 10:08 [ コメントを投稿する ]
  行き違いになってしまいましたね。

既に関数が用意されていたのであれば、どうやら杞憂だったようです。いつの間にか対応してくださって
ありがとうございます。


[ ]

[ 新規に投稿する ]