XSLT 3, fn:transform() and XProc

Which, when, why.

Liam Quin
Delightful Computing


Re-evaluate: what can XSLT do now that’s in the pipeline space? How does it compare in difficulty?

Down arrow

Oil the Machine


  • fn:transform() inspired this talk.
  • You can invoke another stylesheet from within XSLT.
  • The result is returned in a map.
  • fn:transform() changes the scope of XSLT.
  • Now you can use XSLT to coordinate groups of external transformations.


  • An XSLT-only consulting client needed EPUB3 generation;
  • Fewer tools, processes, languages, technologies.

Down arrow

Become the Machine

Gears © Animale38 by permission


  • Run XSLT (obvious, but…)
  • Validation…
  • Queries…
  • External processes…
  • Zip, EPUB, EXPath
  • Reading/writing
  • Inclusions, Exclusions, Conclusions


There’s no version of fn:document() that has options like whether to do DTD validation, Schema validation, Schematron, etc., but some XSLT engines may have extensions or options for this.


There’s no fn:query() in XSLT as standard, although there’s saxon:query(). p:query is also an optional step in XProc.

External Commands

Again optional in XProc.

Down arrow

XSLT that runs Perl

  <xsl:message xmlns:system="java:java.lang.Runtime?void=this">
      <xsl:value-of select=" (
        system:exec(system:getRuntime(), '/bin/perl -f /tmp/insecure.pl', ()),
      )[2] " />
    <xsl:value-of select="doc('/tmp/out.xml')//text()" />

Zip, EPUB, EXPath

Read/write binary content and process Zip-format archives with the EXPath file:read(), file:write() and archive:*() functions.

Updating a ZIP archive

<xsl:variable name="archive" as="xs:base64Binary"
    $alternatingvalues[(position() mod 2) eq 1],
    $alternatingvalues[(position() mod 2) eq 0]
) " />
<xsl:value-of select="( file:create-dir($dirname),
                           file:write-binary($outputfilename, $archive) )[1] "/>

Writing Restriction

  • XSLT 2 introduced xsl:result-document
  • Can’t read from a URL you’ve written to.
  • fn:transform() returns a map containing not-actually-written documents, and also can call a function for each resource.

Mitigation of Restriction

  • The restriction doesn’t aply to file:read(), file:write(), file:write(fn:serialize($node)) etc.
  • You can “catch” the documents written by a called stylesheet and write them out with file:write().


  • Replace each XProc step with an XSLT function;
  • Sequence replaced by nested function calls:
step1 → step2 → step3

can be rewritten as:

 step3( step2( step1() ) )


  • Conditional operations and variables;
  • Catching written resources;
  • Multiple copies and flows.

Down arrow

The Wider Context

Is it a good idea?

  • Performance
  • Memory and Streaming
  • Contorted plumbing
  • Awesomeness of Saxon
  • Relation to EXPath Tasks