検索ヒット数表示(選択文字列)

提供: MeryWiki
ナビゲーションに移動 検索に移動

単独マクロバージョン[編集]

  • アクティブなタブの文書内から選択範囲の文字列に一致するものをカウントします。
  • ヒット件数はステータスバーに表示します。
選択範囲のある状態でマクロを手動で実行するか、「選択範囲が変更されたとき」のイベントマクロに設定して自動で実行させることができます。



設定項目: countMinimum
Mery の検索ではつねにヒットした文字列の「先頭位置より1文字うしろ」から再検索しますが、このマクロではヒットした文字列の「末尾位置」から再検索できます。


e.g.「あああああ」5文字だけの文書で「ああ」を検索した場合、このマクロでは "2件" とカウントさせることができますが、Mery の検索メソッド(次/前を検索)では "4回" ヒットします。
また、「あいあいあ」5文字だけの文書で「あいあ」を検索した場合は、このマクロでは "1件" とカウントさせることができます(Mery の検索メソッドでは "2回" ヒット)。
→ 少なくカウントする設定にした場合、「何件目」かの表示は、上の例のような範囲選択をしている場合には不正確な値を返します。


設定項目: matchCase
文字列のヒット判定では「大文字と小文字を区別する」を適用するかどうかを選択できます。



※ イベントマクロで利用する場合の処理速度を優先させるため、正規表現での検索処理が必要な「単語のみを検索する」は適用できません。
※ イベントマクロで利用する場合は「遅延時間」を適宜調整しないと、大きな文書の編集のさいに動作が "もたつく" ことがあります。

機能強化バージョン を追加(2019/08/26 → 更新 2019/12/03)

ソースコード[編集]

2019/12/03 更新

  • 全体のヒット件数のうしろに「何件目」のヒットかを追加
  • ヒット数のカウント方法を設定変数で指定
#title = "ヒット件数(選択文字列)"
#tooltip = "検索ヒット数表示(選択範囲の文字列)"
// #icon = "Mery用 マテリアルデザインっぽいアイコン.icl",19

/**
 * アクティブなタブの文書内から選択範囲の文字列に完全一致するものをカウントする。
 * ヒット件数はステータスバーに表示する。
 * 
 * 選択範囲のある状態でマクロを手動で実行するか
 * イベント「選択範囲が変更されたとき」に設定して自動で実行させる。
 */

var start = new Date();

// ■ 「あああああ」にたいして「ああ」のヒット数 (ture: 2 件、 false: 4 件)
var countMinimum = true;	// Mery の検索方式にあわせるなら false に

// ■ 大文字と小文字を区別するか? (ture: する、 false: しない)
var matchCase = false;

// ■ ヒット数検索の所要時間を表示するか?
var timerEnable = true;

var d = editor.ActiveDocument, s = d.selection;
var word = matchCase ? word : word.toLowerCase();

if ( word ) {
  Status = "";
  var docu = matchCase ? d.Text : d.Text.toLowerCase();
  var tPos = Math.min( s.GetActivePos(), s.GetAnchorPos() );
  var count = 0, hit = 0, append = "*";
  var next = countMinimum ? word.length : 1;
  var pos = docu.indexOf( word );
  while ( pos >= 0 ) {
    count ++;
    if ( pos <= tPos ) {
      hit = count;
      if ( pos == tPos ) { append = ""; }
    }
    pos = docu.indexOf( word, pos + next );
  }

  Status = ( " ヒット数:" + count + " 件"
             + " ( " + hit + " 件目" + append + " ) "
           ).replace( /(\d)(?=(?:\d{3})+(?!\d))/g, "$1," );
  if ( timerEnable ) {
    Status += "  [ "
            + ( ( new Date() - start ) / 1000 ).toFixed( 3 )
                                               .replace( /\./, ". " )
            + " 秒 ]";
  }
}


組み込み用関数バージョン[編集]

2019/07/20 追加、 2019/12/03 更新

  • 引数に指定した文字列の出現回数 "ヒット数:n 件 ( m 件目 ) " を返します。


※ 動作仕様・制限事項は 単独マクロバージョン に準じます。

使用例[編集]

// 選択範囲の文字列のヒット数をステータスバーの表示内容に追加
var word = document.selection.Text;
if ( word ) {
  Status += "  " + HitStatus( word, true, false );
}

組み込み関数コード[編集]

