InDesign向けのXML書き出し機能を追加
しばらく作っていたMarkdownプレビューツールも、InDesign向けのXML書き出しを追加したので、ひとまず完成です。
[File]→[export InDesignXML]を選択すると、Markdownファイルと同じフォルダ内にXMLファイルが書き出されます。
昔作ったGrunt向けのXML変換タスクに比べると、次のような点でパワーアップしました。
- p要素やh1〜h6要素は親要素のクラスに合わせてタグ名を自動変更する(本文のp要素とコラム内のp要素を区別できる)
- ちゃんとDOMを操作してXML化しているので、XMLエラーが起きない(前は検索置換で強引にHTMLをXMLにしていたので……)
- 画像の倍率やトリミング幅も属性として持ち込める(ただしそれを反映するInDesign側のスクリプトを新規作成する必要あり)
このXMLファイルは何に使うのかというと、InDesignの「XMLを読み込み」機能で読み込んで、 XMLタグを「タグをスタイルにマップ」機能を使ってInDesignのスタイルに割り当ててやります。
レイアウト情報は無理ですが、見出しや太字などのスタイルを自動設定できるわけですね。
すでに実案件でちょっとずつ使い始めているのですが、マニュアル作りとかInDesign向けのスクリプト作成とか色々やらないといけません。
cheerioが凄かった
今回のMarkdownプレビューの中の話なのですが、HTMLをXMLにうまく変換するためのいいものがないかと探した結果、 cheerioという、うってつけなパッケージが見つかりました。
これはDOM操作のAPIを持たないNode.js向けにDOM操作APIを提供し、さらにjQuery互換メソッドで操作できてしまうという便利なものです。 HTMLだけでなくXMLの操作もできます。
個人的には「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で組んだほうが楽になってしまう……)。
段組みを使っていても、ページをまたがない場合は問題ありません。
※具体的な案件名でいうと『最○常識』は組めないけど、『いち○さ教本』なら組める。
ページをまたぐ段組みって案件的にそう多くもないし、大まかなページ分量さえ割り出せればいいので、ごまかしごまかしやってみるしかないですね。