Passing Parameters Into an XSLT Stylesheet Without Surprises
Pass values into XSLT from xsltproc and lxml, and see why a param that looks like a string is read as an XPath expression and silently ignored.

A stylesheet that is useful more than once takes inputs: a currency to filter by, a minimum price, a language, a date. XSLT calls them parameters. They are simple until the first time one is ignored without an error, and the cause is always the same: the difference between a string and an XPath expression. This article shows both ways to pass a value, the silent failure and the lxml equivalent.
Declaring parameters
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="min" select="30"/>
<xsl:param name="cur" select="'USD'"/>
<xsl:template match="/library">
<xsl:text>min=</xsl:text><xsl:value-of select="$min"/><xsl:text> type-test: </xsl:text>
<xsl:value-of select="count(book[price/@currency=$cur and number(price) >= $min])"/><xsl:text> </xsl:text>
<xsl:call-template name="show"><xsl:with-param name="label" select="'books'"/></xsl:call-template>
</xsl:template>
<xsl:template name="show">
<xsl:param name="label" select="'default'"/>
<xsl:value-of select="$label"/>=<xsl:value-of select="count(book)"/><xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>xsl:param at the top level declares a stylesheet parameter. Its select is an XPath expression giving the default, so the string default has its own quotes: select="'USD'" is the string USD, and select="USD" would be a path to a child element called USD. This is the root of the problem. A select attribute always holds an expression.
Run it without arguments:
min=30 type-test: 1
books=3With the defaults (USD, minimum 30), one book matches: the 44.00 book (the 29.50 book is below 30). The second line comes from the named template, and it received label from with-param at the call.
stringparam and param
On the command line there are two options, and they differ in what the value is:
xsltproc --param min 50 params.xsl library.xml
xsltproc --stringparam min 50 params.xsl library.xml
xsltproc --stringparam cur VND --param min 100000 params.xsl library.xml
xsltproc --param cur VND params.xsl library.xmlmin=50 type-test: 0
min=50 type-test: 0
min=100000 type-test: 1
min=30 type-test: 0Line by line. --param min 50: the value 50 is an XPath expression, the number 50; no USD book costs 50 or more, so 0. --stringparam min 50: the value is the string "50", and number(price) >= $min compares a number with a string by converting the string, so the same 0. Both are right here, and they differ in other expressions: $min + 1 is 51 for the number and also 51 for the string (converted), but $min = '50' is true only for the string, and string-length($min) is 2 for both.
The third line mixes the two options: the string VND for the currency and the number 100000 for the minimum matches the one VND book, which costs 250000. Mixing is normal.
The fourth line is the trap. --param cur VND passes VND as an expression, and an unquoted name in XPath is a path selecting child elements named VND from the context, which is the root, and there are none. The parameter becomes an empty node-set. Nothing failed: the transform ran, $cur was empty, the comparison price/@currency = $cur was false for every book, and the output said min=30 type-test: 0. The line also shows min=30, because that parameter was not passed, so the default applied. There is no warning. The symptom is a result of zero matches, which looks exactly like "no data", and it is how a parameter bug hides.
The rule: **use --stringparam for text (currencies, names, paths, dates), and --param for numbers, booleans and node-sets**. If you must pass a string through --param, quote it inside: --param cur "'VND'".
lxml has the same trap
lxml passes keyword arguments as XPath expressions:
xslt = etree.XSLT(etree.parse("params.xsl"))
str(xslt(doc, min="50", cur=etree.XSLT.strparam("VND")))
str(xslt(doc, min="100000", cur="'VND'"))
str(xslt(doc, cur="VND"))'min=50 type-test: 1\nbooks=3\n'
'min=100000 type-test: 1\nbooks=3\n'
'min=30 type-test: 0\nbooks=3\n'The first call uses etree.XSLT.strparam("VND") for the currency, which wraps a Python string as an XPath string literal, and min="50", an expression for the number 50, and matched the VND book (one match, since VND 250000 is at least 50). The second quotes the string by hand, "'VND'", with the same effect. The third passes cur="VND" bare: an unquoted name again, an empty node-set, and zero matches. The Python function signature makes it feel like you passed a string. You did not. strparam exists for this reason, and it is also the right way to pass anything you did not write yourself, since a string containing a quote would otherwise break the expression (or, worse, inject XPath: a value like ' or true() or ' in an unquoted expression is an injection route). Pass user input only through strparam.
Template parameters are separate
The xsl:param inside the named template show is a different thing from the top-level parameters. It has a default and takes a value from xsl:with-param at the call site. A top-level parameter is visible in templates as $min, since it is global, but a template's own xsl:param takes only what the caller passes or its default. In the output, books=3 shows the passed label, and without the with-param it would have been default=3. The same applies to apply-templates with with-param, and the receiving template must declare the parameter or the value is dropped without a message.
Defaults and required parameters
XSLT 1.0 has no way to mark a parameter required. A parameter without a select and without content defaults to the empty string. If the stylesheet cannot work without a value, check it at the start and stop:
<xsl:if test="not($cur)">
<xsl:message terminate="yes">parameter cur is required</xsl:message>
</xsl:if>xsl:message with terminate="yes" stops the transform with an error and a non-zero exit code, which turns the silent failure into a loud one. XSLT 2.0 adds required="yes" on the parameter, with an error from the processor. For parameters with a small set of valid values, test for membership the same way.
Related reading
For running stylesheets see running XSLT from the command line and running XSLT in Python with lxml. The first stylesheet with a parameter is in using XSLT to transform XML into another shape. Parameters are specified in section 11 of XSLT 1.0, and strparam in the lxml XSLT documentation. Test an XPath in the XPath tester.
When a parameter is the wrong way to configure a transform
The setting changes the structure of the output. Switching between two very different layouts is two stylesheets, with a shared one imported for the common part.
The value is a long list. A hundred codes passed as a string and split in XSLT 1.0 is painful. Put them in a small XML file and read it with document().
The value is a secret. Command-line parameters show up in process lists. Pass secrets through a file with restricted permissions.
You pass parameters to untrusted stylesheets. A stylesheet can read files and sometimes the network; control what stylesheets run before worrying about what they receive.


