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

今回はxsl:caharacter-map、xsl:output-characterについて紹介したいと思います.
 
この機能は主に「見る」文書を出力する際に使うものだと思います.例えば従来HTML出力では、HTMLで定義されている実体参照を書き込もうと思っても、disable-output-escapingを使用してxsl:valu-ofやxsl:textを書く必要がありました.
 
    <xsl:text disable-output-escaping="yes">&amp;copy;</xsl:text>
 
これはいちいちdisable-output-escaping="yes"と書いて、意図的にテキストを組み合わせなければなりませんでした.また、たまたま入力テキスト中に、U+00A9 COPYRIGHT SIGNがあっても、それは実体参照とすることはできませんでした.
 
HTML4では多くの文字実体があらかじめ定義されているのに、それが効果的に利用できなかった訳です.ここで、xsl:caharacter-map、xsl:output-characterが登場します.例えば
 
<xsl:output method="html" encoding="UTF-8" use-character-maps="test"/>
<xsl:character-map name="test">
    <xsl:output-character character="&#x00A9;" string="&amp;copy;"/>
</xsl:character-map>
 
としておけば、本物のU+00A9は、&copy;といういう実体参照で出力HTMLファイルの中に現れてくれます.character属性で記述した文字コードが、string属性で記述した文字列に置換されるわけです.もうdisable-output-escapingで変な小細工をする必要はなくなります.コピーマークはまだ多くのエディタで見られるから良いですが、例えばU+00A0 NO-BREAK SPACEはどうでしょうか?スペースなのでグリフを見られませんが、HTMLではU+0020のように詰まるのではなく、間隔が確保されますから、&nbsp;と表示された方がわかりやすいことは言うまでもありません.
 
このように考えると結構有用な機能に思えます.ただし「見る」HTMLです.直ちにブラウザで表示されるだけでソースを見ないし保守もないHTMLだけだったら、実体参照になっていようがどうがあまり関係ないでしょう.
 
また<xsl:text>&copy;</xsl:text>が、そのまま&copy;として出力できないか?とも思いますが、これはスタイルシートXMLなので本体でDTDを定義してやらない限り無理な話です.
 
では、出力がXMLではどうでしょうか?XMLでは、完全に出力するXMLDTDに依存します.出力するXMLDTDが関連付けられていて、そこに実体定義があるかにかかわっています.HTMLとは違ってXMLでは、既定の実体文字定義は&amp;&lt;&gt;&quot;しかありません.
 
かつて、DocBookもバージョン4なんかは、.entファイルがDTDにインクルードされており、多くの実体定義が使えるようになっていました.しかしバージョン5になると、このような.entファイルへの参照は消えてなくなってしまいました.DTDもRelaxからコンバージョンしたと思われるdocbook.dtd一個だけです.またDITAのDTDは一切このような文字参照の実体定義をインクルードしていません.
 
XMLの場合は、このような状態ですから、自分で一生懸命DTDを修正して実体定義を取り込んでやらない限り、あまり有用とは考えられません.
 
あとdisable-output-escapingを置き換える方法として、次のような特別な例があります.
次のような段落テキストがあって、delete-start~delete-endの部分は赤色で、insert-start~insert-endの部分は青色で表示したいような場合です.通常こんなXMLはありませんが、CMSが2つのバージョンを比較して差分を出力するようなときにお目にかかることがあります.
 
<p><?pi delete-start?>これは削除された部分です.<?pi delete-end?><?pi insert-start?>これは挿入された部分です.<?pi insert-end?></p>
 
処理命令は確かに対にはなっていますが、要素ではありませんから、通常の処理を行うのは無理です.XSL-FOに出すなら、
 
<xsl:template match="processing-instruction('pi')[.='delete-start']">
    <xsl:text disable-output-escaping="yes">&lt;fo:inline color='red'&gt;</xsl:text>
</xsl:template>
<xsl:template match="processing-instruction('pi')[.='delete-end']">
    <xsl:text disable-output-escaping="yes">&lt;/fo:inline&gt;</xsl:text>
</xsl:template>
 
とするでしょう.これを少しなりともスマートにしてくれるのが、xsl:character-mapです.
 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY  delete-start "&#xE100;">
<!ENTITY  delete-end   "&#xE101;">
<!ENTITY  insert-start "&#xE102;">
<!ENTITY  insert-end   "&#xE103;">
]>
<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" encoding="UTF-8" use-character-maps="pi"/>
<xsl:character-map name="pi">
    <xsl:output-character character="&delete-start;" string="&lt;fo:inline coolor='red'&gt;"/>
    <xsl:output-character character="&delete-end;" string="&lt;/fo:inline&gt;"/>
    <xsl:output-character character="&insert-start;" string="&lt;fo:inline coolor='blue'&gt;"/>
    <xsl:output-character character="&insert-end;" string="&lt;/fo:inline&gt;"/>
</xsl:character-map>
<xsl:template match="p">
    <fo:block>
        <xsl:apply-templates/>
    </fo:block>
</xsl:template>
<xsl:template match="processing-instruction('pi')[.='delete-start']">
    <xsl:text>&delete-start;</xsl:text>
</xsl:template>
<xsl:template match="processing-instruction('pi')[.='delete-end']">
    <xsl:text>&delete-end;</xsl:text>
</xsl:template>
<xsl:template match="processing-instruction('pi')[.='insert-start']">
    <xsl:text>&insert-start;</xsl:text>
</xsl:template>
<xsl:template match="processing-instruction('pi')[.='insert-end']">
    <xsl:text>&insert-end;</xsl:text>
</xsl:template>
 
これで次のような出力を得られます.
 
<?xml version="1.0" encoding="UTF-8"?><fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format"><fo:inline coolor='red'>これは削除された部分です.</fo:inline><fo:inline coolor='blue'>これは挿入された部分です.</fo:inline></fo:block>
 
でも私だったらやっぱり、簡便なdisable-output-escapeを使ってしまいますね.ちなみにXSLT2.0ではdisable-output-escapeはdeprecatedの扱いです.
 
参考にW3Cではすでにxsl:character-mapを公開しています.