Skip to content

Packages

xsl:include and xsl:import compose stylesheets by textual merging — every template in an imported file becomes part of yours, with all the name-clash and precedence juggling that implies. XSLT 3.0 adds a real module system on top: the package, a separately-compiled unit with an explicit public surface. It is to xsl:import what a library with a header file is to #include-ing source.

Saxon edition

Package support — xsl:package and separately-compiled package libraries — is a Saxon EE feature in the Saxon 12 feature matrix. The free Saxon-HE does not implement packages, so on HE you stay with xsl:import/xsl:include. Everything below assumes a licensed edition.

A package and its consumer

A package replaces xsl:stylesheet with xsl:package, names itself, and declares the visibility of each component:

price-lib.xsl
<xsl:package name="http://example.com/price-lib"
             package-version="1.0"
             xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
             xmlns:p="http://example.com/price">

  <xsl:function name="p:with-vat" as="xs:decimal" visibility="public">
    <xsl:param name="net" as="xs:decimal"/>
    <xsl:sequence select="$net * (1 + $p:rate)"/>
  </xsl:function>

  <xsl:variable name="p:rate" as="xs:decimal" select="0.24" visibility="private"/>
</xsl:package>

Another package pulls it in by name and version, not by file path, and uses only what is public:

report.xsl
<xsl:stylesheet version="3.0" >
  <xsl:use-package name="http://example.com/price-lib" package-version="1.0"/>

  <xsl:template match="cd">
    <gross><xsl:value-of select="p:with-vat(xs:decimal(price))"/></gross>
  </xsl:template>
</xsl:stylesheet>

$p:rate is private, so the consumer cannot see or collide with it — the encapsulation xsl:import could never give you.

Visibility

Every component (template, function, variable, mode, attribute set) carries a visibility, defaulting to private:

Visibility Meaning
private usable only inside this package (the default)
public exported; consumers may use it as-is
final public and cannot be overridden
abstract declared but not defined — the consumer must supply it
hidden inherited from a used package but withdrawn from re-export

xsl:expose can re-set visibility in bulk by name pattern, rather than annotating each component.

Overriding — controlled, not accidental

Where xsl:import lets any later template silently outrank an earlier one, packages make overriding explicit. A consumer wraps replacements in xsl:override, and may only override components the package declared public or abstract:

<xsl:use-package name="http://example.com/price-lib" package-version="1.0">
  <xsl:override>
    <!-- this deployment uses a reduced rate -->
    <xsl:variable name="p:rate" as="xs:decimal" select="0.10"/>
  </xsl:override>
</xsl:use-package>

abstract components turn a package into a template for variation: the library leaves a hole, every consumer fills it. It is dependency injection for stylesheets.

Why bother over xsl:import?

Three concrete wins: compile once, reuse (a package is compiled to its own SEF and linked, not re-parsed into every stylesheet); real encapsulation (private components can't clash or be depended on); and versioned dependencies (package-version + version ranges on xsl:use-package). For a one-off shared template, xsl:import is still fine — packages earn their keep on libraries used across many stylesheets.

Where to go next