PandocのICML出力にでんでんマークダウン記法のルビを対応させる
Pandocというプロジェクトがあります(日本語解説はこちらを参照)。これはMarkDown記法などで書かれたドキュメントをHTMLやWord文書、PDFなどに変換して出力できるといったようなものですが、出力対応形式のひとつにICMLが入っています。これはAdobe InCopyの保存形式で、InDesignに配置して流し込むことができます(Pandocは一応EPUBにも変換できるようなのですが、まだEPUB2.0のようなのでまあ・・・)。
なかなか興味深いのでちょっと調べてみたのですが、残念ながらルビには対応していません。まあMarkDown記法自体にルビの記法がないですし、ルビは(当たり前ですが)日本語版InDesignにしかない機能なので当然と言えば当然です。Pandocは国際プロジェクトなので仕方ないところでしょう。
でもルビ使えたら便利そうだよねということで、でんでんマークダウンのルビタグ拡張記法に対応する置換処理をPerlで書いてみました。まあPandoc本体はHaskellで書かれているようなのですが、そんなもんは非専業プログラマとしては手が出ないので手を出しません。コードは以下。
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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
#!/usr/bin/perl use utf8; use warnings; use XML::LibXML; use Encode qw/encode decode/; #変換するICMLファイルのパスを取得 my $convertIcmlFilePath = $ARGV[0]; $convertIcmlFilePath = decode('UTF-8', $convertIcmlFilePath); #パース実行 my $parser = XML::LibXML->new(); $parser->no_network(1); my $dom = $parser->parse_file($convertIcmlFilePath); ######### DOM置換 ######### foreach my $paragraphStyleRange($dom->findnodes('//ParagraphStyleRange')){ #既定の段落スタイルの値を保存 my $appliedParagraphStyle = $paragraphStyleRange->findvalue('@AppliedParagraphStyle'); #最終置き換え用ノード定義 my $repleceParagraphStyleRangeNode = XML::LibXML::Element->new( "ParagraphStyleRange" ); $repleceParagraphStyleRangeNode->setAttribute( "AppliedParagraphStyle", $appliedParagraphStyle ); #子ノードをリストで取得 my @paragraphStyleRangeChildNodes = $paragraphStyleRange->childNodes; #置換後の中身の配列を定義 my @replacedParagraphStyleRangeChildNodes; #CharacterStyleRange処理用サブルーチンに処理を投げる foreach $paragraphStyleRangeChildNode (@paragraphStyleRangeChildNodes){ my $type = $paragraphStyleRangeChildNode->nodeType; if ($type == 1){ my $name = $paragraphStyleRangeChildNode->nodeName; #エレメントノードでCharacterStyleRangeノードだった場合サブルーチンに処理を投げて結果を得、順番に配列にPush if ($name eq "CharacterStyleRange"){ my @replacedNodes = & replaceCharacterStyleRangeNodes($paragraphStyleRangeChildNode); foreach (@replacedNodes) {push(@replacedParagraphStyleRangeChildNodes,$_)}; } else { #エレメントノードでCharacterStyleRangeノードでなかった場合はそのまま配列にPush push (@replacedParagraphStyleRangeChildNodes,$paragraphStyleRangeChildNode); } } else { #エレメントノードでなかった場合はそのまま配列にPush push (@replacedParagraphStyleRangeChildNodes,$paragraphStyleRangeChildNode); } } #新たに作った$paragraphStyleRangeノードに子ノード流し込み foreach(@replacedParagraphStyleRangeChildNodes){$repleceParagraphStyleRangeNode->appendChild( $_ )} #元の$paragraphStyleRangeノードと新しく作ったヤツの置換を実行 $paragraphStyleRange->replaceNode($repleceParagraphStyleRangeNode); } ######### 出力処理 ######### #ノードをシリアライズして文字列に my $documents = $dom->findnodes('//Document'); my $serializedTxt = $$documents[0]->serialize; #ヘッダ付加 my $mergedTxt = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\x0A" . '<?aid style="50" type="snippet" readerVersion="6.0" featureSet="513" product="8.0(370)" ?>' . "\x0A" . '<?aid SnippetType="InCopyInterchange"?>' . "\x0A" . $serializedTxt; #後処理整形 $mergedTxt =~ s@</CharacterStyleRange><CharacterStyleRange@</CharacterStyleRange>\x0A<CharacterStyleRange@g; $mergedTxt =~ s@\x0A@\x0D\x0A@g; #出力実行 $mergedTxt = encode('UTF-8', $mergedTxt); open(OUT,"> $convertIcmlFilePath"); print OUT $mergedTxt; close (OUT); exit; ######### CharacterStyleRange処理サブルーチン ######### sub replaceCharacterStyleRangeNodes { #引数の取得 my $originalCharacterStyleRangeNode = $_[0]; #既定の文字スタイルの値を保存 my $appliedCharacterStyle = $originalCharacterStyleRangeNode->findvalue('@AppliedCharacterStyle'); #子ノードをリストで取得 my @characterStyleRangeChildNodes = $originalCharacterStyleRangeNode->childNodes; #置換後の中身の配列を定義 my @replacedNodes; #Content処理用サブルーチンに処理を投げる foreach $characterStyleRangeChildNode(@characterStyleRangeChildNodes){ my $type = $characterStyleRangeChildNode->nodeType; if ($type == 1){ my $name = $characterStyleRangeChildNode->nodeName; #エレメントノードでContentノードだった場合中身をsplitしてルビ部分とそうじゃないやつを分けて、順番にCharacterStyleRangeノードでくるんで配列にPush if ($name eq "Content"){ $contentValue = $characterStyleRangeChildNode->textContent; my @splitContentsArray = split(/({.*?})/, $contentValue); foreach $splitContent (@splitContentsArray){ #ルビタグブロックだった場合の処理 if ($splitContent =~ /^{([^\|]+?)\|(.+?)}$/){ #親字のブロックとルビのブロックに分割して処理 my $rbBlock = $1; my $rtBlock = $2; my $rbBlockLength = length($rbBlock); my @rtBlockArray = split(/\|/, $rtBlock); #親字ブロックの文字数がルビブロックの分割数と一致していたらモノルビとして処理 if ($rbBlockLength == scalar(@rtBlockArray)){ my $repleceCharacterStyleRangeNode = XML::LibXML::Element->new( "CharacterStyleRange" ); $repleceCharacterStyleRangeNode->setAttribute( "AppliedCharacterStyle", $appliedCharacterStyle ); $repleceCharacterStyleRangeNode->setAttribute( "RubyFlag", "1" ); my $joinedMonoRubyString = join(" ", @rtBlockArray); $repleceCharacterStyleRangeNode->setAttribute( "RubyString", $joinedMonoRubyString ); my $newContentNode = XML::LibXML::Element->new( "Content" ); $newContentNode->appendTextNode( $rbBlock ); $repleceCharacterStyleRangeNode->appendTextNode( "\x0A" ); $repleceCharacterStyleRangeNode->appendChild( $newContentNode ); $repleceCharacterStyleRangeNode->appendTextNode( "\x0A" ); push (@replacedNodes,$repleceCharacterStyleRangeNode); } else { #親字ブロックの文字数がルビブロックの分割数と一致しなければグループルビとして処理 my $repleceCharacterStyleRangeNode = XML::LibXML::Element->new( "CharacterStyleRange" ); $repleceCharacterStyleRangeNode->setAttribute( "AppliedCharacterStyle", $appliedCharacterStyle ); $repleceCharacterStyleRangeNode->setAttribute( "RubyFlag", "1" ); my $joinedGroupRubyString = join("", @rtBlockArray); $repleceCharacterStyleRangeNode->setAttribute( "RubyString", $joinedGroupRubyString ); $repleceCharacterStyleRangeNode->setAttribute( "RubyType", "GroupRuby" ); my $newContentNode = XML::LibXML::Element->new( "Content" ); $newContentNode->appendTextNode( $rbBlock ); $repleceCharacterStyleRangeNode->appendTextNode( "\x0A" ); $repleceCharacterStyleRangeNode->appendChild( $newContentNode ); $repleceCharacterStyleRangeNode->appendTextNode( "\x0A" ); push (@replacedNodes,$repleceCharacterStyleRangeNode); } #ルビタグブロックでなかった場合の処理 } else { my $repleceCharacterStyleRangeNode = XML::LibXML::Element->new( "CharacterStyleRange" ); $repleceCharacterStyleRangeNode->setAttribute( "AppliedCharacterStyle", $appliedCharacterStyle ); my $newContentNode = XML::LibXML::Element->new( "Content" ); $newContentNode->appendTextNode( $splitContent ); $repleceCharacterStyleRangeNode->appendTextNode( "\x0A" ); $repleceCharacterStyleRangeNode->appendChild( $newContentNode ); $repleceCharacterStyleRangeNode->appendTextNode( "\x0A" ); push (@replacedNodes,$repleceCharacterStyleRangeNode); } } } else { #エレメントノードでContentノードでなかった場合はそのままCharacterStyleRangeノードでくるんで配列にPush my $repleceCharacterStyleRangeNode = XML::LibXML::Element->new( "CharacterStyleRange" ); $repleceCharacterStyleRangeNode->setAttribute( "AppliedCharacterStyle", $appliedCharacterStyle ); $repleceCharacterStyleRangeNode->appendTextNode( "\x0A" ); $repleceCharacterStyleRangeNode->appendChild( $characterStyleRangeChildNode ); $repleceCharacterStyleRangeNode->appendTextNode( "\x0A" ); push (@replacedNodes,$characterStyleRangeChildNode); } } else { #エレメントノードでなかった場合はそのまま配列にPush push (@replacedNodes,$characterStyleRangeChildNode); } } return @replacedNodes; } |
ターミナルで
perl このコードのファイルパス.pl 変換するPacDocの出力したICMLファイルのパス.icml
のような感じで動作し、元ファイルを上書きします。結果はこの通り。
PandocでMarkDown形式のファイルを.icmlにするのは
pandoc マークダウンファイルのパス.md -s -o 出力先パス.icml
のような感じ。
Pandoc自体のインストールは私はhomebrewでやりましたが、インストーラも存在するようです。
Pacdocを絡めたDTPフローの話はここやここにあります。やはりみんないろいろ試されていますね。
テスト用に使ったマークダウンファイルもこちらに置いておきます。
(2017.11.15)