CoffeeScriptはAdobeCSスクリプトのエクリチュールとなりえるか

まえまえから気になっていたのでInDesignをうごかすJavaScriptCoffeeScriptでかくとどんなかんじになるかためしてみた.
あくまでためしてみるだけだしInDesignでやるのはそんなにバッキバキなことではなくDTPでやることを自動的にやるかんじで.

こんなんです

  • マスターページに名刺っぽい台紙をつくっておいたinddファイルを開く(1ページ目はテンプレート)
  • スクリプトのなかにオブジェクトをもっておいて,ひとつのデータにつき1ページ目のテンプレートを複製していく
  • 各ページにあるテキストフレームからラベルのついたものをひっぱりだしてテキストをいれかえる
  • X-1aなPDFを書きだしてinddファイルをとじる

InDesign CoffeeScripting from nbqx on Vimeo.

CoffeeScriptでかいたのはこんなかんじです.はてダだとまだシンタックスハイライトにCoffeeScript無いっぽいのでpythonにしときます

##お約束
file = new File("/path/to/sample.indd")
app.open(file)
doc = app.activeDocument
tpl = doc.pages[0]

##このデータを…
data = [
  {name_jp:"唐 一郎", name_en:"Ichiro Kara", post:"会長"},
  {name_jp:"唐 二郎", name_en:"Jiro Kara", post:"副会長"},
  {name_jp:"唐 三郎", name_en:"Saburo Kara", post:"社長"},
  {name_jp:"唐 四郎", name_en:"Shiro Kara", post:"副社長"},
  {name_jp:"唐 五郎", name_en:"Goro Kara", post:"書記長"},
  {name_jp:"唐 六郎", name_en:"Rock'n'Roll Kara", post:"副書記長"},
  {name_jp:"唐 七郎", name_en:"Shichiro Kara", post:"代表取締役 専務"},
  {name_jp:"唐 八郎", name_en:"Hachiro Kara", post:"資材部 リーダー"}
]

##Arrayから条件にあうものをひとつピック
findOne = (a, fn) ->
  ret = []
  ary = if a instanceof Array then a else [a]
  for v in ary
    if fn(v) then ret.push(v)
  if ret.length is 0 then null else ret[0]

##引数のなにかをコピペ
copyAndPaste = (src) ->
  src.select()
  app.copy()
  app.paste()
  doc.selection[0]

##ラベルのついたテキストフレームを探して文字列をいれかえ
proc = (page,data) ->
  post = findOne page.allPageItems, ((o) -> if o.label is "post" then true else false)
  name_jp = findOne page.allPageItems, ((o) -> if o.label is "name_jp" then true else false)
  name_en = findOne page.allPageItems, ((o) -> if o.label is "name_en" then true else false)
  if post? then post.contents = data.post
  if name_jp? then name_jp.contents = data.name_jp
  if name_en? then name_en.contents = data.name_en

##データごとにページを追加しつつ…
for val in data
  p = doc.pages.add()
  for itm in tpl.allPageItems
    clone = copyAndPaste(itm)
    clone.move(p)
    clone.geometricBounds = itm.geometricBounds
  proc(p, val)

##X-1aなPDFを吐きだしてドキュメントを閉じる
pdf = new File(doc.fullName.toString().replace(/\.indd$/,".pdf"))
doc.exportFile(ExportFormat.PDF_TYPE,pdf,false,'[PDF/X-1a:2001 (日本)]')
doc.close(SaveOptions.NO)

alert('Done!')

でもってJavaScriptに吐きだしたやつ

(function() {
  var clone, copyAndPaste, data, doc, file, findOne, itm, p, pdf, proc, tpl, val, _i, _j, _len, _len2, _ref;
  file = new File("/path/to/sample.indd");
  app.open(file);
  doc = app.activeDocument;
  tpl = doc.pages[0];
  data = [
    {
      name_jp: "唐 一郎",
      name_en: "Ichiro Kara",
      post: "会長"
    }, {
      name_jp: "唐 二郎",
      name_en: "Jiro Kara",
      post: "副会長"
    }, {
      name_jp: "唐 三郎",
      name_en: "Saburo Kara",
      post: "社長"
    }, {
      name_jp: "唐 四郎",
      name_en: "Shiro Kara",
      post: "副社長"
    }, {
      name_jp: "唐 五郎",
      name_en: "Goro Kara",
      post: "書記長"
    }, {
      name_jp: "唐 六郎",
      name_en: "Rock'n'Roll Kara",
      post: "副書記長"
    }, {
      name_jp: "唐 七郎",
      name_en: "Shichiro Kara",
      post: "代表取締役 専務"
    }, {
      name_jp: "唐 八郎",
      name_en: "Hachiro Kara",
      post: "資材部 リーダー"
    }
  ];
  findOne = function(a, fn) {
    var ary, ret, v, _i, _len;
    ret = [];
    ary = a instanceof Array ? a : [a];
    for (_i = 0, _len = ary.length; _i < _len; _i++) {
      v = ary[_i];
      if (fn(v)) {
        ret.push(v);
      }
    }
    if (ret.length === 0) {
      return null;
    } else {
      return ret[0];
    }
  };
  copyAndPaste = function(src) {
    src.select();
    app.copy();
    app.paste();
    return doc.selection[0];
  };
  proc = function(page, data) {
    var name_en, name_jp, post;
    post = findOne(page.allPageItems, (function(o) {
      if (o.label === "post") {
        return true;
      } else {
        return false;
      }
    }));
    name_jp = findOne(page.allPageItems, (function(o) {
      if (o.label === "name_jp") {
        return true;
      } else {
        return false;
      }
    }));
    name_en = findOne(page.allPageItems, (function(o) {
      if (o.label === "name_en") {
        return true;
      } else {
        return false;
      }
    }));
    if (post != null) {
      post.contents = data.post;
    }
    if (name_jp != null) {
      name_jp.contents = data.name_jp;
    }
    if (name_en != null) {
      return name_en.contents = data.name_en;
    }
  };
  for (_i = 0, _len = data.length; _i < _len; _i++) {
    val = data[_i];
    p = doc.pages.add();
    _ref = tpl.allPageItems;
    for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
      itm = _ref[_j];
      clone = copyAndPaste(itm);
      clone.move(p);
      clone.geometricBounds = itm.geometricBounds;
    }
    proc(p, val);
  }
  pdf = new File(doc.fullName.toString().replace(/\.indd$/, ".pdf"));
  doc.exportFile(ExportFormat.PDF_TYPE, pdf, false, '[PDF/X-1a:2001 (日本)]');
  doc.close(SaveOptions.NO);
  alert('Done!');
}).call(this);

まだCoffeeScriptだとこんなべんりにできるよーとか把握してなくてアレなのですが,とかくループのネストが凶暴な牙を剥きがちなCS系のJavaScriptでループがかなりスッキリするし,あと今回はつかってないんだけどHash(というかオブジェクトというかjsonというか)もforでkeyとvalueがひっぱってこれるのでスッキリするんじゃないかとおもいます.それと何気にelvis演算子が小気味いいとおもいました,これもけっこうちゃんとチェックするのメンドーだし.ただじぶんの場合,JavaScript書くときに三項演算子をけっこうつかうのでそれが"if xxx then yyy else zzz"になるのはちょっとなーとおもいました.そのためだけに"`"つかって生JSいれるのもアレだし…

しかしながら,まだがっつりつかっていこうってかんじではないけど可能性としてはありかもなとおもいました.

つかJavaScriptエクリチュールとしてちょーおもろい