Chapter 11. Calling steps as functions
In XML Calabash, any pipeline that you declare with an explicit type also
creates a function with that name. The function is available to XPath
expressions with the scope of that step type. (For a little background about
this feature, see the weblog post
Pipelineception
on https://so.nwalsh.com/
.)
An example
To see how this works, let’s consider a plausibly useful step, ex:hash
:
1 |<p:declare-step type="ex:hash">
| <p:output port="result" content-types="text"/>
| <p:option name="text" as="xs:string" required="true"/>
| <p:option name="algorithm" as="xs:string" select="'sha'"/>
5 | <p:option name="version" as="xs:string?"
| select="if ($algorithm = 'sha') then '256' else ()"/>
|
| <p:hash algorithm="{$algorithm}" value="{$text}" match="/doc">
| <p:with-option name="version" select="$version"/>
10 | <p:with-input>
| <doc/>
| </p:with-input>
| </p:hash>
A typical use for this step in a pipeline might look something like this:
|<ex:hash text="Hello, world."/>
|<p:wrap-sequence wrapper="hash"/>
That pipeline fragment takes the SHA256 hash of “Hello, world.” and
wraps an element named hash
around it, producing:
|<hash>f8c3bf62a9aa3e6fc1619c250e48abe7519373d3edf41be62eb5dc45199af2ef</hash>
Alternatively, we can take advantage of the fact that a function has been created and call that instead (we’ll come back to the function arguments in a bit):
1 |<p:identity>
| <p:with-input>
| <hash>{ex:hash(map{'text': 'Hello, world.'})?result}</hash>
| </p:with-input>
5 |</p:identity>
On the surface, that’s not a huge improvement. The real magic is that the function is declared in the XPath context of the step, and by extension, in the XPath context of all the steps that occur within it.
Consider this pipeline:
1 |<p:declare-step xmlns:p="http://www.w3.org/ns/xproc"
| xmlns:xs="http://www.w3.org/2001/XMLSchema"
| xmlns:ex="http://example.com/ns"
| exclude-inline-prefixes="#all"
5 | name="main" version="3.0">
| <p:output port="result" sequence="true"/>
|
| <p:declare-step type="ex:hash">…same as above…</p:declare-step>
|
10 | <p:xslt>
| <p:with-input port="source">
| <doc>
| <hash>Hello, world.</hash>
| </doc>
15 | </p:with-input>
| <p:with-input port="stylesheet" href="pception.xsl"/>
| </p:xslt>
|
|</p:declare-step>
Where the stylesheet, pception.xsl
, is:
1 |<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
| xmlns:ex="http://example.com/ns"
| xmlns:xs="http://www.w3.org/2001/XMLSchema"
| exclude-result-prefixes="ex xs"
5 | expand-text="yes"
| version="3.0">
|
|<xsl:output method="xml" encoding="utf-8" indent="no"/>
|
10 |<xsl:mode on-no-match="shallow-copy"/>
|
|<xsl:template match="hash">
| <p>The hash of “{string(.)}” is:</p>
| <xsl:copy>
15 | <xsl:apply-templates select="@*"/>
| <xsl:sequence select="ex:hash(map{'text': string(.)})?result"/>
| </xsl:copy>
|</xsl:template>
|
20 |</xsl:stylesheet>
That pipeline will run in XML Calabash, producing the output:
|<doc>
| <p>The hash of “Hello, world.” is:</p>
| <hash>f8c3bf62a9aa3e6fc1619c250e48abe7519373d3edf41be62eb5dc45199af2ef</hash>
|</doc>
That’s because the p:xslt
step inherits the XPath context that
includes the function definition for ex:hash()
and
consequently so does the stylesheet.
Calling a step function
The function signature for a “step function” is one argument for each of
the step inputs, in declaration order. If the step has options, a final
map(xs:QName,item()*)
argument is allowed. The key names must be
the names of options.
The function always returns a map(xs:NCName, item()*)
map
where the key names are the names of output ports from the step.
The function signature for our ex:hash
function from the
example above is:
function ex:hash($options as map(xs:QName, item()*)?) as map(xs:NCName, item()*)
More generally for a step declared thus:
1 |<p:declare-step type="ex:name">
| <p:input name="input1"/>
| <p:input name="input2"/>
| …
5 | <p:input name="inputN"/>
| <p:option name="option1"/>
| …
|</p:declare-step>
The function signature would be:
function ex:name($input1 as item()*,
$input2 as item()*,
…,
$inputN as item()*,
options as map(xs:QName, item()*)?)
as map(xs:NCName, item()*)
Using step functions outside XProc
It’s possible to take this one step further. It’s possible to make this work even if you aren’t running a pipeline. It just requires a little more work to set up.
You have to hook into Saxon’s startup. The
-init:
option let’s you do that.You have to be able to point to a library of pipelines. We can use a Java property for that.
So, in fact, this also “just works” (assuming doc.xml
contains
the input and the ex:hash
step is defined
in library.xpl
):
XLIB=/path/to/library.xpl
CP=...all the jars...
java -Dcom.xmlcalabash.pipelines="$XLIB" -cp "$CP" \
net.sf.saxon.Transform \
-init:com.xmlcalabash.api.RegisterSaxonFunctions \
-s:doc.xml -xsl:pception.xsl
To be fair “…all the jars…” is doing some work in there, but XML Calabash ships with shell and Powershell scripts that point the way.