他のスタイルシートをインポートして使う.

今まで自分でスタイルシートを作って自分で動かすのが常でした.しかしxsl:importを使用すればベースになるスタイルシートをインポートして、それに対する修正差分のみ記述すれば、少ない工数で目的を達成することができます.いちいちお客様やプロジェクト、出力物単位にスタイルシートを個々に作っていたのではメンテナンスも大変になります.ベースになるスタイルシートが共通部品として機能できるように汎用的に作られていればインポートは有効な手段になります.

では自分の作ったスタイルシートはインポートする価値があるのか?これはインポートしてみない限りわかりません.インポートして、それに対する差分やオーバーライドをうまく記述して目的の出力が得られれば良いはずです.たまたまこのような機会があったのでいろいろ試してみることができました.

しかしやってみると、なかなか大変だということがわかってきました.どのようにもカスタマイズできる汎用的なベースのスタイルシートというのはそう簡単ではないのです.例えば次のような例がありました.

インポートするベースのスタイルシートで次のようなテンプレートがあったとします.

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

<!-- b要素のテンプレート(斜体を処理)-->
<xsl:template match="*[contains(@class,' hi-d/b ')]" priority="2">

<!-- i要素のテンプレート(ボールドを処理)-->
<xsl:template match="*[contains(@class,' hi-d/i ')]" priority="2">

これはDITAの例ですが、後者2つに対してpriority="2"がついているのは、b要素は@class="+ topic/ph hi-d/b ", i要素はclass="+ topic/ph hi-d/i "というクラス属性がついているからです.つまりi要素もb要素もph要素の特殊化されたものという位置づけです.

これを単にインポートしただけなら何の問題もありません.しかし、インポートする側で

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

とオーバーライドするテンプレートを書いてしまうと事態は一変します.一番優先されるのがインポートする側のオーバーライドしたテンプレートになってしまうのです.つまり簡単に言えば、i要素とb要素はph要素として処理されるようになってしまいます.「エッだって、priority="2"をつけて優先順位を上げているよね?」と考えられる方もいらっしゃるでしょう.たしかにそのとおりなのですが、期待通りには動いてくれないのです.これは、以下の箇所に説明があります.

6.4 Conflict Resolution for Template Rules

ここの説明によれば、まず第一に"import precedence"により順位が判定されるとあります.つまりこの例では無条件にインポートする側に書いたテンプレートが優先されてしまいます.

"import precedence"は、以下の箇所に説明があります.


スタイルシートAが順にB,Cをインポートしていて、BはDをインポートし、CはEをインポートしていたなら、"import precedence"は、小さい順にD,B,C,Aになるのです.そしてpriority="N"による優先順位付けはこのあとのステップで行われます.

ですので、このようなベースになるようなph要素のテンプレートをオーバーライドしてしまった場合には、インポートする側のテンプレートでそれより高いpriorityをつけていたテンプレートもインポートする側に記述してやらないとならなくなります.つまり

<!-- b要素のテンプレート(斜体を処理)-->
<xsl:template match="*[contains(@class,' hi-d/b ')]" priority="2">

<!-- i要素のテンプレート(ボールドを処理)-->
<xsl:template match="*[contains(@class,' hi-d/i ')]" priority="2">

は、インポートする側でも必要になります.

あと、重要な点として考えられるのがテンプレートのパラメータインタフェースの変更です.例えば

<xsl:template name="A">
  <xsl:param name="prmA" as="xs:string" required="yes"/>
  <xsl:param name="prmB" as="xs:string" required="yes"/>
  ...
</xsl:template>

というテンプレートがインポートする側にあって、これをオーバーライドするテンプレートをパラメータを1こ増やしてインポートする側に次のように書くものとします.

<xsl:template name="A">
  <xsl:param name="prmA" as="xs:string" required="yes"/>
  <xsl:param name="prmB" as="xs:string" required="yes"/>
  <xsl:param name="prmC" as="xs:string" required="yes"/>
  ...
</xsl:template>

インポートする側では、prmA,prmB,prmCをすべてつけて呼び出して矛盾のないようにします.

<xsl:call-template name="A">
  <xsl:with-param name="prmA" select="'...'"/>
  <xsl:with-param name="prmB" select="'...'"/>
  <xsl:with-param name="prmC" select="'...'"/>
  ...
</xsl:template>

しかし結果はコンパイルエラーとなってしまいます.何故かといえば、インポートされる側にはprmCのないテンプレートAの呼び出しが必ず記述されているからです.この部分をいくらインポートする側でオーバーライドして書き換えていてもダメです.

結局この場合はパラメータのインタフェースを変えようとしたら

<xsl:template name="A1">
  <xsl:param name="prmA" as="xs:string" required="yes"/>
  <xsl:param name="prmB" as="xs:string" required="yes"/>
  <xsl:param name="prmC" as="xs:string" required="yes"/>
  ...
</xsl:template>

のような別物をつくってやり、呼び出す側もA1を呼び出すようにしなければコンパイルエラーは消すことができません.という訳で、ベースのスタイルシートでは、共通で使用するような重要なテンプレートは十分パラメータを吟味してつくっておく必要があるように考えられます.でもそれって結構難しいですよね.ダメならテンプレートの名前を変えなければなりませんが、あちこちで使用していると修正量もバカになりません.

という訳でなかなか共通の部品足りうるxsl:import可能なスタイルシートというものはなかなか難しいことがわかりました.やはり汎用的な部品をつくるというのはよほど熟慮を重ねないと、なかなかできるものではなさそうです.