コモノExtendScript100本ノック

超初心者のDTPオペレーターが週にひとつスクリプトを書くブログ

068.【Id】重なっているパネルを切り替える

私が普段使いしているワークスペース上では、文字パネルと段落パネルが重なっています。
f:id:haraguai_is_bad:20200402175438p:plain:w300

こんな感じ。
ディスプレイがもっと大きければ横並びにしたいくらい、どちらもよく使うパネルです。

手っ取り早く切り替える手段には、まずデフォルトのショートカットキーがあります。
文字パネルを表示するショートカットはCtrl+T、段落パネルはCtrl+Alt+T。
正直CtrlとAltって同時押ししにくい…あと、「今開いているのは文字パネルだから…」と一拍挟まなければならいのがとても嫌。

なので、この切り替えをスクリプトにして、そのスクリプトにCtrl+Tのショートカットを宛てることにしました。
f:id:haraguai_is_bad:20200402180249p:plain:w600

コード

var cPanel = app.panels.itemByName("文字");
var pPanel = app.panels.itemByName("段落");
if(cPanel.visible){
  pPanel.visible = true;
  exit();
  }
cPanel.visible = true;

067.【Id】一括再リンク

実行方法

  1. extendscript-es5-shimから、indexOf()のポリフィルをダウンロードする
  2. このスクリプトと同階層に「indexOf.js」を移動する
  3. 以下のようなテキストデータを作成する
    リンクAの現在の絶対パス[タブ]リンクAの差し替え後の絶対パス
    リンクBの現在の絶対パス[タブ]リンクBの差し替え後の絶対パス
  4. 対象としたいinddを開いた状態で実行する

コード

//@include "./indexOf.js"

var list = fileRead("before", "after");
var doc = app.activeDocument;

var log1 = log2 = [];
var tmp = doc.links.everyItem().filePath;
for (var i = 0; i < list.length; i++) {
  //実際のリンクのなかにbeforeのパスのものがなかったら
  if (tmp.indexOf(list[i]['before']) === -1) {
    log1.push(i + 1);
  }
  //もし差し替え先のファイルが存在していなかったら
  if (!new File(list[i]['after']).exist) {
    log2.push(i + 1)
  }
}
if (log1.length > 0) {
  alert(log1 + "行目の変更前のパスを確認してください");
}
if (log2.length > 0) {
  alert(log2 + "行目の変更後のパスを確認してください");
}
if (log1.length > 0 || log2.length > 0) {
  exit();
}

for (var i = 0; i < list.length; i++) {
  for (var j = 0; j < doc.links.length; j++) {
    if (doc.links[j].filePath === list[i].before) {
      doc.links[j].relink(new File(list[i].after));
    }
  }
}


function fileRead(value /*string*/ ) {
  var tgtFile = File.openDialog("ファイルを選んでください", "*.txt");
  if (!tgtFile || !tgtFile.exists) {
    alert("中断しました");
    exitFlag = true;
    exit();
  }
  var res = [];
  var fileReadFlag;
  tgtFile.open("r");
  tgtFile.encoding = "UTF-8";

  try {
    while (!tgtFile.eof) {
      var args = arguments;
      var ln = tgtFile.readln().split(/\t/);
      if (ln.length !== args.length) {
        alert("読み込みテキストに不備があるため中断します。");
        exit();
        exitFlag = true;
      }
      var item = {};
      for (var i = 0; i < args.length; i++) {
        item[args[i]] = ln[i];
      }
      res.push(item);
    }
    fileReadFlag = true;
  } catch (e) {
    alert(e);
    fileReadFlag = false;
  } finally {
    tgtFile.close();
  }
  if (!fileReadFlag || res.length === 0) {
    alert("ファイルを読み込めませんでした");
    exit();
    exitFlag = true;
  }
  return res;
}

066.【Id】選択しているリンクのファイル名を変更する

挙動

  1. リンクを選択した状態で起動する
  2. リンクが正常な状態かチェック
  3. ダイアログが表示されるので新しい名前を入力する
  4. リンクデータのファイル名が変更される
  5. リンクが繋ぎなおされる

コード

