DTPab

印刷やデザイン、アドビ製アプリやスクリプトなど、雑多な技術ブログ

便利なPreference.propertiesプロパティの落とし穴

前段

InDesignの各Preferenceにはたいていpropertiesという便利なプロパティがいて、例えばこんなふうに使うことができます。

app.findGrepPreferences.properties = {
    findWhat: "hoge",
    fillColor: "blue"
};
app.changeGrepPreferences.properties = {
    changeTo: "fuga",
    fillColor: "green"
};

すごく便利で、僕もスクリプトを作る際に何度もお世話になっています。以前このブログで紹介し、InDesign日本語版リリース20周年イベントでも紹介した、InDesignのサンプルスクリプト「FindChangeByList.jsx」にも使われています。

uske-s.hatenablog.com

問題

つい先日、たまたまそのスクリプトのコードを読みながら動作を試していて気づきました。
存在しない塗色(fillColor)を指定したらその設定が無視されて置換が実行されてしまう

例えば先の例文では、blueというスウォッチで塗られたhogeという文字を、fugaに置換して色をgreenにする検索置換を実行したいわけです。これを該当のスウォッチが存在しないドキュメントで実行すると、すべてのhogeがfugaになってしまう。

blueスウォッチが存在しない(Blueならある)と…

これは「FindChangeByList.jsx」のせいではなく、このPreference.propertiesプロパティの仕様(なのかバグなのかはわからないけど)によるものです。

問題の切り分け

いったん「FindChangeByList.jsx」から離れて、このPreference.propertiesの挙動についておさらいしましょう。

存在しないプロパティ

app.findGrepPreferences.properties = {
    findwhat: "hoge",
    fillColor: "blue"
};
app.changeGrepPreferences.properties = {
    changeTo: "fuga",
    fillColor: "green"
};

app.activeDocument.changeGrep();

上のように、もとのpreferenceに存在しないプロパティを指定する(例えばfindWhatではなくfindwhatなど)と、ちゃんとエラーになります。

存在しないプロパティの指定

型違反

app.findGrepPreferences.properties = {
    findWhat: ["hoge"],
    fillColor: "blue"
};
app.changeGrepPreferences.properties = {
    changeTo: "fuga",
    fillColor: "green"
};

app.activeDocument.changeGrep();

上のように、本来文字列型しか取れないfindWhatプロパティに配列を渡すと、ちゃんとエラーになります。

型違反

fillColorがおかしいのか?

じゃぁfillColorプロパティがおかしいのかというと、そうではないです。他にもエラーが起きずに無視されてしまうものがありました。

app.findGrepPreferences.properties = {
    findWhat: "hoge",
    appliedCharacterStyle: "blue"
};
app.changeGrepPreferences.properties = {
    changeTo: "fuga",
    fillColor: "green"
};

app.activeDocument.changeGrep();

例えばappliedCharacterStyle(当たっている文字スタイル)。

文字スタイルblueがなくても置換される

このときの検索置換ダイアログはこうです。

無視されていることがわかる

取得できても利用できないオブジェクト

そうなると、怪しいのは「取得できるけど実際には利用(適用)できないタイプのオブジェクト」ではないか? と思うわけです。

取得できても利用できない(isVaidがfalseになる)ものの例

試してみよう

app.findGrepPreferences.properties = {
    findWhat: "hoge",
    appliedCharacterStyle: "blue",
    appliedParagraphStyles: "blue",
    kinsokuSet: "blue",
    appliedFonts: "blue",
};
app.changeGrepPreferences.properties = {
    changeTo: "fuga",
    fillColor: "green"
};

これを実行した結果の検索置換ダイアログがこれ。

概ね予想通り

禁則設定の場合「禁則を使用しない」が設定されたこと以外、ほぼ予想通りでした。

与える値によって適用される結果が異なるプロパティはあるものの、本来やりたかった検索条件通りには検索できなくなるという結果は同じです。

どうやって回避する?

自前のスクリプトであれば、与えようとするオブジェクトとその値が実際に利用可能かどうか、isValidプロパティを確認すべきでしょう。
ただ、FindChangeByList.jsxのように、こういう使い方をしているケースではとても難しいと思います。

var myString = "app.findGrepPreferences.properties = " + myFindPreferences + ";";
myString += "app.changeGrepPreferences.properties = " + myChangePreferences + ";";
myString += "app.findChangeGrepOptions.properties = " + myFindChangeOptions + ";";
app.doScript(myString, ScriptLanguage.javascript);
var myFoundItems = myObject.changeGrep();

文字列を渡してdoScript()メソッドを使っているのです。

本来であればこのpropertiesプロパティに値を渡す際、存在しない(利用できない)値であればエラーを吐かせるべきところ、どうやらInDesign側がそうなっていない。そうなると自衛のためにスクリプトを利用する側が配慮する必要があります。このような使い方(文字列を渡してdoScript()メソッドを使う)では、オブジェクトが実体化するのがdoScript()メソッドの内側になり、実体化されたタイミングと検索置換パネルに情報が登録されるタイミングを切り分けられず、チェックする機会を失います。

具体的な解決方法がまだ見つけられていませんが、アイディアとしては下記あたりでどうかと考えています。

  1. 事前にテキストをeval()メソッドを使って実体化させる→それをチェックする
  2. 事前にテキストをeval()メソッドを使って実体化させる→検索置換パネルに情報を登録する→登録されたそれぞれの情報を再チェックする

1はあまり現実的ではない、2はさすがに遠回りすぎ、と思うのですが、現実的なやり方では2が確実かなという印象です。もし1なら、そもそもpropertiesを使わずにひとつずつ値を設定していったほうが合理的でしょう。

まとめ

ということで、FindChangeByList.jsxの使い方をまとめると下記のとおりです。

  • 存在しない文字スタイルや段落スタイルなどを検索置換の条件に入れてもエラーにならず、そのまま置換が実行されるケースがある
  • 設定ファイルに記述したものがあるかないか、事前に全部把握してから利用したほうがよい
  • 意図したとおりに置換されたか最後にチェックする

findGrepPreference.propertieschangeGrepPreference.propertiesについては下記のとおり。

  • 事前にオブジェクトが利用可能か調べてから値を利用する
  • でなければ検索置換パネルの内容を再チェックするタイミングを作る
  • でなければpropertiesは使わない

余談

FIndChangeByList.jsx、設定ファイルのパスが固定だとずっと思ってたのですが、そのパスにファイルが見つからない場合、設定ファイルを選べる仕様になっていました。
これはこれで便利。