XSLT2.0で便利になった機能(54) xsl:perform-sort

文書系のスタイルシートを作っていると、なかなかxsl:sortにお目にかかる機会がありません.せいぜいあるとすれば<indexterm>をABC順(日本語ならあいうえお順)にソートするときくらいでしょうか?今回たまたまxsl:sortを使う機会がありました.どのような場合かというと、次のようなDITAのsimpletableを空欄を先に縦結合ついで横結合するというものです.

イメージ 1


でもやたらに空欄を縦結合するわけにはゆかないのであくまでも2列目以降の縦結合は、1列目を縦結合した範囲で行うものとします.
そうするとまず最初に1列目から最終列に向かって縦結合を生成しなければなりません.これを行った結果は従来の行/列の順のセルの順が崩れて、列/行の順になります.例えば次のようなstentryのデータになるでしょう.ここでrow-ends-hereは列の最後を示すマーカーです.

   <stentry row-no="1" col-no="1" morerows="4">1</stentry>
   <stentry row-no="6" col-no="1" morerows="1">2</stentry>
   <stentry row-no="1" col-no="2" morerows="1">Item 1</stentry>
   <stentry row-no="3" col-no="2" morerows="1">Item 2</stentry>
   <stentry row-no="5" col-no="2">Item 3</stentry>
   <stentry row-no="6" col-no="2">Item 1</stentry>
   <stentry row-no="7" col-no="2">Item 2</stentry>
   <stentry row-no="1" col-no="3">A</stentry>
   <stentry row-no="2" col-no="3">B</stentry>
   <stentry row-no="3" col-no="3">C</stentry>
   <stentry row-no="4" col-no="3">D</stentry>
   <stentry row-no="5" col-no="3">E</stentry>
   <stentry row-no="6" col-no="3">E</stentry>
   <stentry row-no="7" col-no="3">F</stentry>
   <stentry row-no="1" col-no="4" morerows="1">That and this</stentry>
   <stentry row-no="3" col-no="4" morerows="1">What is this?</stentry>
   <stentry row-no="5" col-no="4">Many things</stentry>
   <stentry row-no="6" col-no="4" morerows="1">Exists!</stentry>
   <stentry row-no="1" col-no="5" morerows="3">-</stentry>
   <row-ends-here row-no="1" col-no="9999"/>
   <row-ends-here row-no="2" col-no="9999"/>
   <row-ends-here row-no="3" col-no="9999"/>
   <row-ends-here row-no="4" col-no="9999"/>
   <stentry row-no="5" col-no="5">-</stentry>
   <row-ends-here row-no="5" col-no="9999"/>
   <stentry row-no="6" col-no="5" morerows="1">-</stentry>
   <row-ends-here row-no="6" col-no="9999"/>
   <row-ends-here row-no="7" col-no="9999"/>

これはうまく縦結合されていますが、このままではXSL-FOに変換できません.列/行を行/列の順に戻してやらなければならないのです.このようなときに使えるのがxsl:perform-sortです.xsl:perform-sortは、@selectでシーケンスを選択し、子要素のxsl-sortでソートキーを指定します.ですので、次のように書くことができます.

<xsl:perform-sort select="[stentryとrow-ends-hereのシーケンス]">
  <xsl:sort select="@row-no" data-type="number"/>
  <xsl:sort select="@col-no" data-type="number"/>
</xsl:perform-sort>

xsl:sortはXSLT1.0ではxsl:for-eachとxsl:apply-templatesの子要素としか書けませんでした.XSLT2.0では対象を明確に@selectで指定して単純にシーケンスのソート結果を得ることができます.

ソートした結果を<xsl:for-each-group group-ending-with="row-ends-here">でグルーピングしてstrowを生成してやれば次のような結果をえることができます.

   <strow row-no="1">
      <stentry row-no="1" col-no="1" morerows="4">1</stentry>
      <stentry row-no="1" col-no="2" morerows="1">Item 1</stentry>
      <stentry row-no="1" col-no="3">A</stentry>
      <stentry row-no="1" col-no="4" morerows="1">That and this</stentry>
      <stentry row-no="1" col-no="5">-</stentry>
   </strow>
   <strow row-no="2">
      <stentry row-no="2" col-no="3">B</stentry>
   </strow>
   <strow row-no="3">
      <stentry row-no="3" col-no="2" morerows="1">Item 2</stentry>
      <stentry row-no="3" col-no="3" >C</stentry>
      <stentry row-no="3" col-no="4" morerows="1">What is this?</stentry>
   </strow>
   <strow row-no="4">
      <stentry row-no="4" col-no="3">D</stentry>
   </strow>
   <strow row-no="5">
      <stentry row-no="5" col-no="2">Item 3</stentry>
      <stentry row-no="5" col-no="3">E</stentry>
      <stentry row-no="5" col-no="4">Many things</stentry>
      <stentry row-no="5" col-no="5">-</stentry>
   </strow>
   <strow row-no="6">
      <stentry row-no="6" col-no="1" morerows="1">2</stentry>
      <stentry row-no="6" col-no="2">Item 1</stentry>
      <stentry row-no="6" col-no="3">E</stentry>
      <stentry row-no="6" col-no="4" morerows="1">Exists!</stentry>
      <stentry row-no="6" col-no="5" morerows="1">-</stentry>
   </strow>
   <strow row-no="7">
      <stentry row-no="7" col-no="2">Item 2</stentry>
      <stentry row-no="7" col-no="3">F</stentry>
   </strow>

XSL-FOへレイアウトした結果は以下のようになります.本当のDITAにはstenrtyは縦結合/横結合はないのでそのロジックは加えてやらねばなりません.(この例では横結合はありません.)

イメージ 2


ソーティングとグルーピング、それとテンポラリツリーをうまく使うとXSLT1.0では考えることができなかったマジックがXSLT2.0では可能です.この表の自動スパンはその良い例でしょう.