XSLT
offers several
ways to bind a name to a value so that the value can be later
referenced by name any number of times in a stylesheet. The
variable
element binds a name to an immutable
value once it’s been evaluated, while the
param
element binds a name to a default value, but
it’s a value you can change. You can define a
default value with param
and then pass a new value
into the stylesheet or to a template. The
with-param
element allows you to apply or call a
template from another template with a new value for one or more
parameters, like a method or function call with arguments.
Variables in XSLT are limited in what they can do. They are not like variables in programming languages that you can reassign over and over again. Generally, you will define a variable once and then reference it as often as you want. You will also generally change the default value of a parameter just when you pass a value to a stylesheet or template. There are ways around this, but, by and large, that is how you use them. In this way, XSLT variables are more similar to constants in a programming language than to variables.
You can use the variable
and
param
elements globally on the top-level as
stylesheet-wide values, or locally within templates. If a variable is
global, its scope is the entire stylesheet; if it is local, its scope
is restricted to the template where it is defined or passed in. The
with-param
element may appear only as an immediate
child of an apply-templates
template (for
processing child templates) or of a call-template
element (for processing named templates—more on this in Chapter 10). A variable can be of any type—Boolean,
node-set, number, string, or result tree fragment.
In XSLT 2.0, you will also be able to use
with-param
as a child of the
apply-imports
element. You can’t
do that in XSLT 1.0.
This chapter introduces you to using variables and parameters in
stylesheets, both globally and locally. I’ll use the
general term variable to refer to the values of
variable
, param
, and
with-param
in this chapter and throughout the
book.
You can read about variables and parameters in Section 11 of the XSLT specification.
Before going any deeper into the subject, there are a few things
I’d like to discuss that apply to the
variable
, param
, and
with-param
elements. To begin with, all three
elements have just two attributes:
The required name
attribute that binds a name to a
value of the variable. This is a QName.
The optional select
attribute that can contain an
expression that defines the value of the variable.
In addition, all three elements also provide two general ways to define a value:
You can define a value using an expression in the
select
attribute.
You can also define a value using something called a result tree fragment with a template in the content of the element. As discussed in Chapter 4, a result tree fragment is an XSLT 1.0 datatype that defines a fragment of text or markup.
After you define a variable, you can reference its value by preceding
the variable name with a dollar sign, such as in
$discount
. I’ll cover both ways
to define a value and discuss the differences.
By the way, you can’t use circular definitions when defining a variable. This means that you can’t define a variable by referencing itself.
Unlike a value defined with the variable
element,
a value defined with the param
element can have a
default value that you can change. Nevertheless, a parameter is not
required to have an explicit, default value; it can just be empty.
Said another way, if a parameter does not have an explicitly defined
value, the processor will give it a value of a zero-length string.
When you define a global parameter on the top level, you can pass in
a new value when the transformation is performed that replaces the
default value using a mechanism provided by the XSLT processor; and
when you define a local parameter in a template, you can pass in a
new value from another template by using
with-param
. You will see examples of how this
works in the later sections, Section 7.3 for global variables, and
Section 7.4 for local
variables.
As I mentioned earlier, you can define a variable value with an
expression in a select
attribute or with a
template in element content (as you will soon see section
Section 7.1.2.2). However, you cannot
define a value using both an expression and a template at the same
time. In other words, you cannot use the select
attribute together with element content to define a single variable,
as they are mutually exclusive.
For example, the following declaration defines a value using an
expression in a select
attribute:
<xsl:variable name="discount" select="0.40 + 0.30"/>
The expression adds the numbers 0.40
and
0.30
and the resulting number value of
0.70
is bound to the discount
variable. An XSLT processor automatically knows that this variable is
a number. Likewise, the following variable would be interpreted
containing the number 50
:
<xsl:variable name="discount" select="50"/>
You can also bind a string to a variable explicitly using embedded quotation marks:
<xsl:variable name="discount" select="'n/a'"/>
Notice the single
quotes inside the double quotes in
the value of select
. This binds the string value
n/a
to discount
. You could also
write the declaration in this way, with double quotes inside of
single quotes:
<xsl:variable name='discount' select='"n/a"'/>
Either single or double quotes are fine, but you
aren’t allowed to mix them (that is,
name='discount
" is illegal).
If you enclose the value of select
in just double
or single quotes, without any internal quotes, the value is
interpreted as a node-set and not as a string. Because
/
is not a legal XML name character,
n/a
would be interpreted as a location
path—the element n
with a child
a
—probably not what you are after.
Because select
contains an expression, you can use
arithmetic, functions, even references to other variables, when
defining a value for a variable with select
.
Here’s yet another example showing a slightly more
complex expression that defines a parameter:
<xsl:param name="discount" select="floor($option)+0.05"/>
You can also specify a location path in select
, as
in:
<xsl:param name="discount" select="catalog/value"/>
This variable would extract the content of the
value
element for its value. Another possibility
is to use the document( )
function in
select
like this:
<xsl:param name="discount" select="document('discount.xml')"/>
With this, the value of discount
is picked up from
the external document discount.xml:
<value>0.10</value>
Though it is discussed elsewhere in the book, you’ll
learn more about the document( )
function in Chapter 13.
When you define a variable using a template in element content, such content is a result tree fragment. Because it is defined as a template, a result tree fragment can be a node-set consisting of markup, which has its own root element. The following declaration uses a result tree fragment to define a variable:
<xsl:variable name="discount"> <xsl:element name="discount">0.10</xsl:element> </xsl:variable>
Here element
is used to create an element named
discount
with the content 0.10
for the result tree.
The result tree fragment type is defined by XSLT, not by XPath. It is called a temporary tree in XSLT 2.0, and it can be manipulated by an XSLT 2.0 processor in more sophisticated ways than a result tree fragment can be manipulated by an XSLT 1.0 processor. (An XSLT 2.0 temporary tree, however, cannot be manipulated by an XSLT 1.0 processor.) See the section Section 7.5, later in this chapter for a working example.
Variables in XSLT allow you to associate a name with a value, making it easier to use a given value more than once. I’ll start the series of examples in this chapter with a single, global variable that contains a numeric value that is available in every template in the stylesheet, if needed. The following document, price.xml , in examples/ch07, represents a single catalog entry:
<?xml version="1.0"?> <!DOCTYPE catalog SYSTEM "price.dtd"> <catalog> <item id="SC-0001"> <maker>Scratchmore</maker> <description>Wool sweater</description> <size>L</size> <price>120.00</price> <currency>USD</currency> </item> </catalog>
price.xml happens to be valid with regard to the DTD price.dtd , also in examples/ch07. The stylesheet variable.xsl derives a discounted price from price.xml and outputs new content for the catalog:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:output doctype-system="catalog.dtd"/>
<xsl:variable name="discount" select="0.10"/>
<xsl:template match="catalog">
<xsl:copy>
<xsl:apply-templates select="item"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:copy>
<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
<xsl:copy-of select="maker|description|size|price"/>
<discount><xsl:value-of select="$discount"/></discount>
<discountPrice><xsl:value-of select="price - (price * $discount)"/></discountPrice>
<xsl:copy-of select="currency"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The stylesheet outputs XML that includes a new document type
declaration referencing catalog.dtd (available
in examples/ch07). Because the
variable
element is at the top level, it declares
a global variable, discount
, that is available or
visible to all templates in the stylesheet. This variable is
referenced later in the stylesheet with the variable reference
$discount
. A variable reference is preceded by a
dollar sign ($
).
There is no internal conflict, by the way,
between the element name discount
and the variable
name discount
. There is also no name conflict
between a variable defined on the top level and one with the same
name defined in a template. However, there will be a name conflict if
two or more variables share the same name and are defined on the top
level (unless they have a different import precedence; see Chapter 13 for an explanation), or if they share the same
name and are defined locally in a template.
The stylesheet makes copies of the maker
,
description
, size
, and
price
elements
(maker|description|size|price
). In Chapter 4, the
|
operator was said
to imply or. In this instance, the
|
operator implies and. In
other words, the elements maker
and
description
and
size
and
price
are all copied. (|
can generally be read as union.)
The stylesheet creates two new elements in the result tree,
discount
and discountPrice
. The
content of these elements is formed with the aid of the
discount
variable, which contains a discount
percentage of 10 percent (0.10
). The
discount
and discountPrice
elements will contain content that is computed with the value of the
discount
variable.
To see the result, transform price.xml with variable.xsl with the following command:
xalan -v -i 1 price.xml variable.xsl
The -v
option instructs
Xalan to validate the source document
price.xml against the DTD it references in its
document type declaration (price.dtd), not the
DTD output document references (catalog.dtd).
The -i
option indicates that child elements should
be indented by one space per child. This command will give you the
following output:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE catalog SYSTEM "catalog.dtd"> <catalog> <item id="SC-0001"> <maker>Scratchmore</maker> <description>Wool sweater</description> <size>L</size> <price>120.00</price> <discount>0.1</discount> <discountPrice>108</discountPrice> <currency>USD</currency> </item> </catalog>
The added discount
element reports the discount
percentage, and the new discountPrice
element
contains the calculated discounted price. You can improve the
appearance of numbers in the discount
and
discountPrice
elements by using the
format-number( )
function in the stylesheet.
(format-number( )
was mentioned in Chapter 5 and you will learn more about it in Chapter 9.)
variable-alt.xsl
uses both the format-number(
)
and document( )
functions:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:variable name="discount" select="document('discount.xml')"/> <xsl:template match="catalog"> <xsl:copy> <xsl:apply-templates select="item"/> </xsl:copy> </xsl:template> <xsl:template match="item"> <xsl:copy> <xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute> <xsl:copy-of select="maker|description|size|price"/> <discount><xsl:value-of select="$discount"/></discount> <discountPrice><xsl:value-of select="format-number(price - (price*$discount),'###.00')"/></discountPrice> <xsl:copy-of select="currency"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
Xalan reports that it does not support the format-number(
)
function, so I’ll use the
Instant Saxon
processor for this example instead. Instant Saxon 6.5.3, the last
version of Instant Saxon, runs on the
Windows platform and is
available for download from http://saxon.sourceforge.net. (Or just use
saxon.exe in
examples/ch07). After Instant Saxon is
installed, apply variable-alt.xsl to
price.xml with this line:
saxon price.xml variable-alt.xsl
You will see a difference from the output of variable.xsl with that of the output of variable-alt.xsl:
<?xml version="1.0" encoding="utf-8"?> <catalog> <item id="SC-0001"> <maker>Scratchmore</maker> <description>Wool sweater</description> <size>L</size> <price>120.00</price> <discount>0.10</discount> <discountPrice>108.00</discountPrice> <currency>USD</currency> </item> </catalog>
The content of discount
was taken from the content
of the value
element in discount.
xml via the document( )
function.
Because of this, the content in the output is 0.10
rather than 0.1
(it was 0.1
with variable.xsl). Because of
format-number( )
, the content of
discountPrice
is 108.00
rather
than just 108
. The second argument of
format-number( )
(###.00
)
specifies that the number is to be formatted in the output with two
places after the decimal point.
As you know, you can’t change a value defined with
variable
, so what do you do if you want to change
the value of the discount? You can use the param
element.
The following stylesheet,
param.xsl
,
is only a little different than
variable.xsl—the top-level element
variable
is switched with a
param
element—but what a difference that
small change makes:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:output doctype-system="catalog.dtd"/>
<xsl:param name="discount" select="0.10"/>
<xsl:template match="catalog">
<xsl:copy>
<xsl:apply-templates select="item"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:copy>
<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
<xsl:copy-of select="maker|description|size|price"/>
<discount><xsl:value-of select="$discount"/></discount>
<discountPrice><xsl:value-of select="price - (price * discount)"/></discountPrice>
<xsl:copy-of select="currency"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This stylesheet defines a global parameter (it’s
global because it is defined on the top level of the stylesheet).
This means that the parameter discount
is
available throughout the stylesheet, wherever it might be needed.
Given this stylesheet, the default value of
discount
is 0.10
, and you can
change the value of discount
using a mechanism
provided by the XSLT processor on the command line.
This book focuses mostly on XSLT processors that have command-line
interfaces. For XSLT processors written in Java and based on
Sun’s Java API for XML Processing (JAXP), for
example, APIs provide programmatic methods for setting parameter
values, namely javax.xml.transform.setParameter(String name,
Object value)
. You will get a chance to use JAXP in a
programming example in Chapter 17.
The
Xalan processor has a
-p
command-line option. This option allows you to
associate a parameter name with a new value, which is in turn handed
to the processor to produce a different result tree. To increase the
amount of the discount from 10 percent (0.10
) to
20 percent (0.20
), enter the following command
line:
xalan -i 1 -p discount '0.20' price.xml param.xsl
This transformation will produce the following result:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE catalog SYSTEM "catalog.dtd"> <catalog> <item id="SC-0001"> <maker>Scratchmore</maker> <description>Wool sweater</description> <size>L</size> <price>120.00</price> <discount>0.20</discount> <discountPrice>96</discountPrice> <currency>USD</currency> </item> </catalog>
Notice the new content of the discount
and
discountPrice
elements. This was a result of
passing in a parameter value. You can use the -p
option for as many parameters as you wish to change, provided that
those parameters are defined in the stylesheet you are processing.
Other processors, such as Instant Saxon, use a different syntax to
pass in parameter values.
The
Instant Saxon processor uses a
simpler command-line syntax than Xalan’s syntax. To
associate a parameter name with a new value, increasing the discount
from 10 percent (0.10
) to 30 percent
(0.30
), enter this line:
saxon price.xml param.xsl discount="0.3"
The parameter discount
is coupled with a new value
just by using the equals sign (=
). Here is the
output:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE catalog SYSTEM "catalog.dtd"> <catalog> <item id="SC-0001"> <maker>Scratchmore</maker> <description>Wool sweater</description> <size>L</size> <price>120.00</price> <discount>0.3</discount> <discountPrice>84</discountPrice> <currency>USD</currency> </item> </catalog>
Other XSLT processors, such as James Clark’s XT and Microsoft’s MSXSL, also use this simple syntax for associating a parameter with a new value.
So far, you have seen a global variable and parameter. Next you’ll see how to define and use a local one.
The with-param.xsl stylesheet shown in Example 7-1 doesn’t define a global parameter on the top level, but it does define a local variable within a template.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:output doctype-system="catalog.dtd"/>
<xsl:template match="catalog">
<xsl:copy>
<xsl:apply-templates select="item">
<xsl:with-param name="discount" select="'0.50'"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:param name="discount"/>
<xsl:copy>
<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
<xsl:copy-of select="maker|description|size|price"/>
<discount><xsl:value-of select="$discount"/></discount>
<discountPrice><xsl:value-of select="price - (price * $discount)"/></discountPrice>
<xsl:copy-of select="currency"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The first child element of the template that matches
item
is the param
element,
which just happens to be empty by default. When you use local
parameters defined with param
, the
param
element must appear as the first child
element in a template. This was probably so that their values are
taken into account before the template is
processed. You can also define local variables in a template with the
variable
element, but these can appear anywhere in
a template. They don’t have to be first in line.
The template that matches catalog
applies
templates to item
elements, but as it does so, it
adds a child to apply-templates
called
with-param
. This element passes a new value for
the local discount
parameter to the template that
matches item
. To see how it works, enter the
following line at a prompt:
xalan -i 1 price.xml with-param.xsl
The processor yields the following deeply discounted results:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE catalog SYSTEM "catalog.dtd"> <catalog> <item id="SC-0001"> <maker>Scratchmore</maker> <description>Wool sweater</description> <size>L</size> <price>120.00</price> <discount>0.50</discount> <discountPrice>60</discountPrice> <currency>USD</currency> </item> </catalog>
You can use the with-param
element in two places,
either as a child of apply-templates
or as a child
of call-template
. You will see how you can use
with-param
with call-template
in Chapter 10.
In XSLT 1.0, parameters are not passed through by built-in templates. XSLT 2.0, however, does pass through parameters via built-in templates.
In the final examples in this chapter, I’ll show you how to create a variable value with a result tree fragment, and then how to use that fragment later in the stylesheet.
As you
saw earlier, a variable value, created with a template as element
content, creates a result tree fragment. The stylesheet in Example 7-2,
fragment.xsl
,
constructs a variable value for discount
using a
template with a variable
element, and then later
accesses that value with copy-of
.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:output doctype-system="catalog.dtd"/> <xsl:variable name="discount"> <discount>0.40</discount> <discountPrice><xsl:value-of select="format-number(catalog/item/price * 0.60, '###.00')"/></discountPrice> </xsl:variable> <xsl:template match="catalog"> <xsl:copy> <xsl:apply-templates select="item"/> </xsl:copy> </xsl:template> <xsl:template match="item"> <xsl:copy> <xsl:copy-of select="@id"/> <xsl:copy-of select="maker|description|size|price"/> <xsl:copy-of select="$discount"/> <xsl:copy-of select="currency"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
Notice the value-of
contained in
discountPrice
in variable
. The
first argument of format-number( )
uses a location
path to address the content of the price
element
in the source tree. The context node for evaluating a global variable
is the root node of the original source document;
that’s why the location path uses
catalog/item/price
instead of
/catalog/item/price
, an absolute location path.
Later, in the template for item
,
copy-of
elements copy the id
attribute from the source item
and the nodes
contained in discount
’s result
tree fragment.
To test it out, enter this command line:
saxon price.xml fragment.xsl
You will get this outcome:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE catalog SYSTEM "catalog.dtd"> <catalog> <item id="SC-0001"> <maker>Scratchmore</maker> <description>Wool sweater</description> <size>L</size> <price>120.00</price> <discount>0.40</discount> <discountPrice>72.00</discountPrice> <currency>USD</currency> </item> </catalog>
A result tree fragment does not have to be well-formed XML. For
example, you could also just use a bit of text in a result tree
fragment, as shown in the variable
definition for
discount
in Example 7-3,
frag.xsl.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:output doctype-system="catalog.dtd"/>
<xsl:variable name="discount">0.70</xsl:variable>
<xsl:variable name="discountPrice" select="format-number(catalog/item/price -
(catalog/item/price) * $discount, '###.00')"/>
<xsl:template match="catalog">
<xsl:copy>
<xsl:apply-templates select="item"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:copy>
<xsl:copy-of select="@id"/>
<xsl:copy-of select="maker|description|size|price"/>
<discount><xsl:value-of select="$discount"/></discount>
<discountPrice><xsl:value-of select="$discountPrice"/></discountPrice>
<xsl:copy-of select="currency"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The variable discount
contains only the text
0.70
. (It’s not best to define a
simple value like 0.70
as a tree fragment, as it
is somewhat more costly processing-wise to do so.) When the
discountPrice
variable is defined, it contains a
reference to discount
as part of its definition.
The two variables are referenced later in the stylesheet by instances
of value-of
.
You can reference a global variable before you define it, but you must define a local variable before you reference it.
Watch what happens with this command:
xalan -i 1 price.xml frag.xsl
Here is the result:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE catalog SYSTEM "catalog.dtd"> <catalog> <item id="SC-0001"> <maker>Scratchmore</maker> <description>Wool sweater</description> <size>L</size> <price>120.00</price> <discount>0.70</discount> <discountPrice>36.00</discountPrice> <currency>USD</currency> </item> </catalog>
Before concluding, I should mention a few more things about result
tree fragments. Only XSLT 1.0 allows you to copy result tree
fragments or use them as strings. However, the node-set(
)
extension function offered by many XSLT 1.0 processors
allows you to use a result tree fragment as a tree of temporary
nodes. node-set( )
is not actually part of XSLT
1.0, but it is a useful extension added by many XSLT processors. You
will learn how to use two versions of node-set( )
in Chapter 15. XSLT 2.0 uses temporary trees rather
than result tree fragments, which are stricter. Unlike its 1.0
predecessor, XSLT 2.0 is a tree of XML nodes and cannot contain mere
fragments of text.
In this chapter, you have learned how to define variables and
parameters using variable
and
param
. You have also learned how to pass
parameters into a stylesheet using XSLT processor mechanisms and to
pass them within a stylesheet using with-param
.
Result tree fragments now should be a little less mysterious. You
will have other encounters with variables and parameters in the
chapters that follow.
In the next chapter, you’ll learn how to sort nodes
with the XSLT sort
instruction.
13.58.220.83