XSLT2.0で便利になった機能(52) xsl:param/@as属性

もう前に一回書いている気もしますが、XSLT2.0ではxsl:paramで関数やテンプレートにパラメータを記述するときに、as属性でパラメータの型を記述することができます.

<xsl:param name="prmMax" as="xsl:integer" required="yes"/>
<xsl:param name="prmInputStr" as="xsl:string" required="yes"/>

※ required="yes"が書けるのはxsl:templateの場合のみです.xsl:functionでは元々必須です.

ところで要素を渡すときにはどう書くでしょうか?たぶん以下だと思います.

<xsl:param name="prmElem" as="element()" required="yes"/>

実は私もずっとこのように書いてきました.

でも今般あるスタイルシートを作る仕事をしていて、私のスタイルシートを他の方が流用して使用する場合ができてきました.ですので決して間違いがないようにもっとパラメータのインタフェースを厳しくしています.それは渡す要素の要素名を指定することです.

例えばdiv要素を渡すことがわかっている、というかdiv要素向けに作った関数やテンプレートの場合、

<xsl:param name="prmDiv as="element(div)" required="yes"/>

と書くことができます.

こうすると、もし間違ってdiv以外の要素を$prmDivに渡してしまうと速攻でoXygen IDEがエラーを出してくれます.

例えば

[Saxon-PE 9.4.0.6] XTTE0590: Required item type of value of parameter $prmDiv is element('':div); supplied value has item type element('':p)

という具合です.ここでelement('':div)の''はネームスペースを表しています.この例では処理する文書はネームスペースを使っていないので、''となります.

このようにxsl:paramに記述すれば絶対テンプレートや関数の呼び出しでドジを踏むことはありません.

プログラム(スタイルシート)は、開発ばかりでなくメンテナンスや流用をもとの開発者以外の別の方が行うことも決して少なくはありません.開発のときはとかく納期に追われて勢いで作ってしまうことも多いのですが、その後のスタイルシートの寿命を考えてみれば、このようにインタフェースを間違いのないように作っておくことは非常に重要です.

ちなみに、要素名が指定できない場合も存在します.例えば、任意の要素からxml:lang属性をxs:stirngで返す関数があったとしましょう.たぶん次のようになるはずです.

<xsl:function name="tmf:getXmlLang" as="xs:string">
  <xsl:param name="prmElem" as="element(*)">
  <xsl:sequence select="string($prmElem/@xml:lang)"/>
</xsl:function>

このような場合はパラメータの性格上要素名を特定できないので、as="element(*)"となります.

ちなみにこのような記法はXPath2.0で定義されています.

2.5.3 SequenceType Syntax

本格的には要素型は

ElementTest ::= "element" "(" (ElementNameOrWildcard ("," TypeName "?"?)?)? ")"

と書けます.例えば

element(po:shipto, po:address) refers to an element node that has the name po:shipto and has the type annotation po:address (or a schema type derived from po:address)

という解説があります.po:shiptoという要素名(ただしpoは名前空間接頭辞)で、XML Schemaでpo:address型と定義されているということです.でもこのような使い方はSchema AwareなXSLTプロセッサ+XML Schemaを使用しない限りは使わないでしょう.特に文書データはこういう使い方はあまりしないと思います.

ちなみにDITAのスタイルシートではこのように要素名を指定することはしませんのでこのやり方は使えないと思います.例えばtopicのテンプレートは一般に

<xsl:template match="*[contains(@class,' topic/topic ')]>

と記述します.同じtopicの@class属性を持っているtaskを参照するときもmatch="task"とは決して書きません.例えば

<xsl:template match="*[contains(@class,' task/task ')] priority="2">

です.DITAはクラス属性で要素を分類します.ですので、

<xsl:param name="prmTopic" as="element(topic)" required="yes"/>

とは書かないのです.一般的にこのように書いてしまうと、task,reference,conceptをtopicと同様に扱って渡す場合に困ってしまうからです.