arinoth's memo

arinothのメモ

InDesign向けのXML書き出し機能を追加

しばらく作っていたMarkdownプレビューツールも、InDesign向けのXML書き出しを追加したので、ひとまず完成です。

github.com

[File]→[export InDesignXML]を選択すると、Markdownファイルと同じフォルダ内にXMLファイルが書き出されます。

昔作ったGrunt向けのXML変換タスクに比べると、次のような点でパワーアップしました。

  • p要素やh1〜h6要素は親要素のクラスに合わせてタグ名を自動変更する(本文のp要素とコラム内のp要素を区別できる)
  • ちゃんとDOMを操作してXML化しているので、XMLエラーが起きない(前は検索置換で強引にHTMLをXMLにしていたので……)
  • 画像の倍率やトリミング幅も属性として持ち込める(ただしそれを反映するInDesign側のスクリプトを新規作成する必要あり)

このXMLファイルは何に使うのかというと、InDesignの「XMLを読み込み」機能で読み込んで、 XMLタグを「タグをスタイルにマップ」機能を使ってInDesignのスタイルに割り当ててやります。

レイアウト情報は無理ですが、見出しや太字などのスタイルを自動設定できるわけですね。

f:id:arinoth:20160525000324j:plain

すでに実案件でちょっとずつ使い始めているのですが、マニュアル作りとかInDesign向けのスクリプト作成とか色々やらないといけません。

最低限の使い方を書いた簡易マニュアル

cheerioが凄かった

今回のMarkdownプレビューの中の話なのですが、HTMLをXMLにうまく変換するためのいいものがないかと探した結果、 cheerioという、うってつけなパッケージが見つかりました。

これはDOM操作のAPIを持たないNode.js向けにDOM操作APIを提供し、さらにjQuery互換メソッドで操作できてしまうという便利なものです。 HTMLだけでなくXMLの操作もできます。

github.com

個人的には「Node.jsは標準だとDOM操作できない」というあたりで妙に感心してしまいました。 そうかサーバサイドだからウィンドウもないしDOMもないのか……と。

HTMLを読み込む

HTMLのテキストを読み込んで、loadメソッドに渡すだけです。jQuery風に$を使えます。

fileUtil.js
var cheerio = require('cheerio');
……中略……
  exportInDesignXML: function(htmlfile){    
……中略……
    try{
      var src = fs.readFileSync(htmlfile, 'utf-8');
      var $ = cheerio.load(src);
    } catch (err){
      dialog.showErrorBox('File Open Error', err.message);
      throw new Error('cannot open file.');
    }
    // body要素を取得
    var body = $('body');

XMLのDOMを作成

XMLのDOMを作るときも同様ですが、オプションでxmlModeをtrueにします。

    // XMLを構築
    var out = '<?xml version="1.0" encoding="UTF-8"?>';
        out += '<story xmlns:aid5="http://ns.adobe.com/AdobeInDesign/5.0/" '
            + 'xmlns:aid="http://ns.adobe.com/AdobeInDesign/4.0/">'
            + '</story>';
    var $x = cheerio.load(out, {
      normalizeWhitespace: true,
      xmlMode: true
    });
    var xstory = $x('story');

HTMLからXMLへ要素を複製する

後は再帰関数を書いて、要素を複製していきます。

    function htmltoxml(htmldom, xmldom){
      if(htmldom.type == 'tag'){
        // 要素の移植
        var name = htmldom.tagName;
        var classname = $(htmldom).attr('class');
        ……中略……
        // HTMLのタグ名とクラス名を連結したものをXMLのタグ名とする
        if(classname) name = name + '_' + classname;
        $x(xmldom).append('<' + name + '></' + name + '>' );
        // 追加したノードを取得
        var nodes = $x(xmldom).children(name);
        var newnode = nodes[nodes.length-1];
        ……中略……
        // 子の取得
        var contents = $(htmldom).contents();
        if(contents.length > 0){
          for(var i=0; i<contents.length; i++){
            htmltoxml(contents.get(i), newnode);
          }
        }
      } else if(htmldom.type == 'text'){
        // テキストノード
        // テキストすべて16進数になってしまうが問題はないらしい
        $x(xmldom).append(htmldom);
      }
    }

DOM操作もあくまで互換なので、createElementがなくて、jQuery風のappendは使えるというあたりも面白いです。

XMLのファイル書き出し

brはちゃんと<br/>にしてくれるのですが、imgは<img></img>になってしまい、 そのままだとInDesignがグラフィック要素と認識してくれないので、 XMLファイルを書き出す直前に検索置換で強制的に直します。

    htmltoxml(body.get(0), xstory.get(0));

    //書き出しファイル名
    var xmlfilepath = htmlfile.replace('.html', '.xml');
    console.log(xmlfilepath);
    var xmltext = $x.xml();
    // img要素が単独要素にならないので、置換で強引に直す。
    xmltext = xmltext.replace(/>[^<]*<\/img>/g, '/>\n');
    try {
      fs.writeFileSync(xmlfilepath, xmltext);    
    } catch (err){
      dialog.showErrorBox('File Write Error', err.message);
      throw new Error('cannot write file.');
    }

余談ですが段組みが……

テスト用に三段組みのサンプルも作ってみたのですが、段組みが複数ページにまたがるとどうも崩れてしまいます。 Vivliostyleさんのサイトには段組みサンプルもあるのですが、図やら表やら色々なものを組み込むとダメなのかもしれません。

https://github.com/lwohtsu/mdpreview/tree/master/test/test3

これは一見うまくできているように見えますが、実はページをまたがないように強引にテキストを範囲分けしています。 実際の原稿整理でそんなことやってられません(InDesignで組んだほうが楽になってしまう……)。

f:id:arinoth:20160525005307j:plain

段組みを使っていても、ページをまたがない場合は問題ありません。

f:id:arinoth:20160525005251j:plain

※具体的な案件名でいうと『最○常識』は組めないけど、『いち○さ教本』なら組める。

ページをまたぐ段組みって案件的にそう多くもないし、大まかなページ分量さえ割り出せればいいので、ごまかしごまかしやってみるしかないですね。