XSLT 2.0で便利になった機能(58) キャラクタマップ(2)

キャラクタマップ(xsl:character-map)についてはすでに以前書きましたのでダブルポストです.xsl:character-mapは、XSLTスタイルシートで入力XMLを出力XMLに変換し、それを実際のファイルに書き出す(シリアライズ)するときに、自動的に「指定した文字」を「指定した文字列」に置換してくれる機能です.

あまり使う機会はなかったのですが、最近Word2003のWordMLをフィルタリングしてコンバージョンするプログラムを書くことがあり、これを使わさせてもらいました.Word2003はWord文書をXMLで表現可能にした最初のWordのバージョンです.それ以来様々な変遷がありましたが、いまやWordでは.docxフォーマット(XMLをフォルダ階層ごとZIPしたファイル)が標準の保存形式になっています.

ところで何故キャラクタマップを使ったかなのですが、WordMLや.docxの中のWordML(XML)はMicrosoft Wordが書き出したものです.なので行末の改行コードがCR/LFになってしまっています.ところがXSLTスタイルシートで書き出したXMLは基本的に行末コードはLFです.フィルタリングしてコンバージョンするので、WinDiffのようなコンペアツールでフィルタリング前後を検証のため比べるのですが、ここで改行コードが違っているとアウトです.文書全体が違っているという表示になってしまいます.

以下がその表示例です.全体が黄土色ですべてが差分ありになってしまっています.

イメージ 1


これでは検証のしようがないのでいろいろ考えた末に使用したのがキャラクタマップでした.使い方は以下の様なものです.

<xsl:character-map name="windows-newlines">
    <xsl:output-character character="&#xFFFD;" string="&#x0D;&#x0A;"/>
</xsl:character-map>

<xsl:output method="xml" version="1.0" encoding="UTF-8" byte-order-mark="no" indent="no" use-character-maps="windows-newlines"/>

<!--テキストノードのテンプレート
    改行をU+FFFDに置き換えます.
    別にU+FFFDでなくともつかわれないコードだったらなんでもOKです.
  -->
<xsl:template match="text()">
    <xsl:analyze-string select="." regex="[\n]">
        <xsl:matching-substring>
            <xsl:text>&#xFFFD;</xsl:text>
        </xsl:matching-substring>
        <xsl:non-matching-substring>
            <xsl:value-of select="."/>
        </xsl:non-matching-substring>
    </xsl:analyze-string>
</xsl:template>

こうすればWordの出力と同様に行末をCR/LFにすることができます.例えば以下の様な感じです.こんどは本当の差分の箇所(黄土色の線で示されている)だけが比べられます.

イメージ 2

まあWordMLなんかを変換する機会はあまりないかもしれませんが、キャラクタマップがあってくれると比較/検証は非常に楽にすることができます.