XSLT3.0への道(21) マップ

プログラミングで良く使用するデータ構造に、キーと値の関連性を保持するものがあります.多くのプログラミング言語ではmapという名前でこの機能をサポートしています.XSLT 3.0でもmapという「データ型」がサポートされるようになりました.今回はこれを簡単に紹介したいと思います.

まずXSLT 3.0より以前では、様々な工夫をしてキーと対応する値を表現してきました.いろいろなスタイルシートを見てきましたが、次のようなものがあります.一つはこの構造を外部のXMLとして与えるというものです.例えば次のようなXMLファイルを用意します.


[mapdata.xml]
<?xml version="1.0" encoding="UTF-8"?>
<items>
  <item key="red" value="赤"/>
  <item key="green" value="緑"/>
  <item key="blue" value="青"/>
</items>


これに対して次のようなスタイルシートでキーに対する値を求めることができます.


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xs"
    version="2.0">
    
    <xsl:variable name="mapDoc" as="document-node()" select="document('mapdata.xml')"/>

    <xsl:function name="tmf:getColor" as="xs:string">
        <xsl:param name="prmKey" as="xs:string"/>
        <xsl:variable name="result" as="attribute()?" select="($mapDoc//item[string(@key) eq $prmKey]/@value)[1]"/>
        <xsl:sequence select="if (exists($result)) then string($result) else '???'"/>
    </xsl:function>
  
    <xsl:template match="/">
      <xsl:message select="'red=',tmf:getColor('red')"/>
      <xsl:message select="'green=',tmf:getColor('green')"/>
      <xsl:message select="'blue=',tmf:getColor('blue')"/>
      <xsl:message select="'purple=',tmf:getColor('purple')"/>
    </xsl:template>
  
</xsl:stylesheet>

結果は次のようにメッセージ出力されるでしょう.

red= 赤
green= 緑
blue= 青
purple= ???

またxs:stringのシーケンスを2つ用意して次のように定義しても同じ結果を得ることができます.

  <xsl:variable name="key" as="xs:string+" select="('red','green','blue')"/>
  <xsl:variable name="value" as="xs:string+" select="('赤','緑','青')"/>
  
  <xsl:function name="tmf:getColor" as="xs:string">
    <xsl:param name="prmKey" as="xs:string"/>
    <xsl:variable name="index" as="xs:integer?" select="index-of($key,$prmKey)"/>
    <xsl:sequence select="if (exists($index)) then $value[$index] else '???'"/>
  </xsl:function>

しかしいずれにせよキーと値を素直に表せるデータ構造ではありませんでした.XSLT 3.0ではダイレクトに次のようにスタイルシートに書くことができます.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xs map"
    version="3.0">
    
    <xsl:variable name="mapColors" as="map(xs:string,xs:string)">
        <xsl:map>
          <xsl:map-entry key="'red'"   select="'赤'"/>
          <xsl:map-entry key="'green'" select="'緑'"/>
          <xsl:map-entry key="'blue'"  select="'青'"/>
        </xsl:map>
    </xsl:variable>

    <xsl:function name="tmf:getColor" as="xs:string">
        <xsl:param name="prmKey" as="xs:string"/>
        <xsl:variable name="result" as="xs:string?" select="(map:get($mapColors,$prmKey))[1]"/>
        <xsl:sequence select="if (exists($result)) then $result else '???'"/>
    </xsl:function>
    
    <xsl:template match="/">
      <xsl:message select="'red=',tmf:getColor('red')"/>
      <xsl:message select="'green=',tmf:getColor('green')"/>
      <xsl:message select="'blue=',tmf:getColor('blue')"/>
      <xsl:message select="'purple=',tmf:getColor('purple')"/>
    </xsl:template>
  
</xsl:stylesheet>

map()はいわばコンストラクタの関数なのですが、XSLT 3.0では関数そのものもデータ型と定義出来ます.ここがすごいところです.map:getはマップ型の変数とキーを与えて、対応する値を取りだすことができます.なければ空シーケンスを返します.

もしマップの定義が「ダサイ!」とお考えならば次のようにXPath式で一発で書くことも可能です.

    <xsl:variable name="mapColors" as="map(xs:string,xs:string)" select="map{'red':'赤','green':'緑','blue':'青'}"/>

前者は動的にスタイルシートでマップを作れます.マップの内容が静的に与えうるものならば、XPathで書いた方が楽ですね.

動的にマップを生成する例として、xsl:paramから作る例を試してみました.これも上記と同じ結果となります.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xs map"
    version="3.0">
    
    <xsl:param name="PRM_COLORS" as="xs:string" select="'red=赤,green=緑,blue=青'"/>
    
    <xsl:variable name="mapColors" as="map(xs:string,xs:string)">
        <xsl:map>
            <xsl:variable name="colorDefinitions" as="xs:string*">
                <xsl:analyze-string select="$PRM_COLORS" regex="[,]">
                    <xsl:matching-substring/>
                    <xsl:non-matching-substring>
                        <xsl:sequence select="."/>
                    </xsl:non-matching-substring>
                </xsl:analyze-string>
            </xsl:variable>
            <xsl:for-each select="$colorDefinitions">
                <xsl:variable name="colorDefinition" as="xs:string" select="."/>
                <xsl:variable name="key" as="xs:string" select="substring-before($colorDefinition,'=')"/>
                <xsl:variable name="value" as="xs:string"  select="substring-after($colorDefinition,'=')"/>
                <xsl:map-entry key="$key" select="$value"/>
            </xsl:for-each>
        </xsl:map>
    </xsl:variable>
    
    <xsl:function name="tmf:getColor" as="xs:string">
        <xsl:param name="prmKey" as="xs:string"/>
        <xsl:variable name="result" as="xs:string?" select="(map:get($mapColors,$prmKey))[1]"/>
        <xsl:sequence select="if (exists($result)) then $result else '???'"/>
    </xsl:function>
    
    <xsl:template match="/">
    <xsl:message select="'red=',tmf:getColor('red')"/>
    <xsl:message select="'green=',tmf:getColor('green')"/>
    <xsl:message select="'blue=',tmf:getColor('blue')"/>
    <xsl:message select="'purple=',tmf:getColor('purple')"/>
    </xsl:template>
</xsl:stylesheet>


マップは非常に便利です.XSLT 3.0は2015年11月にCandidate Recommendationになっていますが、早く勧告になってもらいたいものです.