/* 
 * 関数 HitStatus( word, matchCase, countMinimum )
 * 引数に指定した文字列の出現回数 "ヒット数:n 件 ( m 件目 ) " を返す
 * 
 * 第一引数 str: 選択範囲の文字列などの「文字列」オブジェクト
 * 第二引数 matchCase: 大文字と小文字を区別するなら true、区別しないなら false
 * 第三引数 countMinimum:「あああああ」にたいして「ああ」のヒット数を
 *                        2 件とカウントするなら true、4 件とカウントするなら false
 */
function HitStatus( word, matchCase, countMinimum ) {
  word = matchCase ? word : word.toLowerCase();
  var d = editor.ActiveDocument, s = d.selection;
  var docu = matchCase ? d.Text : d.Text.toLowerCase();
  var tPos = Math.min( s.GetActivePos(), s.GetAnchorPos() );
  var count = 0,  hit = 0, append = "*";
  var next = countMinimum ? word.length : 1;
  var pos = docu.indexOf( word );
  while ( pos >= 0 ) {
    count ++;
    if ( pos <= tPos ) {
      hit = count;
      if ( pos == tPos ) { append = ""; }
    }
    pos = docu.indexOf( word, pos + next );
  }
  return ( " ヒット数:" + count + " 件"
           + " ( " + hit + " 件目" + append + " ) "
         ).replace( /(\d)(?=(?:\d{3})+(?!\d))/g, "$1," );
}


機能強化バージョン[編集]

2019/08/26 追加、2019/12/03 更新

ヒット件数表示の 単独マクロバージョン に以下の機能を追加しています。

  • 選択範囲の文字数と行数を表示
  • 選択範囲の文字列のバイト数(半角=1バイト、全角=2バイト でカウント)
  • 「すべて選択」しているときにファイルサイズを表示
  • 設定項目から表示方法や検索方法などを変更可

※ 動作仕様の説明は ソースコード末尾 に記載してあります。

ダウンロード[編集]

2019/12/03 更新

ダウンロード >> 「ファイル:文字数・行数・バイト数・ヒット件数.zip」(アイコン入り)


ソースコード[編集]

#title = "文字数・行数・バイト数・ヒット件数"
#tooltip = "文字数・行数・バイト数・選択文字列の検索ヒット数表示"
// #icon = "Search[2].ico"
// #icon = "Mery用 マテリアルデザインっぽいアイコン.icl",19

/**
 * --------------------------------------------------------------------
 * 文字数・行数・バイト数・ヒット件数(選択文字列)
 * sukemaru ( 2019/08/26 - 2019/12/03 )
 * --------------------------------------------------------------------
 * 編集中の文書または選択範囲の文字数と行数をステータスバーに表示します。
 * 選択範囲があるときは、選択範囲の文字列の出現回数を表示します。
 * 文字数からのバイト数の簡易計算、ファイルサイズも表示できます。
 * 
 * 任意のタイミングで手動で実行するか、
 * イベント「選択範囲が変更された時」などに設定して自動で実行させる想定です。
 * 
 * ※ ソースコード末尾に能書き(動作仕様/制限事項)あり。
 */

var timerStart = new Date();
var d = editor.ActiveDocument;


// ---------- ▼ 設定項目 ▼ ---------- //

// ■ 行数のカウント方法
// true:論理行(改行) / false:物理行(折り返し)
var logical = false;		// 初期値: false(Mery 方式)

// ■ 行数のカウント での選択範囲の末尾の改行記号
// true:次の行頭を含めない / false:含める
var ignoreEnter = false;	// 初期値: false(Mery 方式)

// ■ 文字数のカウント での改行記号 CR+LF の数え方
// true:2文字あつかい / false:1文字あつかい
var countCRLF = false;		// 初期値: false(Mery 方式)

// ■ バイト数・ファイルサイズを表示
// true:表示する / false:表示しない
var bytesEnable = true;		// 初期値: true

// ■ 文書全体の「文字数」または「行数」で動作制限
// 「範囲選択なし」のときのバイト数計算をしない
var limitChars = 150000;	// 150,000 文字超
var limitLines =  10000;	//  10,000 行超

// ※ 特定のファイル名などの条件でも動作制限するなら120行目付近に記述する


// ■ ヒット数のカウント方法
// true:大文字と小文字を区別する / false:区別しない
var matchCase = false;		// 初期値: false( ← 自動マーカーの初期値)

// ■ 「あああああ」 5文字だけの文書で 「ああ」 を範囲選択したときのカウント方法
// true:ヒット数 2件 / false:ヒット数 4件
var countMinimum = false;	// 初期値: false( ← Mery 方式)


