前々回で関数を返す関数というのをつくってみました.
<!--関数を返す関数-->
<xsl:function name="ahf:getFunc" as="function(*)">
<xsl:param name="prmFuncName" as="xs:QName"/>
<xsl:param name="prmArity" as="xs:integer"/>
<xsl:sequence select="function-lookup($prmFuncName,$prmArity)"/>
</xsl:function>
<xsl:function name="ahf:getFunc" as="function(*)">
<xsl:param name="prmFuncName" as="xs:QName"/>
<xsl:param name="prmArity" as="xs:integer"/>
<xsl:sequence select="function-lookup($prmFuncName,$prmArity)"/>
</xsl:function>
しかしこの関数は、パラメータ与えられた関数名と引数の数を持った関数をfunction-lookup()で返すというもので、関数自体は外部にxsl:functionとして定義されている必要があります.ですのでサンプルとしてはいまいちです.おもしろくありません.
Scalaの本なんかを読むと、関数リテラルというのがあって、無名関数をさらっとリテラルとして記述できます.実はXSLT3.0(XPath 3.0)でもこのような機能があります.それはインライン関数式と言われるものです.前回なんの前提もなく
<xsl:variable name="secondPowerOf" as="function(xs:integer) as xs:integer" select="function($a as xs:integer) as xs:integer {$a * $a}"/>
という変数を書きましたが、このselect属性の中身がインライン関数式です.これは次のようなBNFで表されます.
3.1.7 Inline Function Expressions
http://www.w3.org/TR/2013/CR-xpath-30-20130108/#id-inline-func
http://www.w3.org/TR/2013/CR-xpath-30-20130108/#id-inline-func
InlineFunctionExpr ::= "function" "(" ParamList? ")" ("as" SequenceType)? FunctionBody
ParamList ::= Param ("," Param)*
Param ::= "$" EQName TypeDeclaration?
TypeDeclaration ::= "as" SequenceType
FunctionBody ::= EnclosedExpr
EnclosedExpr ::= "{" Expr "}"
ParamList ::= Param ("," Param)*
Param ::= "$" EQName TypeDeclaration?
TypeDeclaration ::= "as" SequenceType
FunctionBody ::= EnclosedExpr
EnclosedExpr ::= "{" Expr "}"
それほど難しくはありません."function()"と書いて、"()"内に関数のパラメータを記述し、その後にasを続けて関数の戻り値を書きます.そして"{}"で囲んで、関数の実体を記述します.
さてここで考えたのは次のようなことです.
1.ある関数型の戻り値を持つxsl:functionを作る.
2.その関数はxsl:paramを持ち
3.そのパラメータ値によりカスタマイズされた関数の実体を返す.
2.その関数はxsl:paramを持ち
3.そのパラメータ値によりカスタマイズされた関数の実体を返す.
XSLTでこのようなことはできるのか?と考えました.ここまでできれば本当にXSLTは関数型言語になったと言えるのではと思ったからです.ともかくわからないので、まずはということで次のようなサンプルを作りました.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ahf="http://www.yahoo.co.jp/tmakita">
<xsl:template match="root">
<xsl:variable name="selectFunc" as="function(element()) as element()*" select="ahf:funcGetChildElementByName('d')"/>
<xsl:copy>
<xsl:sequence select="$selectFunc(.)"/>
</xsl:copy>
</xsl:template>
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ahf="http://www.yahoo.co.jp/tmakita">
<xsl:template match="root">
<xsl:variable name="selectFunc" as="function(element()) as element()*" select="ahf:funcGetChildElementByName('d')"/>
<xsl:copy>
<xsl:sequence select="$selectFunc(.)"/>
</xsl:copy>
</xsl:template>
<xsl:function name="ahf:funcGetChildElementByName" as="function(element()) as element()*">
<xsl:param name="prmElemName" as="xs:string"/>
<xsl:sequence select="function($a as element()) as element()* {$a/child::*[starts-with(name(),$prmElemName)]}"/>
</xsl:function>
<xsl:param name="prmElemName" as="xs:string"/>
<xsl:sequence select="function($a as element()) as element()* {$a/child::*[starts-with(name(),$prmElemName)]}"/>
</xsl:function>
</xsl:stylesheet>
要素rootにマッチしたら、下位要素から要素名が"d"で始まるものを選んで出力側に書き込むというものです.xsl:functionのahf:funcGetChildElementByNameが、カスタマイズされた関数を返してそれで子要素を選択する意図です.
ところがこのスタイルシートはSaxonでエラーが出てしまいました.
System ID: D:\My Documents\Proj\XSLT-TEST\20130119-func\test.xsl
Main validation file: D:\My Documents\Proj\XSLT-TEST\20130119-func\test.xsl
Engine name: Saxon-EE 9.4.0.4
Severity: error
Description: 0
Main validation file: D:\My Documents\Proj\XSLT-TEST\20130119-func\test.xsl
Engine name: Saxon-EE 9.4.0.4
Severity: error
Description: 0
これはどうもSaxon 9.4のバグのようで、
<xsl:sequence select="function($a as element()) as element()* {$a/child::*[starts-with(name(.),$prmElemName)]}"/>
と直すと一発で動いてくれました.違いはname()⇒name(.)です.あと以下のようにやっても回避できました.
<xsl:function name="ahf:funcGetChildElementByName" as="function(element()) as element()*">
<xsl:param name="prmElemName" as="xs:string"/>
<xsl:sequence select="let $en := $prmElemName,
$f := function($a as element(),$elemName as xs:string) as element()* {$a/child::*[starts-with(name(),$elemName)]}
return $f(?,$en)"/>
</xsl:function>
<xsl:param name="prmElemName" as="xs:string"/>
<xsl:sequence select="let $en := $prmElemName,
$f := function($a as element(),$elemName as xs:string) as element()* {$a/child::*[starts-with(name(),$elemName)]}
return $f(?,$en)"/>
</xsl:function>
ここでのポイントは、関数式の中から関数のパラメータで定義されていない変数(この場合は$prmElemName)をちゃんと参照できるか?ということでした.xsl:functionの中では問題なくxsl:paramを参照できるようです.これならパラメータに応じてフレキシビリティに富んだ関数を記述して呼び出し側に返すことができます.