var main = function() {
    app.scriptPreferences.enableRedraw = false;
    if (app.selection.length === 0) {
        alert("リンクを1つ選択してください。");
        exit();
    }
    var sel = app.selection[0];
    if (!sel.properties.itemLink) {
        alert("リンクを選択してください。");
        exit();
    }
    var myLink = sel.properties.itemLink;
    if (myLink.status !== LinkStatus.NORMAL) {
        alert("リンクが最新の状態ではないか埋め込みです。\n中断します。");
        exit();
    }
    var myLinkName = myLink.name;
    var myLinkFobj = new File(myLink.filePath);
    var fld = myLinkFobj.parent;
    var format = myLinkName.slice(myLinkName.lastIndexOf(".")); //ファイル形式
    var newName = prompt("新しい名前を入力してください", myLinkName.replace(/\..+?$/, ""), "リンクファイル名変更スクリプト");
    if (!newName) { //キャンセルされた場合
        exit();
    }
    var newpath = fld + "/" + newName + format;
    if (new File(newpath).exist) { //同名ファイルがあった場合
        alert("同名のファイルがあるため中断します");
        exit();
    }
    var rename = myLinkFobj.rename(newpath);
    if (!rename) {
        alert("リネームできませんでした"); //ファイル名に使用できない文字を指定した場合など
        exit();
    }
    myLink.relink(new File(newpath));
    alert("完了しました");
}

app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.FAST_ENTIRE_SCRIPT, "リンクファイル名変更スクリプト");

メモ

InDesign上で取消し(ctrl+Z)しても、リンクデータのファイル名は元に戻らない。
EventListnerを利用すれば、取消しを実行したときにファイル名も元に戻すようにできるのかもしれないけれど、そこまでする必要はないかな…。

065.【Id】異なるinddの同名段落スタイルの差分を取得

※未完成
※同じ名前の段落スタイルであっても、スタイルグループが異なる場合は別物として扱う

挙動

前準備

  1. Github上でextendscript-es5-shimとして公開されているプロジェクトから、indexOf()isArray()のポリフィルをダウンロードする
  2. このスクリプトと同階層に「indexOf.js」「isArray.js」を移動する

実行

  1. ドキュメントを2つ開いた状態でスクリプトを実行する
  2. 各inddの同名同グループの段落スタイルを比較する
  3. プロパティの値が異なる場合、差分を取得する
  4. デスクトップにlog.txtを出力する

コード

//@include "./indexOf.js"
//@include "./isArray.js"

if (app.documents.length !== 2) {
    alert("2つのドキュメントを開いてください");
    exit();
}

var indd0 = app.documents[0];
var indd1 = app.documents[1];

//各inddの全段落スタイルの情報を取得
var pStyles0 = getPstylesInfo(indd0);
var pStyles1 = getPstylesInfo(indd1);

//両inddで名前とグループ名の一致する段落スタイルを取得
var dupe = [];
for (var i = 0; i < pStyles0.length; i++) {
    for (var j = 0; j < pStyles1.length; j++) {
        if (pStyles0[i]['name'] === pStyles1[j]['name'] && pStyles0[i]['group'] === pStyles1[j]['group']) {
            dupe.push([pStyles0[i], pStyles1[j]])
        }
    }
}

if (dupe.length === 0) {
    alert("重複したスタイルはありませんでした");
    exit();
}

var log = "スタイル名\tグループ名\tプロパティ\t" + indd0.name + "\t" + indd1.name;
for (var i = 1; i < dupe.length; i++) { //段落スタイルなしはスキップ
    var tmpStyle = indd0.paragraphStyles[0];
    log = log + "\n" + compareProp(tmpStyle, dupe[i][0]['obj'], dupe[i][1]['obj'])
}

writeTxt("~/Desktop/log.txt", log);

function compareProp(tgt, a, b) {
    var r = "";
    for (p in tgt) {
        var propn = p;
        var aprop = bprop = "";
        var skip = ["properties", "parent", "preferences", "styleUniqueId", "id","index"]; //このプロパティはスキップする
        if (skip.indexOf(p) === -1) {
            switch (getType(a[p])) {
                case "object": //nextStyleなどobjectの場合はnameで比較
//~                     compareProp(a[p], a[p], b[p]); //本当はここで再帰処理するべきなんだと思う
                    try {
                        if (a[p].name !== b[p].name) {
                            aprop = a[p].name.replace(/\t/g," ");
                            bprop = b[p].name.replace(/\t/g," ");
                        }
                    } catch (e) { //nameで比較できない場合は比較しない
                        aprop = "この項目は比較しませんでした"
                    }
                    break;
                case "array":
                    if (a[p].toSource() !== b[p].toSource()) {
                      if (typeof a[p][0] ==="object") { //tabListなどobjectを要素として持つ配列の場合
                        aprop = "この項目は比較しませんでした"
                        } else {
                        aprop = a[p];
                        bprop = b[p];
                        }
                    }
                    break;
                case "string":
                    if (a[p] !== b[p]) {
                        aprop = a[p].replace(/\t/g," ");
                        bprop = b[p].replace(/\t/g," ");
                    }
                break;
                default: //enum・数値・真偽値などの場合
                    if (a[p] !== b[p]) {
                        aprop = a[p];
                        bprop = b[p];
                    }
                    break;
            }
        }
        if (aprop !== "") {
            r = r + [dupe[i][0]['name'], dupe[i][0]['group'], p, aprop, bprop].join("\t") + "\n";
        }
    }
    return r
}

