IDMLでの自動組版を試してみる
2017/09/05先日、知人の@macneko_ayuさんたちが開催した「DTPerのスクリプトもくもく会 #2」に参加してきました。まあセミナーじゃないのでその場でみんなもくもく何かを作ってるだけなんですが、家で一人でやるっていうとどうしても気が散るのでなかなかよい会だったかなと。で、#3も近いことなので、当日もくもく作ってたものを出しておこうかなと思います。
IDMLを使った自動組版処理の初歩的実験
InDesignから書き出せるファイル形式のひとつにIDML(InDesign Markup Language)というものがあります。これはInDesignの通常の記録ファイルと完全な互換性はないのですが、ほとんどの内部情報を保ったまま下位バージョンで開けるようになるため、やむを得ず下位バージョンでデータを開くような場合に使われたりしています。で、このIDMLは実はZIPのパッケージファイルで、解凍すると中にXMLファイルや画像ファイルが階層化されて収納されているのが確認できます。であるならば、
- あらかじめ決められた名称で差し替え部分のテキストを作ったInDesignのデータを作り
- それをテンプレートとしてIDML形式で書き出し
- IDMLをテンポラリ領域にプログラムで解凍し
- 別途差し替え用のテキストデータを(CSVなどで)読み込んで、解凍したIDML内テキストに対して置換処理を行い
- 置換処理完了後のIDMLをリネームして出力する
というような手順で簡単な自動組版処理ができるのではないかなと考えました。
普通に再圧縮してもダメ
まず最初にこの手順を手動でやってみたのですが、どうやら普通にZIP形式で再圧縮してもInDesignで読み込めないようです。ちょっと調べてみるとこちらのフォーラムに情報が。なるほど、mimetypeを非圧縮の状態でZIPファイルの先頭に置かなきゃダメと。要はEPUBと同じですね。話が早い。
ということで作ってみたコードが以下。macのターミナル画面で
perl csv2idml.pl CSVファイルのパス IDMLテンプレートファイルのパス 最終出力先フォルダのパス
と入力すれば動くはず。ああ、CSVを扱うためのモジュールとしてText::CSV_XSを使ってますのでCPANから入れる必要があります。このあたり参考にどうぞ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
#! /usr/bin/perl use utf8; use Encode qw/encode decode/; use Text::CSV_XS; use File::Basename qw/basename dirname/; use Archive::Extract; use File::Path; use File::Find; #変換CSVファイルのパスを取得 my $convertCsvFilePath = $ARGV[0]; $convertCsvFilePath = decode('UTF-8', $convertCsvFilePath); #IDMLテンプレートファイルのパスを取得 my $idmlTemplatePath = $ARGV[1]; $idmlTemplatePath = decode('UTF-8', $idmlTemplatePath); #最終出力先のパスを取得 my $exportFolderPath = $ARGV[2]; $exportFolderPath = decode('UTF-8', $exportFolderPath); #idml一時解凍フォルダ名を拡張子抜きで取得 my $idmlTmpFolderName = basename ($idmlTemplatePath, ".idml"); #idml一時解凍フォルダパスの合成 my $idmlTmpFolderPath; $idmlTmpFolderPath = '/tmp/' . $idmlTmpFolderName; #同一フォルダが存在したら連番をつける処理 my $mynum = 1; if (-d $idmlTmpFolderPath){ while (-d $idmlTmpFolderPath){ $idmlTmpFolderPath = ('/tmp/' . $idmlTmpFolderName . '_' . $mynum); $mynum++; } } #出力IDMLファイルの連番指定 my $exportIdmlNum = 1; #各CSVファイルを展開 open(IN,"$convertCsvFilePath"); @eachLineTxts = <IN>; close (IN); my @xmlfilePaths; #最初の1回目用のidml置換処理一時フォルダをtmpに作成 @xmlfilePaths = & unzipIdml2Tmp ($idmlTmpFolderPath); my $countNum = 0; foreach $eachline(@eachLineTxts){ #カウントが10だったらIDMLを出力/IDML出力番号加算/tmpフォルダの中身を消す/新たにテンプレートからidmlをコピー/カウントをリセット if ($countNum == 10){ #idmlファイル出力 #出力先パスの決定 my $idmlExportPath = $exportFolderPath . "/" . "file" . $exportIdmlNum . ".idml"; #出力実行 my $syscommand = 'cd ' . '"' . $idmlTmpFolderPath . '"' . ';zip -0 -X ../tmpIdmlFile.idml mimetype;zip -r ../tmpIdmlFile.idml * -x mimetype */.DS_Store */*/.DS_Store */*/*/.DS_Store;cd ..;mv -f tmpIdmlFile.idml ' . '"' . $idmlExportPath . '"'; system $syscommand; $exportIdmlNum++; #一時ファイルの削除 rmtree($idmlTmpFolderPath); #idml置換処理一時フォルダをtmpに作成 my @xmlfilePaths = & unzipIdml2Tmp ($idmlTmpFolderPath); $countNum = 1; } else { $countNum++; } #置換処理 #CSVをパースして変数に入れる my $csvRef = Text::CSV_XS->new({binary => 1}); $csvRef->parse($eachline); my @eachparameters = $csvRef->fields; my $titleNameString = $eachparameters[0]; my $fullNameString = $eachparameters[1]; #名前が空欄だったら処理せず、そうでなければ置換実行 if ($fullNameString eq ""){ next; } else { & idmlReplace ($titleNameString,$fullNameString,$countNum,\@xmlfilePaths); } } #idmlファイル出力(余り部分) #出力先パスの決定 my $idmlExportPath = $exportFolderPath . "/" . "file" . $exportIdmlNum . ".idml"; #出力実行 my $syscommand = 'cd ' . '"' . $idmlTmpFolderPath . '"' . ';zip -0 -X ../tmpIdmlFile.idml mimetype;zip -r ../tmpIdmlFile.idml * -x mimetype */.DS_Store */*/.DS_Store */*/*/.DS_Store;cd ..;mv -f tmpIdmlFile.idml ' . '"' . $idmlExportPath . '"'; system $syscommand; #一時ファイルの最終削除 rmtree($idmlTmpFolderPath); exit; #IDMLをテンポラリフォルダに解凍 sub unzipIdml2Tmp { my $idmlTmpFolderPath = $_[0]; #解凍実行 my $idmlArchive = Archive::Extract->new(archive => $idmlTemplatePath,type => 'zip') or die; $idmlArchive->extract(to => $idmlTmpFolderPath); #IDML一時作業フォルダ内の各XMLファイルのパスリストを取得 my @xmlfilePaths; find(\&getEachFile, $idmlTmpFolderPath); sub getEachFile { my $file = $_; my $path = $File::Find::name; push(@xmlfilePaths,$path) if ($path =~ /^.*?\/Story_.*?\.xml$/); } return @xmlfilePaths; } #IDML置換サブルーチン sub idmlReplace { #置換テキストの取得 my $titleName = $_[0]; my $fullName = $_[1]; my $replaceNum = $_[2]; my @xmlfilePaths = @{$_[3]}; my $titlenameMarge = "■■■肩書き" . $replaceNum . "■■■"; my $fullnameMarge = "■■■名前" . $replaceNum . "■■■"; foreach $openFilePath(@xmlfilePaths){ open(IN,"$openFilePath"); #連結処理 my @eachXmlLineTxts = <IN>; my $joinedTxt = join("",@eachXmlLineTxts); $joinedTxt = decode('UTF-8', $joinedTxt); close (IN); #置換処理 $joinedTxt =~ s@$titlenameMarge@$titleName@g; $joinedTxt =~ s@$fullnameMarge@$fullName@g; #出力 $joinedTxt = encode('UTF-8', $joinedTxt); open(OUT,"> $openFilePath"); print OUT $joinedTxt; close (OUT); } } |
テスト用のIDMLのファイルとCSVファイルもセットで置いときます。ごく簡単な名刺のテンプレートですがまあテストなので。CSVの氏名はダミーとして自動生成させたものです。
◇
今回のテストで簡単な置換処理ならIDMLベースでやれることがわかりました。まあInDesignのファイル結合とかでも同じことは当然できますが、これのポイントは自動処理の過程に一切InDesignそのものは噛ませてないことでしょうかね。つまりサーバ内に仕込んでも動くはず。もちろんお仕事として自動処理をやるにはこの程度のコード量では済まないでしょうが。なにしろ今回ロクにIDMLそのものの解析すらしてない(笑)。
あ、@macneko_ayuさん、助言ありがとうございます。おかげで動きました。
(2017.9.6)