見出し画像

InDesignで検索置換を連続で行うソリューション

InDesignでの制作では、検索置換をいかにコントロールするかが鍵です。

  • 検索置換条件は「クエリ」として保存できる

  • クエリには「テキスト/正規表現」、段落スタイル、文字スタイルを含められる

  • クエリが増えるほどに探すのが面倒になる

  • ドキュメントによって、使うクエリは異なる

  • クエリの適用順も重要

これらを行く付くところは、

  • プロジェクトに応じて、複数のクエリを「セット」として実行できるのが望ましい(セット内での順番も重要)

「FindChangeByList.jsx」を使ったアプローチ

ここ最近、「FindChangeByList.jsx」を使ったアプローチに陶酔してきました。

グッド

  • クエリとして保存しておく必要はない。しかし、検証のためにInDesignの[検索と置換]ダイアログボックスを使うので、クエリとして保存しまうのがよいかも(と思い始めた)

バッド

  • 正規表現の利用時「\」を「\\」のようにエスケープする必要がある。見た目に複雑でデバッグしにくい

  • 段落スタイル、文字スタイルが複雑に絡むと見た目に複雑

  • プロジェクトによって「FindChangeList.txt」ファイルを置き換える必要があり、面倒

Uske_Sさんの記事がむっちゃ参考になります。


「ここは、もっとエレガントにできるソリューションがあるハズだ!」という視点で検索すると、先人たちの知恵が出てきます。

seuzoさんのrun_Queries

15年前から公開されているスクリプト。
InDesignの最新版でも動くのがスゴい!!!

クエリの指定は、次のソースコード部分で行います。

var my_Queries = [
["transliterate","半角カナ"],
["glyph","葛飾区"],
["text","カンマ"],
["grep","大見出し"],
["grep","中見出し"],
["grep","小見出し"],
["grep","図太字"],
];

「run_Queries.jsx」ファイルを複製し、プロジェクトに応じて使い分ければよい。

実際の運用

実際に運用していく場合、1つのスクリプトで完結するよりも、小分けに用意し、プロジェクトに応じて組み合わせ使うのが吉。

その場合、スクリプト実行ごとに「選択範囲/ストーリー/ドキュメント」を選択したり、確認ダイアログボックスを閉じるのが煩雑。

ChatGPTで「対象を選択範囲」に限定するように改変して使っている。

照山さん(mottainaiDTP)のフォルダ内のGREPクエリをスクリプトで一括実行する

クエリをフォルダー分けしておくという発想。

クエリの実行順のために、ファイル名の冒頭でナンバリングする必要があるのが面倒かも…

DTPとか電書さんのChainGREP

複数の検索置換クエリから独立した単体のスクリプトを作成するいうアプローチ。発想がスゴい!!!

そもそも検索置換をJavaScriptで行えるの?

「FindChangeByList.jsx」について、Uske_Sさんが記事内で次のように解説されています。

設定ファイルの書き方がほとんどスクリプトそのものなので、知識がそこそこ必要で、スペルミス一つすら許されない(エラーになる)

「あ、JavaScriptで完結できるのか!」と思い立ち、ChatGPTとやりとりしてみました。

次のようなソースコードで完結できます。

// InDesignオブジェクトモデルの初期化
var myDocument = app.activeDocument;