//全段落スタイルの情報を取得
function getPstylesInfo(tgtDoc) {
    var result = [];
    for (var i = 0; i < tgtDoc.allParagraphStyles.length; i++) {
        var tmp = tgtDoc.allParagraphStyles[i];
        result.push({
            name: tmp.name,
            group: getStyleGroup(tmp).replace(/\//, ""),
            obj: tmp
        });
    }
    return result;
}

//所属スタイルグループ名を取得
//入れ子になっている場合はスラッシュで繋ぐ
function getStyleGroup(tgt) {
    var tgtGroup = ""
    if (tgt.parent.constructor.name === "Document") {
        return "";
    }
    tgtGroup = getStyleGroup(tgt.parent) + "/" + tgt.parent.name;
    return tgtGroup;
}

//テキスト書き出し
function writeTxt(path, txt) {
    var fObj = new File(path);
    fObj.encoding = (/csv$/.test(path)) ? "Shift-JIS" : "UTF-8"; //CSVで書き出す場合はShift-JIS
    if (fObj.open("w")) {
        fObj.write(txt);
        fObj.close();
        return fObj;
    } else {
        alert("ファイルが開けません\n" + path);
    }
}

//型判定
//最初に大部分をふるい落とすほうがよい?
//if (typeof obj !== "object") {return typeof obj;}
function getType(obj) {
    if (obj === null){
      return "null";
      }
    if (Array.isArray(obj)) {
        return "array";
    }
    if (obj.constructor.name === "Enumerator") {
        return "enumerator";
    }
    return typeof obj;
}

メモ

分かったこと

  • Enumulatorについて
var tgt = app.activeDocument.paragraphStyles[2].justification;
$.writeln(typeof tgt);//object
$.writeln(Object.prototype.toString.call(tgt));//LEFT_JUSTIFIED
$.writeln(tgt.constructor.name);//Enumerator
$.writeln(tgt.toSource());//({})
var tgt2 = app.activeDocument.paragraphStyles[4].justification;
$.writeln(tgt===tgt2);//true enum同士で比較できる

積み残し

  • テストが不十分。おそらくバグがある。
  • オブジェクト同士の比較について別の方法を模索する。
    現状、値がオブジェクトの場合(「次の段落スタイル」など)はnameプロパティだけを比較している。
    そのため名前が同じで中身が違う場合でも同じと見なしてしまう。
  • 3つ以上のinddでも使えるようする。
    ブックドキュメントの同期をする前に差分をチェックしたいなと思ったのがきっかけなので、複数のinddを対象としたい。

参考

064.【Id】未使用のスタイル・レイヤー・スウォッチ一括削除

※未完成です

挙動

以下の順番で削除する。
・未使用のレイヤー
・未使用の文字スタイル・文字スタイルグループ
・未使用の段落スタイル・段落スタイルグループ
・未使用のオブジェクトスタイル・オブジェクトスタイルグループ
・未使用のセルスタイル・セルスタイルグループ
・未使用の表スタイル・表スタイルグループ
・未使用のスウォッチ・カラーグループ

コード

var doc = app.activeDocument;

//レイヤー
app.menus.item("レイヤーパネルメニュー").menuItems.item("未使用レイヤーを削除").associatedMenuAction.invoke();

//文字スタイル
var chrStyles = doc.allCharacterStyles;
var chrStylesLen = chrStyles.length;
for (var i = chrStylesLen - 1; i >= 0; i--) {
    if (!isUsedChrStyle(chrStyles[i])) {
        chrStyles[i].remove();
    }
}
//文字スタイルグループ
arrangeChrStyleGrp(doc);

//段落スタイル
var pStyles = doc.allParagraphStyles;
var pStylesLen = pStyles.length;
for (var i = pStylesLen - 1; i >= 0; i--) {
    if (!isUsedParaStyle(pStyles[i])) {
        pStyles[i].remove();
    }
}
//段落スタイルグループ
arrangeParaStyleGrp(doc);


//オブジェクトスタイル
var objStyles = doc.allObjectStyles;
var objStylesLen = objStyles.length;
for (var i = objStylesLen - 1; i >= 0; i--) {
    if (!isUsedObjStyle(objStyles[i])) {
        objStyles[i].remove();
    }
}
//オブジェクトスタイルグループ
arrangeObjStyleGrp(doc);

//セルスタイル
var cllStyles = doc.allCellStyles;
var cllStylesLen = cllStyles.length;
for (var i = cllStylesLen - 1; i >= 0; i--) {
    if (!isUsedCellStyle(cllStyles[i])) {
        cllStyles[i].remove();
    }
}
//セルスタイルグループ
arrangeCellStyleGrp(doc);

//表スタイル
var tblStyles = doc.allTableStyles;
var tblStylesLen = tblStyles.length;
for (var i = tblStylesLen - 1; i >= 0; i--) {
    if (!isUsedTableStyle(tblStyles[i])) {
        tblStyles[i].remove();
    }
}
//表スタイルグループ
arrangeTableStyleGrp(doc);

//スウォッチ
app.menus.item("スウォッチパネルのコンテキストメニュー").menuItems.item("未使用をすべて選択").associatedMenuAction.invoke();
app.menus.item("スウォッチパネルのコンテキストメニュー").menuItems.item("スウォッチを削除...").associatedMenuAction.invoke();
//カラーグループ
var clrGrp = doc.colorGroups;
var clrGrpLen = clrGrp.count();
for (var i = clrGrpLen - 1; i >= 0; i--) {
    if (clrGrp[i].colorGroupSwatches.count()===0) {
        clrGrp[i].remove();
    }
}



//以下関数
//文字スタイル
function isUsedChrStyle(chrStyle) {
    var chrStyles = doc.allCharacterStyles;
    var chrStylesLen = chrStyles.length;
    var isUsed = false; //不使用かつ基準でない
    app.findTextPreferences = app.changeTextPreferences = NothingEnum.NOTHING;
    app.findTextPreferences.appliedCharacterStyle = chrStyle;
    var result = doc.findText();
    if (result.length > 0) {
        isUsed = true; //使用している
    } else {
        for (var cc = 2; cc < chrStylesLen; cc++) { //[なし][基本段落]はスキップ
            if (chrStyles[cc].basedOn === chrStyle) {
                isUsed = true; //不使用だが基準になっている
                break;
            }
        }
    }
    app.findTextPreferences = app.changeTextPreferences = NothingEnum.NOTHING;
    return isUsed;
}

//文字スタイルグループ
function arrangeChrStyleGrp(tgt/*document*/){
  var chrStylesGrp = tgt.characterStyleGroups;
  for (var i = chrStylesGrp.length - 1; i >= 0; i--) {
    if (chrStylesGrp[i].characterStyleGroups.length > 0){
      arrangeChrStyleGrp(chrStylesGrp[i]);
      } else if (chrStylesGrp[i].characterStyles.length ===0){
        chrStylesGrp[i].remove();
        }
    }
  }

//段落スタイル
function isUsedParaStyle(paraStyle) {
    var pStyles = doc.allParagraphStyles;
    var pStylesLen = pStyles.length;
    var isUsed = false; //不使用かつ基準でない
    app.findTextPreferences = app.changeTextPreferences = NothingEnum.NOTHING;
    app.findTextPreferences.appliedParagraphStyle = paraStyle;
    var result = doc.findText();
    if (result.length > 0) {
        isUsed = true; //使用している
    } else {
        for (var pp = 2; pp < pStylesLen; pp++) { //[なし][基本段落]はスキップ
            if (pStyles[pp].basedOn === paraStyle) {
                isUsed = true; //不使用だが基準になっている
                break;
            }
        }
    }
    app.findTextPreferences = app.changeTextPreferences = NothingEnum.NOTHING;
    return isUsed;
}

//段落スタイルグループ
function arrangeParaStyleGrp(tgt/*document*/){
  var pStylesGrp = tgt.paragraphStyleGroups;
  for (var i = pStylesGrp.length - 1; i >= 0; i--) {
    if (pStylesGrp[i].paragraphStyleGroups.length > 0){
      arrangeParaStyleGrp(pStylesGrp[i]);
      } else if (pStylesGrp[i].paragraphStyles.length ===0){
        pStylesGrp[i].remove();
        }
    }
  }

//オブジェクトスタイル
function isUsedObjStyle(objStyle) {
    var objStyles = doc.allObjectStyles;
    var objStylesLen = objStyles.length;
    var isUsed = false; //不使用かつ基準でない
    app.findObjectPreferences = app.changeObjectPreferences = NothingEnum.NOTHING;
    app.findObjectPreferences.appliedObjectStyles = objStyle;
    var result = doc.findObject();
    if (result.length > 0) {
        isUsed = true; //使用している
    } else {
        for (var oo = 4; oo < objStylesLen; oo++) { //[なし][基本グラフィックフレーム][基本テキストフレーム][基本グリッド]はスキップ
            if (objStyles[oo].basedOn === objStyle) {
                isUsed = true; //不使用だが基準になっている
                break;
            }
        }
    }
    app.findObjectPreferences = app.changeObjectPreferences = NothingEnum.NOTHING;
    return isUsed;
}

//オブジェクトスタイルグループ
function arrangeObjStyleGrp(tgt/*document*/){
  var objStylesGrp = tgt.objectStyleGroups;
  for (var i = objStylesGrp.length - 1; i >= 0; i--) {
    if (objStylesGrp[i].objectStyleGroups.length > 0){
      arrangeObjStyleGrp(objStylesGrp[i]);
      } else if (objStylesGrp[i].objectStyles.length ===0){
        objStylesGrp[i].remove();
        }
    }
  }

//表スタイル
function isUsedTableStyle(tblStyle) {
    var tblStyles = doc.allTableStyles;
    var tblStylesLen = tblStyles.length;
    var isUsed = false; //不使用かつ基準でない
    app.findTextPreferences = app.changeTextPreferences = NothingEnum.NOTHING;
    app.findTextPreferences.findWhat = "<0016>";
    var tblsParent = app.findText();
    for (var tt = 0; tt < tblsParent.length; tt++) {
        if (tblsParent[tt].tables[0].appliedTableStyle === tblStyle) {
            isUsed = true; //使用している
        }
    }
    for (var ttt = 2; ttt < tblStylesLen; ttt++) { //[表スタイルなし][基本表]はスキップ
        if (tblStyles[ttt].basedOn === tblStyle) {
            isUsed = true; //不使用だが基準になっている
        }
    }
    app.findTextPreferences = app.changeTextPreferences = NothingEnum.NOTHING;
    return isUsed;
}

//表スタイルグループ
function arrangeTableStyleGrp(tgt/*document*/){
  var tblStylesGrp = tgt.tableStyleGroups;
  for (var i = tblStylesGrp.length - 1; i >= 0; i--) {
    if (tblStylesGrp[i].tableStyleGroups.length > 0){
      arrangeTableStyleGrp(tblStylesGrp[i]);
      } else if (tblStylesGrp[i].tableStyles.length ===0){
        tblStylesGrp[i].remove();
        }
    }
  }

//セルスタイル
function isUsedCellStyle(cllStyle) {
    var cllStyles = doc.allCellStyles;
    var cllStylesLen = cllStyles.length;
    var isUsed = false; //不使用かつ基準でない
    app.findTextPreferences = app.changeTextPreferences = NothingEnum.NOTHING;
    app.findTextPreferences.findWhat = "<0016>";
    var tblsParent = app.findText();
    for (var tt = 0; tt < tblsParent.length; tt++) {
        for (var cc = 0; cc < tblsParent[tt].tables[0].cells.length; cc++) {
            if (tblsParent[tt].tables[0].cells[cc].appliedCellStyle === cllStyle) {
                isUsed = true; //使用している
            }
        }
    }
    for (var ccc = 1; ccc < cllStylesLen; ccc++) { //[なし]はスキップ
        if (cllStyles[ccc].basedOn === cllStyle) {
            isUsed = true; //不使用だが基準になっている
        }
    }
    app.findTextPreferences = app.changeTextPreferences = NothingEnum.NOTHING;
    return isUsed;
}

//セルスタイルグループ
function arrangeCellStyleGrp(tgt/*document*/){
  var cllStylesGrp = tgt.cellStyleGroups;
  for (var i = cllStylesGrp.length - 1; i >= 0; i--) {
    if (cllStylesGrp[i].cellStyleGroups.length > 0){
      arrangeCellStyleGrp(cllStylesGrp[i]);
      } else if (cllStylesGrp[i].cellStyles.length ===0){
        cllStylesGrp[i].remove();
        }
    }
  }

メモ

積み残し

  • 消す順番によって、使っていないのに残ってしまう可能性がある
    段落スタイル1:不使用だが段落スタイル2の基準。
    段落スタイル2:不使用。
    のような場合に起こる。
    最初に親子関係をすべて取得したうえで末端から処理する?
  • 似たような処理があちこちにあるので統一したい

参考

063.【Id】マスターページ一括割り当て

挙動

  1. 起動するとダイアログが立ち上がる
    f:id:haraguai_is_bad:20200215102353p:plain:w300
  2. ダイアログに表示されたレイヤー名を元に、以下のようなテキストデータを作成する
    ページ範囲 [タブ] マスター名1
    ページ範囲 [タブ] マスター名2
    (ページの指定は名前か絶対ページ番号で行う。どちらも「-」での範囲指定可)
  3. ページ名と絶対ページ番号のどちらで指示するか、ラジオボタンで指定する
  4. 実行を押すと指示書の通りにマスターを宛がう

コード

//@targetengine "setMaster"
/* 
あらかじめ以下のようなテキストデータを作成する。
ページ範囲 [タブ] マスター名1
ページ範囲 [タブ] マスター名2
*/
var main = function() {
    app.scriptPreferences.enableRedraw = false;

    var doc = app.activeDocument;
    var mstNames = [];
    for (var i = 0, mst = doc.masterSpreads; i < mst.length; i++) {
        mstNames.push(mst[i].name);
    }
    if (app.documents.length === 0) {
        alert("開いているドキュメントがないため中断します");
        exitFlag = true;
        exit();
    }
    // ウィンドウを作成
    var dialog = new Window("dialog");
    dialog.text = "マスターページ一括指定";
    dialog.orientation = "column";
    dialog.alignChildren = ["center", "top"];

    var statictext2 = dialog.add("statictext");
    statictext2.text = "マスターページ一覧";

    var edittext2 = dialog.add('edittext {properties: {multiline: true, scrollable: true}}');
    edittext2.text = mstNames.join("\n");
    edittext2.preferredSize.width = 200;
    edittext2.preferredSize.height = 300;
    edittext2.multiline = true;

    var group1 = dialog.add("group");
    group1.orientation = "row";

    var radiobutton1 = group1.add("radiobutton");
    radiobutton1.text = "ページ名で指定";
    var radiobutton2 = group1.add("radiobutton");
    radiobutton2.text = "絶対ページ番号で指定";

    var button1 = dialog.add("button");
    button1.text = "実行";

    button1.onClick = function() {
        dialog.close(1);
    }
    var dlgResult = dialog.show();
    if (dlgResult !== 1) {
        alert("中断しました");
        exitFlag = true;
        exit();
    }

    if (radiobutton1.value === false && radiobutton2.value === false) {
        alert("ページ指定の種類が選択されていないため中断しました。");
        exitFlag = true;
        exit();
    }
    var myList = fileRead("page", "master");
    var cnt = myList.length;

    for (var i = 0; i < cnt; i++) {
        try {

            if (radiobutton1.value) {
                // ページ名で指定した場合
                var tmp = myList[i]['page'].split("-");
                if (tmp[0] === "") {
                    tmp[0] = doc.pages[0].name
                }
                if (tmp[1] === "") {
                    tmp[1] = doc.pages.lastItem().name
                }
                if (tmp[1] === undefined) { // 範囲指定では無い場合
                    myList[i]['pageObjs'] = doc.pages.itemByName(tmp[0]);
                } else {
                    myList[i]['pageObjs'] = doc.pages.itemByRange(doc.pages.itemByName(tmp[0]), doc.pages.itemByName(tmp[1]));
                }
            } else {
                // 絶対ページ番号で指定した場合
                var tmp = myList[i]['page'].replace("+", "").split("-");
                if (tmp[0] === "") {
                    tmp[0] = 1;
                }
                if (tmp[1] === "") {
                    tmp[1] = doc.pages.length;
                }
                if (tmp[1] === undefined) { // 範囲指定では無い場合
                    myList[i]['pageObjs'] = doc.pages[parseInt(tmp[0], 10) - 1];
                } else {
                    myList[i]['pageObjs'] = doc.pages.itemByRange(parseInt(tmp[0], 10) - 1, parseInt(tmp[1], 10) - 1);
                }
            }
            myList[i]['pageObjs'].appliedMaster = doc.masterSpreads.itemByName(myList[i]['master']);
        } catch (e) {
            alert("指定のページあるいはマスターが存在しないため中断しました");
            exitFlag = true;
            exit();
        }
    }

    function fileRead(value /*string*/ ) {
        var tgtFile = File.openDialog("ファイルを選んでください", "*.txt");
        if (!tgtFile || !tgtFile.exists) {
            alert("中断しました");
            exitFlag = true;
            exit();
        }
        var res = [];
        var fileReadFlag;
        tgtFile.open("r");
        tgtFile.encoding = "UTF-8";

        try {
            while (!tgtFile.eof) {
                var args = arguments;
                var ln = tgtFile.readln().split(/\t/);
                if (ln.length !== args.length) {
                    alert("読み込みテキストに不備があるため中断します。");
                    exit();
                    exitFlag = true;
                }
                var item = {};
                for (var i = 0; i < args.length; i++) {
                    item[args[i]] = ln[i];
                }
                res.push(item);
            }
            fileReadFlag = true;
        } catch (e) {
            alert(e);
            fileReadFlag = false;
        } finally {
            tgtFile.close();
        }
        if (!fileReadFlag || res.length === 0) {
            alert("ファイルを読み込めませんでした");
            exit();
            exitFlag = true;
        }
        return res;
    }
}

var exitFlag = false; // main内で中断した場合に完了アラートを出さないようにするため
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.FAST_ENTIRE_SCRIPT, "setMaster.jsx");
if (!exitFlag) {
    app.undo(); // dialogの段階で中断している場合はundoできない
    alert("完了しました");
}