// ■ マクロの処理の所要時間を表示
// true:表示する / false:表示しない
var timerEnable = true;	// 初期値: false


/* 未保存文書のファイルサイズの計算用 */
// ■ 文字コードの形式の指定
// "" なら文書の文字コード形式 / 指定時はその文字コード(e.g. "sjis", "utf-8")
var enc = "";		// 初期値: ""

// ■ 改行コードの形式の指定
// true: CR+LF / false: CR or LF / d.LineEnding === 0: 自動( ← 要 Mery 2.6.10 以上)
var eol = ( d.LineEnding === 0 );	// 初期値: ( d.LineEnding === 0 )

// ■ BOM コード分を加算するかの指定
// true: BOM を考慮する / false: 考慮しない
var bom = false;	// 初期値: false

// ---------- ▲ 設定項目 ▲ ---------- //


var s = d.selection;
var fullPath = d.FullName || "";
var isEmpty = s.IsEmpty;	// 「範囲選択なし」フラグ
var selAll = false;
var dLines = d.GetLines( logical ? 0 : meGetLineView );
var dt = d.Text,  st = "",  statusStr = "";

// ◆ 文字数・行数(論理行 or 表示行)
if ( isEmpty ) { st = dt; }	// 範囲選択なしなら文書全体
else {
  st = s.Text;				// 選択範囲
  selAll = ( st == dt );	// 「すべて選択」フラグ
  var posMode = logical ? mePosLogical : mePosView;
  var sLines = s.GetBottomPointY( posMode )
             - s.GetTopPointY( posMode )
             + ( ( ignoreEnter && /[\r\n]$/.test( st ) ) ? 0 : 1 );
}
// CR+LF 補正
var isCRLF = ( countCRLF && eol );
var sLen = st.length
         + ( isCRLF ? ( st.match( /\n/g ) || [] ).length : 0 );

var charStatus = " " + sLen + " 文字";
var lineStatus = " ( " + ( isEmpty ? dLines : sLines ) + " 行 )";
statusStr = charStatus + lineStatus;

// ◆ バイト数・ファイルサイズ
if ( bytesEnable ) {
  var byteStatus = "",  fileSizeStatus = "";

  // ◆ 範囲選択なしのとき、大きいファイルでは文書全体のバイト数を計算しない
  var limitation = (
      dLines    > limitLines	// 文書全体の「行数」で制限
   || dt.length > limitChars	// 文書全体の「文字数」で制限
// || d.Mode === "foo"				// 編集モードで制限
// || fullPath === "C:\\HOGE.txt"	// フルパス指定で制限
// || d.Name === "Fuga.txt"			// ファイル名指定で制限
// || fullPath.match( /\\piyo\\/i )	// フォルダ名指定(正規表現)で制限
  );

  if ( isEmpty && limitation ) {
    statusStr += "  ** Ctrl+A キーで全体のバイト数を表示 **";
  }
  // ◆ 文字数からバイト数を簡易集計(kuro 版「バイト数」マクロ)
  else {
    var bytes = CalculateBytesKuro( st, sLen );
    byteStatus = " / " + bytes + " バイト";
  }
  // ◆ 「すべて選択」しているときはファイルサイズを表示
  if ( selAll ) {
    var Fso = new ActiveXObject( "Scripting.FileSystemObject" );
    // ファイルサイズ(ファイルシステムから取得、または ks 版「バイト数」マクロ)
    var fileSize = ( d.Saved && fullPath && Fso.FileExists( fullPath ) )
                 ? Fso.GetFile( fullPath ).Size
                 : CalculateBytesKs( dt, enc, eol, bom );
    fileSizeStatus = " ( ファイルサイズ: " + fileSize + " バイト )";
  }
  statusStr += byteStatus + fileSizeStatus;
}

// ◆ 選択範囲の文字列のヒット数
if ( ! isEmpty && ! selAll ) {
  statusStr += "  /  " + HitStatus( st, dt, matchCase, countMinimum );
}

// ◆ ステータスバーに表示
Status = SeparateNum( statusStr );

// ◆ マクロの処理の所要時間
Status += timerEnable ? TimerElapsed( new Date(), timerStart ) : "";
// Quit();


// ---------- ▼ 関数 ▼ ---------- //

/**
 * 関数 SeparateNum( str )
 * 文字列中の数字を3ケタ区切りに
 */
function SeparateNum( str ) {
  return str.replace( /(\d)(?=(?:\d{3})+(?!\d))/g, "$1," );
}

