XSLT 2.0で便利になった機能(38) union、intersect、except演算子

今回は、union、intersect、except演算子について紹介します.これらの演算子はnode()*をオペランドとして使用します.この3つの中で、XSLT1.0では、union演算子がサポートされていました.実はunionと"|"は同じです.例えばXSLT1.0では
 
<xsl:apply-templates select="frontmatter | chapter | part | appendix | backmatter"/>
 
というように使用していたと思います."|"はSQLのユニオンクェリーに似て、重複を排除したノードの集合を、ドキュメントオーダーで返してくれていたのです.
 
unionは和集合、intersectは積集合、exceptは差集合に対応します.でも、集合とnode()*は等価ではありません.一般に集合は重複が許されますし、順序も定義されていませんが、これらの演算結果は、重複は排除され、順序はドキュメントオーダーになる点が異なります.例を見てみましょう.
 
<xsl:variable name="src" as="document-node()">
    <xsl:document>
        <product type="pencil" maker="三菱"     price="100"/>
        <product type="pencil" maker="コーリン" price="90"/>
        <product type="pencil" maker="トンボ"   price="110"/>
        <product type="pencil" maker="北星"     price="105"/>
        <product type="note"   maker="コクヨ"   price="200"/>
        <product type="note"   maker="ツバメ"   price="150"/>
        <product type="note"   maker="マルマン" price="100"/>
    </xsl:document>
</xsl:variable>
<xsl:variable name="a" select="$src/product[@type='pencil' or xs:integer(@price) lt 110]"/>
<xsl:variable name="b" select="$src/product[@type='note'   or xs:integer(@price) lt 110]"/>
 
と定義しておいて以下を実行します.
 
    <xsl:message select="'$a'"/>
    <xsl:call-template name="disp">
        <xsl:with-param name="prmNode" select="$a"/>
    </xsl:call-template>
    <xsl:message select="'$b'"/>
    <xsl:call-template name="disp">
        <xsl:with-param name="prmNode" select="$b"/>
    </xsl:call-template>
    <xsl:message select="'$a union $b'"/>
    <xsl:call-template name="disp">
        <xsl:with-param name="prmNode" select="$a union $b"/>
    </xsl:call-template>
    <xsl:message select="'$a intersect $b'"/>
    <xsl:call-template name="disp">
        <xsl:with-param name="prmNode" select="$a intersect $b"/>
    </xsl:call-template>
    <xsl:message select="'$a except $b'"/>
    <xsl:call-template name="disp">
        <xsl:with-param name="prmNode" select="$a except $b"/>
    </xsl:call-template>
    ...
   
    <xsl:template name="disp">
        <xsl:param name="prmNode" as="node()*" required="yes"/>
        <xsl:for-each select="$prmNode">
            <xsl:message select="'type=',string(@type),'maker=',string(@maker),'price=',string(@price)"/>
        </xsl:for-each>
    </xsl:template>
   
すると結果は以下のようになります.
$a
type= pencil maker= 三菱 price= 100
type= pencil maker= コーリン price= 90
type= pencil maker= トンボ price= 110
type= pencil maker= 北星 price= 105
type= note maker= マルマン price= 100
$b
type= pencil maker= 三菱 price= 100
type= pencil maker= コーリン price= 90
type= pencil maker= 北星 price= 105
type= note maker= コクヨ price= 200
type= note maker= ツバメ price= 150
type= note maker= マルマン price= 100
$a union $b
type= pencil maker= 三菱 price= 100
type= pencil maker= コーリン price= 90
type= pencil maker= トンボ price= 110
type= pencil maker= 北星 price= 105
type= note maker= コクヨ price= 200
type= note maker= ツバメ price= 150
type= note maker= マルマン price= 100
$a intersect $b
type= pencil maker= 三菱 price= 100
type= pencil maker= コーリン price= 90
type= pencil maker= 北星 price= 105
type= note maker= マルマン price= 100
$a except $b
type= pencil maker= トンボ price= 110
 
いかがでしょう.確かに仕様どおり動いているのが確認できました.
でもXSLTでこれらの演算子を使う場合はあるでしょうか?exceptだけ頭に浮かびました.例えば、次のようなコードを書いたことがあります.
 
<xsl:copy-of select="@*[name()!='xml:lang']"/>
 
これはexcept演算子を使って次のようにちょっとスマートに書くことが出来ます.
 
<xsl:copy-of select="@* except @xml:lang"/>
 
また、ある要素に対して、ルートからドキュメントオーダーでたどってその要素にたどりつくまでの要素の集合を求めたいことがあります.これは、ancestor軸やpreceding軸を使ったXPath式では求められないものです.これはカレントコンテキストノードがある前提で次のようにexceptを使うことで求められます.
 
<xsl:variable name="fromStartToHere" select="//* except //*[current() &lt;&lt; .]" as="element()*"/>
 
しかし、やはりこれらの演算子は、XMLデータベースでクェリーの結果に対して適用するのが本格的な使い方のように感じられますがいかがでしょうか?