Word2010で苦戦(その3)

またWord2010で苦戦しています.こんどはWord2010のWordML(.xml)出力です.Word2010のネイティブ出力形式はOpen XML(.docx)ですから、WordML出力は過去のWord2003との互換性のためだけにあると言ってもおかしくありません.でもいくらそうであってもビックリするような出力があります.例えば以下のようなものです.w:pは段落で、w:rは「ラン」と呼ばれるインラインタグです.(関係ない箇所は省略して編集してあります)
 
<w:p>
<w:pPr><w:keepNext/></w:pPr>
<w:r><w:fldChar w:fldCharType="begin"/></w:r>
<w:r><w:instrText> INCLUDEPICTURE  \d "C:\\My Documents\\sample.png" \* MERGEFORMATINET </w:instrText></w:r>
<w:r><w:fldChar w:fldCharType="separate"/></w:r>
<w:r><w:fldChar w:fldCharType="begin"/></w:r>
<w:r><w:instrText> INCLUDEPICTURE  \d "C:\\My Documents\\sample.png" \* MERGEFORMATINET </w:instrText></w:r>
<w:r><w:fldChar w:fldCharType="separate"/></w:r>
<w:r><w:fldChar w:fldCharType="begin"/></w:r>
<w:r><w:instrText> INCLUDEPICTURE  \d "C:\\My Documents\\sample.png" \* MERGEFORMATINET </w:instrText></w:r>
<w:r><w:fldChar w:fldCharType="separate"/></w:r>
<w:r><w:fldChar w:fldCharType="begin"/></w:r>
<w:r><w:instrText> INCLUDEPICTURE  \d "C:\\My Documents\\sample.png" \* MERGEFORMATINET </w:instrText></w:r>
<w:r><w:fldChar w:fldCharType="separate"/></w:r>
<w:r><w:fldChar w:fldCharType="begin"/></w:r>
<w:r><w:instrText> INCLUDEPICTURE  \d "C:\\My Documents\\sample.png" \* MERGEFORMATINET </w:instrText></w:r>
<w:r><w:fldChar w:fldCharType="separate"/></w:r>
<w:r><w:pict><v:shape id="_x0000_i1025" type="#_x0000_t75" style="width:66pt;height:12pt"><v:imagedata src="../../../sample.png"/></v:shape></w:pict></w:r>
<w:r><w:fldChar w:fldCharType="end"/></w:r>
<w:r><w:fldChar w:fldCharType="end"/></w:r>
<w:r><w:fldChar w:fldCharType="end"/></w:r>
<w:r><w:fldChar w:fldCharType="end"/></w:r>
<w:r><w:fldChar w:fldCharType="end"/></w:r>
</w:p>
 
一個のPNG画像をリンク貼り付けで挿入しただけなのですが、編集/保存しているうちにフィールドがなんと五重にネストされて出力されています.でもWordで表示すると画像の実体は1つだけなのでなんの問題なく表示されてしまいます.これを入力して真ん中のw:r/w:pictだけ処理したいのです.あとは邪魔なだけでまったく要りません.Word2003の時は真ん中のw:r/w:pictだけ出力されていましたが、Word2010で変更されてしまいました.
 
これってバグじゃないの~!と本当はWordの出力を直してもらいたいのですが、世界的なアプリケーションのWordをそう簡単にマイクロソフトが直してくれる訳はありません.仕方がないのでスタイルシートで処理してみました.
 
こういうデータは
 
<xsl:template match="w:p">
    <xsl:apply-templates/>
<xsl:template>
 
というような一般的なパターンのテンプレートでは処理できません.この場合はまず
 
<xsl:template match="w:p">
    <xsl:apply-templates select="*[1]"/>
<xsl:template>
 
とします.そしてw:pの子要素は、まず自分を処理して、次にfollowing-sibling::*[1]にapply-templatesするという方法をとります.(tail-recursionというのだと思います)
 
w:rのテンプレートでは、w:fldChar/@w:fldCharType="begin"で次のw:r/w:instrTextが" INCLUDEPICTURE"であるかをチェックします.
条件を満たしたら、次のような機能のテンプレートを呼び出します.
 
1.w:r/w:fldChar/@w:fldCharType="begin"でパラメータのカウントを1増やし.
2.w:r/w:fldChar/@w:fldCharType="end"でパラメータのカウントを1減らします.
3.w:r/w:fldChar/@w:fldCharType="separate"は黙って読み飛ばします.
4.w:r/w:pictがあったらそのw:rのgenerate-id()をパラメータに記憶します.(id-pとします)
5.パラメータのカウントがゼロになったら、次に処理すべきノードであるfollowing-sibling::*[1]のgenerate-id()を求めます.(id-nとします)これとid-pを":"をセパレータとしてconcatして、テンプレートの結果テキストとして呼び出し側に戻します.(<xsl:value-of select="concat($id-p,':',$id-n)"/>)
 
呼び出し側では、テンプレートの戻したテキストを":"で分割して元のid-pとid-nを求めます.まずid-pを指定してw:r/w:pictの内容を出力させます.
 
<xsl:apply-templates select="following-sibling::*[generate-id()=$id-p]" mode="COPY"/>
<xsl:template match="*" mode="COPY">
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates mode="COPY"/>
    </xsl:copy>
</xsl:template>
 
次に処理すべきノードのidを指定して制御を移します.
 
<xsl:apply-templates select="following-sibling::*[generate-id()=$id-n]"/>
 
これで、安全にw:p/w:rだけ出力できます.少しトリッキーですが、XSLT1.0の範疇で十分対応できます.それにしても、マイクロソフトにとっては主力のOffice2010のWordなんですから、こんなバグはなしにしてもらいたいものです.