メモ

split()について

var test1 = "001"
var result1 = test1.split("-"); 
// result1[0]は"001"、result1[1]はundefined

var test2 = "001-"
var result2 = test2.split("-"); 
// result1[0]は001、result1[1]は""

var test3 = "-001"
var result3 = test3.split("-"); 
// result1[0]は""、result1[1]は"001"

Falsyな値

0、−0、undefined、空文字、null、false、NaN
つまり if(!result1[0]) では空文字かundefinedか区別できない。

page.index

page.indexは親スプレッドに対するインデックスを意味する。
あるふぁ(仮)さんに教えていただきました。いつもありがとうございます!)

積み残し

  • セクション名を含んだページ指定にも対応

062.【Id】レイヤーを切り替えて複数回PDF書き出し

挙動

  1. 起動するとダイアログが立ち上がるので、書き出しプリセットを選択する
  2. ダイアログに表示されたレイヤー名を元に、以下のようなテキストデータを作成する
    ページ範囲 [タブ] PDFファイル名1 [タブ] レイヤー1,レイヤー2
    ページ範囲 [タブ] PDFファイル名2 [タブ] レイヤー1,レイヤー3,レイヤー4
    ページ範囲 [タブ] PDFファイル名3 [タブ] レイヤー5
  3. 実行ボタンを押すと、指定したレイヤーのみが表示された状態でPDFが書き出される
  4. スクリプト実行前の状態に戻る

    コード

