さて前回の続きで、FO Plug-inの話です.
■ おかしなI18N処理
FO Plug-inは、概略次のような処理を行います.
FO Plug-inは、概略次のような処理を行います.
1.DITA共通の初期処理(topicのパージング、conrefの処理などなど)
2.トピックのマージ処理(mapまたはbookmapに基づいてtopicをマージ)
3.索引の前処理(前回紹介したもの、indextermを別ネームスペースの要素に置き換え)
4.FOの作成
5.I18N前処理
6.I18N後処理
2.トピックのマージ処理(mapまたはbookmapに基づいてtopicをマージ)
3.索引の前処理(前回紹介したもの、indextermを別ネームスペースの要素に置き換え)
4.FOの作成
5.I18N前処理
6.I18N後処理
6の結果がFormatterでPDFに変換されます.以上の処理内容は、[DITA-OT]/demo/fo/build.xmlを読み解くとわかります.(Antの知識が必要ですね)
<fo:block text-indent="0em" space-before="0.6em" space-after="0.6em" space-after.optimum="3pt">この製品は教育用目的のみに与えられる.This product is meant for educational purposes only.</fo:block>
これがI18N前処理を経由すると次のようになります.
<fo:block space-after="0.6em" space-after.optimum="3pt" space-before="0.6em" text-indent="0em"><opentopic-i18n:text-fragment char-set="Japanese">この製品は教育用目的のみに与えられる.</opentopic-i18n:text-fragment>This product is meant for educational purposes only.</fo:block>
最後にI18N後処理を経由すると次のようになります.
<fo:block space-after="0.6em" space-after.optimum="3pt" space-before="0.6em" text-indent="0em" line-height-shift-adjustment="disregard-shifts"><fo:inline line-height="100%" font-family="KozMinProVI-Regular">この製品は教育用目的のみに与えられる.</fo:inline>This product is meant for educational purposes only.</fo:block>
KozMinProVI-Regularとは、ただWindowsを使っているだけではそう耳にするフォントではないです.Adobe ReaderでCJK言語パック(今は出されていないみたい)を入れるとAcrobatのresourceフォルダにインストールされるフォントのはずです.
I18N前処理が使っているのは、boomap(map)のxml:lang属性に指定されたファイルです.例えば、xml:lang="ja-JP"と指定すると、[DITA-OT]/demo/do/cfg/fo/i18n/ja_JP.xmlが使われます.ja_JP.xmlの内容(一部)は以下のようなもので、char-set="Japanese"は文字コードからマッピングされるのがわかります.
<alphabet char-set="Japanese">
<character-set>
<!-- CJK Unified Ideographs -->
<character-range>
<start include="yes">一</start>
<end include="yes">龿</end>
</character-range>
<!-- 途中省略 -->
<!-- Katakana Phonetic Extensions -->
<character-range>
<start include="yes">ㇰ</start>
<end include="yes">ㇿ</end>
</character-range>
<!-- Arrows -->
<character-range>
<start include="yes">←</start>
<end include="yes">⇿</end>
</character-range>
<!-- Roman numerals -->
<character-range>
<start include="yes">Ⅰ</start>
<end include="yes">Ↄ</end>
</character-range>
<!-- Enclosed Alphanumerics -->
<character-range>
<start include="yes">①</start>
<end include="yes">⓿</end>
</character-range>
</character-set>
</alphabet>
<character-set>
<!-- CJK Unified Ideographs -->
<character-range>
<start include="yes">一</start>
<end include="yes">龿</end>
</character-range>
<!-- 途中省略 -->
<!-- Katakana Phonetic Extensions -->
<character-range>
<start include="yes">ㇰ</start>
<end include="yes">ㇿ</end>
</character-range>
<!-- Arrows -->
<character-range>
<start include="yes">←</start>
<end include="yes">⇿</end>
</character-range>
<!-- Roman numerals -->
<character-range>
<start include="yes">Ⅰ</start>
<end include="yes">Ↄ</end>
</character-range>
<!-- Enclosed Alphanumerics -->
<character-range>
<start include="yes">①</start>
<end include="yes">⓿</end>
</character-range>
</character-set>
</alphabet>
この方法では、xml:lang="ja-JP"からja_JP.xmlを経てフォントが決定されます.例えば、日本語の文章で、中国漢字を紹介するような文書は組版できないでしょう.U+4E00からU+9FBFのCJK統合漢字のすべてを日本語フォントはサポートしているわけではないのです.例えば中国語の「ツアィチェン(さようなら)」の2文字の後の「見」の簡体字U+89C1は日本語フォントとしてよく使う「MS 明朝」はグリフを持っていません.
また日本語の文章でも英文交じりの場合、英文部分を日本語フォントで組版する場合と英文フォントで組版する場合があります.このあたりは、デザイナーの趣味の問題でもありますが、この方法では前者には対応できません.
結局このI18n Preprocess~I18n Postprocessは、西欧の組版を元に他の言語をサポートするという頭で作られています.西欧以外の言語はUnicodeのコード範囲(コードブロック)を指定しさえすればなんとかなるという思想です.
一般的には、ベースとなるmapまたはboomapのxml:langからメインの言語用のフォントを決め、特別な言語のフォントをアサインするためには、topicや任意のinline要素にオーサリングレベルでxml:langを指定して、それからスタイルシートで適切なフォントをアサインするのが常識でしょう.
また、標準でついてくるchar-set="Japanese"に対応するフォントが、KozMinProVI-Regularなのも不勉強のそしりを免れません.これは[DITA-OT]/demo/fo/cfg/fo/font-mappings.xmlに記述されています.Windowsさえ使っていれば、日本語だったら「MS 明朝」、中国語簡体字だったら「SimSun」、中国語繁体字だったら「MingLiu」くらいは常識でわかるはずです.AdobeのAcrobat Readerについてくるフォントをならべているのはまともに調べていない証拠です.(Render-XのXEPのフォント定義ファイルもこんな感じでした)
■ 手抜きのid処理
トピックがマージされた中間ファイルからXSL-FOを作るとき、idの処理が重要になります.例えば、topicのidはPDFのしおり(Book Mark)からのジャンプ先となるからです.また、xrefでなんらかの要素を参照しているときも、参照先のFO(Formatting Object)でid属性を生成していないと、リンクが行き先不明になってしまいます.
トピックがマージされた中間ファイルからXSL-FOを作るとき、idの処理が重要になります.例えば、topicのidはPDFのしおり(Book Mark)からのジャンプ先となるからです.また、xrefでなんらかの要素を参照しているときも、参照先のFO(Formatting Object)でid属性を生成していないと、リンクが行き先不明になってしまいます.
では、FO Plug-inのid生成処理はどのようになっているのでしょうか?例えば、もっとも一般的な<p>要素を見てみましょう.
<xsl:template match="*[contains(@class, ' topic/p ')]">
<fo:block xsl:use-attribute-sets="p" id="{@id}">
<xsl:apply-templates/>
</fo:block>
</xsl:template>
<fo:block xsl:use-attribute-sets="p" id="{@id}">
<xsl:apply-templates/>
</fo:block>
</xsl:template>
あきれて物も言えません.いくらXSL-FOといえどもXMLファイルなのですからid属性はユニークであることがあたりまえです.これでは、id=""というfo:blockが山ほど出来てしまうことでしょう.元々がRenderXのXEP専用のPlug-inでXEPがチェックを行っていないために平気でこのようなコーディングをしています.Antenna HouseのFormatterは厳しくエラーを出します.せめて以下くらいのコーディングはしてもらいたいものです.
<xsl:template match="*[contains(@class, ' topic/p ')]">
<fo:block xsl:use-attribute-sets="p">
<xsl:if test="string(@id)">
<xsl:attribute name="id" select="string(@id)"/>
</xsl:if>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
<fo:block xsl:use-attribute-sets="p">
<xsl:if test="string(@id)">
<xsl:attribute name="id" select="string(@id)"/>
</xsl:if>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
しかしこれでも致命的な欠陥があります.それは、「topicのidは文書中でユニークである必要がある.topic以下の要素のidはそのtopic中でユニークであれば良い」ということをまったく考慮していないからです.(まあよくこんなスタイルシートが動いているものだと思います.)
さらに、もっと大変なことはこのidのコーディングは、topicがtopicrefのあるmapもしくはbookmapから複数回参照された際に、二重のidを生成してしまうということです.一般的にtopicは再利用がしやすいようにモジュール化されますが、それでも一つの文書の中からは一回しか参照されないのではないか?と思うかもしれません.しかし現実世界はそんなに甘くないのです.例えば私が担当しているシステムでは、CMSの文書新旧比較機能により、バンバンと同じtopicを二重参照するbookmapが送られてきます.
ですからidの生成機能は、xrefとの関連もあいまって実は非常にDITAの場合実装が難しいのです.FO Plug-inをベースに業務に使えるスタイルシートにしようとしたらそれは「大きな間違い」です.FO Plug-inはまったく初歩的なidの生成要件も満たしていません.