プログラミングで良く使用するデータ構造に、キーと値の関連性を保持するものがあります.多くのプログラミング言語ではmapという名前でこの機能をサポートしています.XSLT 3.0でもmapという「データ型」がサポートされるようになりました.今回はこれを簡単に紹介したいと思います.
まずXSLT 3.0より以前では、様々な工夫をしてキーと対応する値を表現してきました.いろいろなスタイルシートを見てきましたが、次のようなものがあります.一つはこの構造を外部のXMLとして与えるというものです.例えば次のようなXMLファイルを用意します.
[mapdata.xml]
<items>
<item key="red" value="赤"/>
<item key="green" value="緑"/>
<item key="blue" value="青"/>
</items>
これに対して次のようなスタイルシートでキーに対する値を求めることができます.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tmf="http://www.tmakita.org/xslt/function"
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>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tmf="http://www.tmakita.org/xslt/function"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
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':'青'}"/>
動的にマップを生成する例として、xsl:paramから作る例を試してみました.これも上記と同じ結果となります.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tmf="http://www.tmakita.org/xslt/function"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
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になっていますが、早く勧告になってもらいたいものです.