//@targetengine "filterLayers"
/* 
あらかじめ以下のようなテキストデータを作成する。
ページ範囲 [タブ] PDFファイル名1 [タブ] レイヤー1,レイヤー2
ページ範囲 [タブ] PDFファイル名2 [タブ] レイヤー1,レイヤー3,レイヤー4
*/
var main = function() {
    app.scriptPreferences.enableRedraw = false;

    var doc = app.activeDocument;
    var ls = [];
    for (var i = 0, l = doc.layers; i < l.length; i++) {
        ls.push(l[i].name);
    }
    if (app.documents.length === 0) {
        alert("開いているドキュメントがないため中断します");
        exitFlag = true;
        exit();
    }
    // ウィンドウを作成
    var dialog = new Window("dialog");
    dialog.text = "レイヤー切り替えPDF書き出し";
    dialog.orientation = "column";
    dialog.alignChildren = ["center", "top"];

    var group1 = dialog.add("group");
    group1.orientation = "column";
    group1.alignChildren = ["left", "top"];

    var group2 = group1.add("group");
    group2.orientation = "column";
    group2.alignChildren = ["left", "center"];

    var dropdown1 = dialog.add("dropdownlist", undefined, app.pdfExportPresets.everyItem().name);
    dropdown1.preferredSize.width = 500;

    var group3 = group1.add("group");
    group3.orientation = "column";
    group3.alignChildren = ["left", "center"];

    var statictext2 = group3.add("statictext");
    statictext2.text = "レイヤー一覧";

    var edittext2 = group3.add('edittext {properties: {multiline: true, scrollable: true}}');
    edittext2.text = ls.join(",");
    edittext2.preferredSize.width = 500;
    edittext2.preferredSize.height = 100;
    edittext2.multiline = true;

    var button1 = dialog.add("button");
    button1.text = "実行";

    button1.onClick = function() {
        dialog.close(1);
    }
    var dlgResult = dialog.show();
    if (dlgResult !== 1) {
        alert("中断しました");
        exitFlag = true;
        exit();
    }

    var myList = fileReadInAppend("prange", "pname", "visibleLayers");
    var cnt = myList.length;

    // ファイル名の重複チェック
    if (!chkDuplicates(myList, "pname")) {
        alert("ファイル名が重複しているため中断しました");
        exitFlag = true;
        exit();
    }

    if (!dropdown1.selection) {
        alert("書き出しプリセットが選択されていないため中断しました");
        exitFlag = true;
        exit();
    }
    var p = app.pdfExportPresets.itemByName(dropdown1.selection.text);

    for (var i = 0; i < cnt; i++) {
        doc.layers.everyItem().visible = false;
        for (var j = 0, vlcnt = myList[i].visibleLayers.length; j < vlcnt; j++) {
            try {
                doc.layers.itemByName(myList[i].visibleLayers[j]).visible = true;
            } catch (e) {
                alert("指定のレイヤーが存在しないため中断しました");
                exitFlag = true;
                exit();
            }
        }
        // ファイル名に使用できない文字チェック
        if (/[\*\?"<>\|]/g.test(myList[i].pname)) {
            alert("ファイル名に使用できない文字が含まれているため中断しました");
            exitFlag = true;
            exit();
        }
        var fn = doc.filePath + "/" + myList[i].pname + ".pdf".replace(".pdf.pdf", ".pdf").replace(".PDF", "");
        app.pdfExportPreferences.pageRange = myList[i].prange;
        try {
            doc.exportFile(ExportFormat.pdfType, File(fn), false, p);
        } catch (e) {
            alert("存在しないページが指定されている等なんらかのエラーが発生しました。\n中断します。");
            exitFlag = true;
            exit();
        }
    }


    function fileReadInAppend(value1, value2, value3 /*string*/ ) {
        var tgtFile = File.openDialog("ファイルを選んでください", "*.txt");
        if (!tgtFile || !tgtFile.exists) {
            alert("中断しました");
            exitFlag = true;
            exit();
        }
        var res = [];
        var fileReadFlag;
        tgtFile.open("r");
        tgtFile.encoding = "UTF-8";

        try {
            while (!tgtFile.eof) {
                var ln = tgtFile.readln().split(/\t/);
                if (ln.length !== arguments.length) {
                    alert("読み込みテキストに不備があります。\n中断します。");
                    exit();
                    exitFlag = true;
                }
                var item = {};
                item[value1] = ln[0];
                item[value2] = ln[1];
                item[value3] = ln[2].split(",");
                res.push(item);
            }
            fileReadFlag = true;
        } catch (e) {
            alert(e);
            fileReadFlag = false;
        } finally {
            tgtFile.close();
        }
        if (!fileReadFlag || res.length === 0) {
            alert("ファイルを読み込めませんでした");
            exit();
            exitFlag = true;
        }

        return res;
    }

    // 配列内で同じプロパティを持つオブジェクトを抽出
    function chkDuplicates(objArr, prop) {
        var exist = {};
        var result = [];
        for (var i = 0, l = objArr.length; i < l; i++) {
            if (typeof objArr[i] !== "object") {
                continue;
            }
            var tmp = objArr[i][prop];
            if (!exist[tmp]) {
                exist[tmp] = 1;
            } else if (exist[tmp] === 1) {
                exist[tmp]++;
                result.push(arr[i]);
            } else {
                exist[tmp]++;
            }
        }
        return result;
    }
}

var exitFlag = false; // main内で中断した場合に完了アラートを出さないようにするため
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.FAST_ENTIRE_SCRIPT, "filterLayers.jsx");
app.undo();
if (!exitFlag) {
    alert("完了しました");
}

メモ

分かったこと

doScriptでmain関数を呼ぶような書き方をしている場合、main関数内でexit()した後はdoScript以降の処理に移行する(ちょっと考えれば当たり前なんだけど…)。

積み残し

  • ページ範囲指定
    色んなページの指定の仕方があるのと、数値っぽい文字列を扱わなくちゃならないあたり、こんな単純な書き方で大丈夫かどうかちょっと不安。
    もう少し考えておきたい。
  • fileReadIn関数の引数
    いくつ項目があっても対応できるようにしておきたい。
    arguments.length分itemにプロパティを追加するかたちにするか、あるいは引数を配列でわたすか、かなあ。

参考