XSLT 4.0 Proposal (3) Union Types

今回はUnion Typesの話題です.Union Typeつまり共用体という型というと昔のC言語のunionを思い出します.XMLではどうなのでしょうか?

XMLでも共用体はXML Schemaで表すことができます.ある要素のコンテンツや属性が複数の型から成ることは十分ありえるのです.例えばXML Schemaのunion型の例でWebによく出てくるのはジーンズのサイズを表す以下のようなスキーマです.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    elementFormDefault="qualified"
    attributeFormDefault="unqualified">
    
    <xs:element name="jeans_size" type="jeansType">
    </xs:element>
    
    <xs:simpleType name="jeansType">
        <xs:union memberTypes="sizebyno sizebystring" />
    </xs:simpleType>
    
    <xs:simpleType name="sizebyno">
        <xs:restriction base="xs:positiveInteger">
            <xs:maxInclusive value="42"/>
        </xs:restriction>
    </xs:simpleType>
    
    <xs:simpleType name="sizebystring">
        <xs:restriction base="xs:string">
            <xs:enumeration value="small"/>
            <xs:enumeration value="medium"/>
            <xs:enumeration value="large"/>
        </xs:restriction>
    </xs:simpleType>    
</xs:schema>

これにより、ジーンズのサイズは42(インチ?)までの正の整数、もしくは"small", "medium", "large"のいずれかの文字列と表現できます.このXML Schemeを参照するXMLインスタンスは例えば次のように書けるでしょう.

<?xml version="1.0" encoding="UTF-8"?>
<jeans_size
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="./xsd/union-sample.xsd">42</jeans_size>

これを処理するXSLTスタイルシートの例は以下のようになります.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    xmlns:ahf="http://www.antennahouse.com/names/function"
    xmlns:saxon="http://saxon.sf.net/" 
    exclude-result-prefixes="xs math"
    version="3.0">
    
    <xsl:template match="jeans_size">
        <xsl:choose>
            <xsl:when test="data() instance of xs:positiveInteger">
                <xsl:message select="'Numeric jeans size = ' || (data() treat as xs:positiveInteger) => string()"/>
            </xsl:when>
            <xsl:when test="data() instance of xs:string">
                <xsl:message select="'Enumerated jeans size = ' || data()"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:message select="'Invalid jeans size type'"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

data()関数はSchema Awareな変換をやるときでないとめったに使用することはありません.引数に一般的にはノードを取って、それに型付けられた値を返してくれます.省略すればカレントコンテキストになります.(この場合はjeans_size要素)

これで先ほどのXMLを処理すれば

Numeric jeans size = 42

と表示されます.

でもこのように複数の型の値を取りうるデータを処理するときにいちいちXML Schemeを書かなければならないのは骨の折れる仕事です.つまりコストが高いです.もっと簡単にできないか?というのがUnion Typesの提案の趣旨です.

最新のSaxon 10.0ではEEバージョンでないとUnion Typesは使えないのですが、これより以前のバージョンならsaxon:asで表現できます.以下のスタイルシートが入力データにXML Schemeを関連付けなくとも動いてくれるフレキシブルなバージョンです.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    xmlns:ahf="http://www.antennahouse.com/names/function"
    xmlns:saxon="http://saxon.sf.net/" 
    exclude-result-prefixes="xs math"
    version="3.0">
    
    <xsl:template match="jeans_size">
        <xsl:variable name="jeansSize" saxon:as="union(xs:positiveInteger,xs:string)" select="."/>
        <xsl:choose>
            <xsl:when test="data($jeansSize) instance of xs:positiveInteger">
                <xsl:message select="'Numeric jeans size = ' || ($jeansSize treat as xs:positiveInteger) => string()"/>
            </xsl:when>
            <xsl:when test="data($jeansSize) instance of xs:string">
                <xsl:message select="'Enumerated jeans size = ' || ($jeansSize treat as xs:string)"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:message select="'Invalid jeans_size type'"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    
</xsl:stylesheet>

このスタイルシートだと、入力XMLファイルが

<?xml version="1.0" encoding="UTF-8"?>
<jeans_size>42</jeans_size>

のようにwell-formedだけのものであっても、ちゃんと

Numeric jeans size = 42

と出てくれます.いちいちスキーマを作る手間がないので実に簡単です.

またUnion Typesは入力データから導かれた変数ばかりに使う必要はありません.次のような例で変数をそれに結び付けて使うこともできます.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    xmlns:ahf="http://www.antennahouse.com/names/function"
    xmlns:saxon="http://saxon.sf.net/" 
    exclude-result-prefixes="xs math"
    version="3.0">
    
    <xsl:variable name="gUnionVar" saxon:as="union(xs:date, xs:dateTime, xs:time)" select="current-date()"/>

    <xsl:template match="/">
        <xsl:choose>
            <xsl:when test="$gUnionVar instance of xs:date">
                <xsl:message select="'$gUnionVar = ' || format-date($gUnionVar, 'Today is [Y,1,4]-[M,1,2]-[D,1,2]')"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:message select="'$gUnionVar is not instance of xs:date'"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    
</xsl:stylesheet>

この場合、ちゃんと

$gUnionVar = Today is 2020-04-07

と出てくれます.

Union TypesはSaxon 10ではEEでないと使えないのがちょっと残念ですが、これはたぶんschme-awareな変換が主な用途、つまり商用での使用が主な使い道と考えてのことなのでしょう.