エッ!EPUB作れ?

突然の仕事の依頼です.それもお客さんからお金をもらえるわけではない、単なる自分の会社の宣伝用.それもワタクシの大嫌いなEPUBです.EPUBなんてちよっと.epubを解凍してみたら画像とCSSXHTMLが入っているだけで、いろいろサンプルを見てみても対象デバイススマートフォンだったりするのでまるでデザインがギチギチに詰めてあって綺麗じゃないからです.特に読めればいいという感じで作った自炊のEPUBは見るだけでデザインにげんなりします.いやなのでいろいろケチをつけましたが、結局やる羽目になりました.
 
対象はいたって簡単でW3Cで出している勧告やワーキングドラフトのXHTMLを分割してEPUB化するものです.だから私もFlash Getを使ってW3Cからソースデータを落としました.
でも問題はいろいろありました.単一のXHTMLになっているので、これをまずなんとか分割することを考えなければならないのですが、
 
1.個別のXHTMLを一発で作るためには、何らかのルールでグルーピング化し、XSLT2.0のxsl:result-documentでXHTMLに落としてやればいい.
 
2.グルーピングをするのには、XSLT2.0のxsl:for-each-groupを使えばよい.でもこれをやるためには、html/bodyの下にグルーピング対象の要素(h2とか)が平坦に並んでいる必要がある.
 
3.リンクを解決しなければならない.今までは単一のXHTMLだったので<a href="#~">になっていたけれど、こんどは<a href="[分割後のXHTML]#~">としてやらないといけません.
 
4.EPUBでは"OEBPS"のフォルダの下に、それなりに整理して画像フォルダ、スタイルシートCSS)フォルダ、XHTMLフォルダという感じで作ってやる.なので例えばXHTMLを"xhtml"画像を"image"というフォルダにするのだったら、image要素のsrc属性はsrc="../imag/[元のsrc属性]"というように加工してやらねばならない.
 
などなどいろいろありました.
 
まずグルーピングなんですが、W3CXHTMLは<div class="div1">、<div class="div2">、<div class="div3">なんていう要素で階層化してあってbodyの下に「平坦」にグルーピング対象要素が並んでくれないのでこれらは前変換で削除するようにしました.
 
あとグルーピングですがカスタマイズできるよう、一個の関数でグルーピング条件をまとめてあります.
<xsl:template match="xhtm:body">
    <xsl:for-each-group select="*"
            group-starting-with="*[ahf:isGroupStarts(.)]">
        ...
        <xsl:result-documet>で個別のXHTMLを作ります.
        ...
    <xsl:for-each-group>
</xsl:template>
 
グループの最初の要素を決める関数は次のようなものです.これさえ変えればカスタマイズできるのが強みです.<h2>~<h3>~<h4>と並んでいるところは、<h2>や<h3>だけで1つのXHTMLを作らないように工夫してあります.
<xsl:function name="ahf:isGroupStarts" as="xs:boolean">
    <xsl:param name="prmElem" as="element()"/>
    <xsl:variable name="name" select="local-name($prmElem)" as="xs:string"/>
    <xsl:variable name="class" select="string($prmElem/@class)" as="xs:string"/>
    <xsl:choose>
        <xsl:when test="$name eq 'div' and $class eq 'head'">
            <xsl:sequence select="true()"/>
        </xsl:when>
        <xsl:when test="$name eq 'div' and $class eq 'abstract'">
            <xsl:sequence select="true()"/>
        </xsl:when>
        <xsl:when test="$name eq 'div' and $class eq 'divtoc'">
            <xsl:sequence select="true()"/>
        </xsl:when>
        <xsl:when test="$name eq 'h2' and not($prmElem/ancestor::div[@class = 'head'])">
            <xsl:sequence select="true()"/>
        </xsl:when>
        <xsl:when test="$name eq 'h3' and $prmElem/preceding-sibling::*[1][local-name() ne 'h2']">
            <xsl:sequence select="true()"/>
        </xsl:when>
        <xsl:when test="$name eq 'h4' and $prmElem/preceding-sibling::*[1][local-name() ne 'h3']">
            <xsl:sequence select="true()"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:sequence select="false()"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:function>
 
で、なんとかまともなXHTMLをつくれるようにして、あとは画像とCSSは、元のXHTMLからファイルリストを作って、antのコピータスクで一発でコピーさせます.
 
ファイルリストはこんな感じのただのテキストファイルです.
Images-en/w3c_home.png
Images-en/img1_4.png
...
コピータスクはこんな感じです.
<copy todir="${epub.image.dir}">
    <fileset dir="${xhtm2epub.input.dirname}" includesfile="${epub.image.list.file}"/>
</copy>
 
結局XHTMLの分割/生成まで行きました.表示させてみると、リンクもちゃんと動くし画像も表示されます.で気がついたのですが、元のXHTMLの表示とちょっとだけ違うのです.それは、次のようなCSSによるものでした.
 
body {
  background-image: url(logo-WG-Note.png);
}
 
まさか、CSSの中にも画像ファイルの参照があるとは思いませんでした.でもこれを処理してやらないと、(この例では)ワーキングドラフトの画像が表示されません.悔しいのでW3CのSAC(Simple API for CSS)でCSSを読み込んで、この画像を処理してEPUB作成完全自動化にしたいと思います.せっかくantでビルドするので、途中に手作業なんて入れられないのです.