/**
 * 関数 TimerElapsed( end, start )
 * マクロの処理の所要時間を計測
 */
function TimerElapsed( end, start ) {
  var elapsedSec = ( ( end - start ) / 1000 ).toFixed( 3 );
  return " [ " + elapsedSec.replace( /\./, ". " ) + " 秒 ]";
}

/**
 * 関数 HitStatus( strSelection, strDocuText, matchCase, countMinimum )
 * 選択文字列の出現回数を返す
 */
function HitStatus( word, docu, matchCase, countMinimum ) {
  docu = matchCase ? docu : docu.toLowerCase();
  word = matchCase ? word : word.toLowerCase();
  var s = editor.ActiveDocument.selection;
  var tPos = Math.min( s.GetActivePos(), s.GetAnchorPos() )
  var count = 0,  hit = 0, append = "*";
  var next = countMinimum ? word.length : 1;
  var pos = docu.indexOf( word );
  while ( pos >= 0 ) {
    count ++;
    if ( pos <= tPos ) {
      hit = count;
      if ( pos == tPos ) { append = ""; }
    }
    pos = docu.indexOf( word, pos + next );
  }
  return "ヒット数:" + count + " 件"
         + " ( " + hit + " 件目" + append + " ) ";
}

/**
 * 関数 CalculateBytesKuro( str, str.length )
 * kuro 版「バイト数」マクロのコードを関数化
 */
function CalculateBytesKuro( str, sLen ) {
  var bytes = 0;
  for ( var i = 0, c; i < sLen; i ++ ) {
    c = str.charCodeAt( i );
    bytes += ( ( c >= 0x0 && c < 0x81 ) ||	// ascii 英数字, 制御コード
               ( c >= 0xff61 && c < 0xffa0 ) ||	// 半角カナ
               ( c == 0xf8f0 ) || ( c >= 0xf8f1 && c < 0xf8f4 ) )
           ? 1 : 2;
  }
  return bytes;
}

/**
 * 関数 CalculateBytesKs( str, Encord, CRLF, BOM )
 * ks 版「バイト数」マクロのコードを関数化
 */
function CalculateBytesKs( str, enc, eol, bom ) {
  str = ( eol ) ? str.replace( /(?:\r|\r?\n)/g, "\r\n" ) : str;
  var charset = "",  bytes = 0;
  if ( ! enc ) {
    var dEnc = editor.ActiveDocument.Encoding;
    switch ( dEnc ) {
      case meEncodingShiftJIS:  charset = "shift_jis";  break;
      case meEncodingUTF8BOM:  case meEncodingUTF8NoBOM:
        charset = "utf-8";  break;
      case meEncodingUTF16LEBOM:  case meEncodingUTF16LE:
      case meEncodingUTF16BEBOM:  case meEncodingUTF16BE:
        bytes = str.length *2 + ( bom ? 2 : 0 );  return bytes;
      case meEncodingUTF16LENoBOM:  case meEncodingUTF16BENoBOM:
        bytes = str.length *2;  return bytes;
      case meEncodingUTF7:  charset = "utf-7";  break;
      case meEncodingEUC:   charset = "euc-jp";  break;
      case meEncodingJIS:   charset = "iso-2022-jp";  break;
      case meEncodingThai:  charset = "windows-874";  break;
      case meEncodingChineseSimplified:   charset = "gb2312";  break;
      case meEncodingKorean:              charset = "euc-kr";  break;
      case meEncodingChineseTraditional:  charset = "big5";  break;
      case meEncodingCentralEuropean:  charset = "windows-1250";  break;
      case meEncodingCyrillic:    charset = "windows-1251";  break;
      case meEncodingWesternEuropean:  charset = "windows-1252";  break;
      case meEncodingGreek:       charset = "windows-1253";  break;
      case meEncodingTurkish:     charset = "windows-1254";  break;
      case meEncodingHebrew:      charset = "windows-1255";  break;
      case meEncodingArabic:      charset = "windows-1256";  break;
      case meEncodingBaltic:      charset = "windows-1257";  break;
      case meEncodingVietnamese:  charset = "windows-1258";  break;
    }
  }
  else { charset = enc; }
  var Adodb = new ActiveXObject( "ADODB.Stream" );
  Adodb.Type = 2;  Adodb.Charset = charset;
  Adodb.Open();  Adodb.WriteText( str );
  bytes = Adodb.Position;
  Adodb.Close();
  if ( ! enc && ( dEnc == meEncodingUTF8NoBOM ||
       ( ! bom && dEnc == meEncodingUTF8BOM ) ) ) {
    bytes -= 3;
  }
  return bytes;
}