// 検索置換を行う関数
function replaceText(findPattern, changeTo, findParagraphStyle, findCharacterStyle, applyParagraphStyle, applyCharacterStyle, useGrep) {
    if (useGrep) {
        app.findGrepPreferences = NothingEnum.nothing; // 以前の検索設定をクリア
        app.changeGrepPreferences = NothingEnum.nothing; // 以前の置換設定をクリア
    } else {
        app.findTextPreferences = NothingEnum.nothing; // 以前の検索設定をクリア
        app.changeTextPreferences = NothingEnum.nothing; // 以前の置換設定をクリア
    }

    // 検索条件の設定
    if (findParagraphStyle) {
        if (useGrep) {
            app.findGrepPreferences.appliedParagraphStyle = myDocument.paragraphStyles.item(findParagraphStyle);
        } else {
            app.findTextPreferences.appliedParagraphStyle = myDocument.paragraphStyles.item(findParagraphStyle);
        }
    }

    if (findCharacterStyle) {
        if (useGrep) {
            app.findGrepPreferences.appliedCharacterStyle = myDocument.characterStyles.item(findCharacterStyle);
        } else {
            app.findTextPreferences.appliedCharacterStyle = myDocument.characterStyles.item(findCharacterStyle);
        }
    }

    if (useGrep) {
        app.findGrepPreferences.findWhat = findPattern; // 正規表現パターンを指定
        app.changeGrepPreferences.changeTo = changeTo; // 置換後のテキストを指定
    } else {
        app.findTextPreferences.findWhat = findPattern; // 単純なテキストを指定
        app.changeTextPreferences.changeTo = changeTo; // 置換後のテキストを指定
    }

    if (applyParagraphStyle) {
        if (useGrep) {
            app.changeGrepPreferences.appliedParagraphStyle = myDocument.paragraphStyles.item(applyParagraphStyle);
        } else {
            app.changeTextPreferences.appliedParagraphStyle = myDocument.paragraphStyles.item(applyParagraphStyle);
        }
    }

    if (applyCharacterStyle) {
        if (useGrep) {
            app.changeGrepPreferences.appliedCharacterStyle = myDocument.characterStyles.item(applyCharacterStyle);
        } else {
            app.changeTextPreferences.appliedCharacterStyle = myDocument.characterStyles.item(applyCharacterStyle);
        }
    }

    // 検索と置換の実行
    if (useGrep) {
        myDocument.changeGrep();
        // 検索と置換の設定をクリア
        app.findGrepPreferences = NothingEnum.nothing;
        app.changeGrepPreferences = NothingEnum.nothing;
    } else {
        myDocument.changeText();
        // 検索と置換の設定をクリア
        app.findTextPreferences = NothingEnum.nothing;
        app.changeTextPreferences = NothingEnum.nothing;
    }
}

// 連続して検索置換を実行(useGrep引数がtrueなら正規表現、falseなら単純テキスト)
replaceText("\\d+", "*", null, null, "NewParagraphStyle", "NewCharacterStyle", true); // 数字をアスタリスクに置換し、新しい段落スタイルと文字スタイルを適用(正規表現)
replaceText("command", "replace", "MyParagraphStyle", null, null, "MyCharacterStyle", false); // "command"を"replace"に置換し、文字スタイルを適用(単純テキスト)
replaceText("foo", "bar", null, "OldCharacterStyle", null, "NewCharacterStyle", false); // "foo"を"bar"に置換し、文字スタイルを適用(単純テキスト)
replaceText("example", "sample", "MyParagraphStyle", "OldCharacterStyle", "NewParagraphStyle", "NewCharacterStyle", true); // "example"を"sample"に置換し、新しい段落スタイルと文字スタイルを適用(正規表現)

末尾が検索・置換条件をスクリプト化したものです。

replaceText("\\d+", "*", null, null, "NewParagraphStyle", "NewCharacterStyle", true); 
  • 「\」を「\\」のようにエスケープする必要があります。

よさげですが、実際に手書きで書くのは面倒です。

そこで、Google スプレッドシートを作成してみました。Excel/Google スプレッドシートでは、"は使えないため、'にしています(利用時に置換する必要があります)。

番外編:クエリのネーミングについて

テキスト/正規表現で同じ名称のクエリを作成したときを想定し、「text_」のように付けておくとよいよね!と解説されています。これはよい!

追記(1)

Uske_Sさんから、こんなの作っているけど…とご連絡いただきました。
今ならわかる、このアイデアの凄み!!!

追記(2)

ChatGPT謹製スクリプト、選択範囲/ストーリー/ドキュメントを選択できるようにしました。

追記(3)

Maestro Packの「Id Execute Jsx Text」アクション内にペーストしただけでは動かないスクリプトが多々ある。

その場合にはスクリプトのパスを指定。

「パスを指定」の場合、ファイル名が見えにくいため、次のように分割してとくとハンドリングがよい。

Keyboard Maestroプラグイン

Keyboard Maestroのプラグインとして実行できるのが理想です。

ここから先は

0字
月に10-20本くらいの記事を投稿しています。定期購読されると、更新のお知らせを受け取ったり、マガジン限定記事やサンプルファイルをダウンロードできます。 購読を開始した月に更新された記事から読むことができます(初月無料)。

DTP Transit 定期購読マガジン

¥100 / 月 初月無料

マガジン限定記事やサンプルファイルをダウンロードできます。

定期マガジンを購読されるとサンプルファイルをダウンロードいただけます。 https://note.com/dtp_tranist/m/mebd7eab21ea5