/**
 * 関数 GetIniOption( key )
 * 引数で指定された設定項目の「値」を返す(※ ascii 値のみ)
 */
function GetIniOption( key ) {
  // Mery.ini を探す
  var Fso = new ActiveXObject( "Scripting.FileSystemObject" );
  var iniPath = editor.FullName.replace( /\.exe$/i, ".ini" );
  if ( ! Fso.FileExists( iniPath ) ) {
    iniPath = new ActiveXObject( "WScript.Shell" ).SpecialFolders( "APPDATA" )
            + "\\Mery\\" + Fso.GetBaseName( editor.FullName ) + ".ini";
  }
  // Mery.ini を読み込む
  var iniFile = Fso.OpenTextFile( iniPath, 1 );
  var iniText = iniFile.ReadAll();
  iniFile.Close();
  // 項目の値を取得する
  var value = RegExp( "^" + key + "=([^\\r\\n]+)$", "m" ).exec( iniText )[1];
  iniText = "";
  // 10進数なら Number 型で返す
  return ( /^(?:0|-?[1-9][0-9]*)$/.test( value ) )
         ? Number( value ) : value;
}
/**
 * イベントマクロに設定する場合は、「選択範囲が変更された時」以外にも、
 * 「ファイルを開いた時」「テキストが変更された時」「文字が挿入された時」
 * 「アクティブな文書が変更された時」など、任意で。
 * 
 * ※ イベントの設定で「遅延時間」を適宜調整すること
 *  (とくに「カーソルが移動した時」を適用する場合は遅延を長めに)。
 * 
 * 「ヒット数のカウント方法」の設定項目「大文字と小文字を区別」は、
 * 自動マーカーの設定状態(初期値:false)にあわせるのがよろしいかと。
 * 表示メニュー >> マーカー >> マーカーの設定... >> オプション
 * 
 * 
 * ▼ 動作仕様 / 制限事項 ▼
 * 
 * ※ 行数のカウント方法の設定を
 *   logical = ! GetIniOption( "LineColumnView" );
 *   とすると Mery.ini から自動取得できますが、
 *   このマクロをイベントマクロとして使う場合は truefalse で指定してください。
 *   イベントの遅延時間の設定値が小さいと、
 *   INI ファイルの読みこみによる動作の遅滞やファイルシステムエラーを誘発します。
 * 
 * ※ 矩形選択範囲では正しく機能しません。
 * 
 * ※ 各設定項目で無効( false )を "Mery 方式" としているのは、
 *    ステータスバーの中央右寄りに表示される "選択範囲" の数え方にあわせた表現です。
 *    設定項目 ignoreEnter = true  にしても、
 *    文字数のカウントでは選択範囲末尾の改行記号を含めます。
 * 
 * ※ 文字数のカウントでは、改行記号の
 *    CR+LF と CR と LF の区別なしに1文字として扱います( ← Mery 方式)。
 * → 設定変数で区別可能にした(※ 推奨要件: Mery ver 2.6.10 以上)
 * 
 *    よそのエディタの 表示/カウント 方式にあわせたい場合は、
 *    任意の項目を有効化(true)してください。
 * 
 * ※「あああああ」5文字だけの文書で「ああ」で検索した場合、
 *    Mery の通常の検索メソッドでは "4回" ヒットします。
 *    また、「あ あ あ」5文字だけの文書で「あ あ」を検索した場合は、
 *    Mery では "2回" ヒットします。
 *    このマクロでは、設定変数  hitCountMinimum = true  にすると、
 *    選択範囲「ああ」で" ヒット数: 2件"、「あ あ」で "1件" とカウントします。
 *    ただし、 hitCountMinimum = true  で上の例のような範囲選択をしている場合、
 *    ヒット数の "(n件目)" の表示は不正確な値を返します。
 * 
 * ※ 設定項目  bytesEnable = true  で「すべて選択」状態のときのみ、
 *    実体ファイルのファイルサイズを表示できます。
 *   「無題」や「未保存*」のタブでは ks 氏の「バイト数」マクロから移植した
 *    コードにより、文字数からファイルサイズを計算します。
 * 
 * ※ 選択範囲の文字列のバイト数の簡易計算は、
 *    kuro 氏の「バイト数」マクロから移植したコードを使用して
 *    半角=1バイト、全角=2バイト でカウントします。
 */
スポンサーリンク