Variables and Paths
June 26, 2002
Q: Can I change the value of an XSLT variable?
Is there any way to change the value of an
xsl:variable element inside an
A: Of all the hurdles facing people who're learning XSLT, in my opinion the biggest one is the word "variable" when used in this context. That's because XSLT variables do not vary. Rather, the word signifies that a value is not necessarily known until runtime: it may vary every time a stylesheet is consumed by an XSLT processor in order to perform a transformation. But, once set in a given transformation, the variable's value remains unchanged.
First, some background for those of you who haven't used XSLT variables previously.
A variable is declared in a stylesheet using an
xsl:variable element. Here's
<xsl:variable name="empname" select="/employee/name">
In this case, a variable with the given
name is assigned a value equal to the
string-value of the XPath expression given by the
select attribute. The
variable's value can then be retrieved by referencing the variable name with a dollar
prepended. For instance:
empname variable has a value of "Munchausen," this
xsl:value-of element causes the string "Munchausen" to be instantiated in the
result tree at that point.
The chief gotcha when dealing with XSLT variables is that a particular variable
only has meaning within the context of the
xsl:variable element's parent
element. This is referred to as the variable's scope. You can declare a global variable
one whose value is accessible ("in scope") anywhere in the stylesheet -- by making
xsl:variable a child of the stylesheet's root
element. For instance,
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:variable name="booktitle" select="/book/@title"/> ... </xsl:stylesheet>
(This is a convenient shorthand which avoids repeating complex XPath expressions throughout the stylesheet. It can also result in more maintainable and easily understood stylesheets.)
xsl:variable can also be a child of nearly any other element in the XSLT
namespace. For example,
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="book"> <xsl:variable name="booktitle" select="@title"/> <xsl:if> <xsl:when test="substring($booktitle,1,1) <= 'M'"> [template to apply for book titles from first half of alphabet] </xsl:when> <xsl:otherwise> [template to apply for book titles from second half of alphabet] </xsl:otherwise> </xsl:if> </xsl:template> ... </xsl:stylesheet>
xsl:template element above, the value of the
variable will vary depending on the string-value of a given
title attribute. So, yes, it varies, but it remains constant for any given
It seems what the questioner wants to do, in the absence of a particular code example, would look something like
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="book"> <xsl:if> <xsl:when test="substring(@title,1,1) <='M'"> <xsl:variable name="somevar" select="[XPath expression]"/> [template to apply for book titles from first half of alphabet] </xsl:when> <xsl:otherwise> <xsl:variable name="somevar" select="[XPath expression]"/> [template to apply for book titles from second half of alphabet] </xsl:otherwise> </xsl:if> </xsl:template> ... </xsl:stylesheet>
There are a couple of potential problems here. The first
element's parent is now the
xsl:when element, and, therefore, the variable is
not available within, for example, the
xsl:otherwise element. Furthermore, no
matter where you place your
xsl:variable, its value is never really
changeable. By declaring a variable called
somevar twice, above, all
you've done is create two variables which just happen to have the same name but entirely
different scopes: one within the
xsl:when element and one within the
I think the best way to understand XSLT variable behavior -- and maybe to solve your problem (although the solution may not be what you expect) -- is by consulting Dave Pawson's indispensable XSLT FAQ, in particular the section specifically about variables.
Q: How do I extract all possible document paths using just a DTD?
I'm trying to extract all the paths from a given DTD: for every DTD there exists a node tree; for each node there exists a path from the root of the tree to that node. Is there some simple way of doing this? I need to extract all these paths and store them in a relational database management system.
A: My first guess is no, there's no simple way to accomplish this task. A DTD provides a general road map to a valid document's structure for a given XML application; it does not define the specific structures of all possible valid documents for a given XML application.
Assume you've got a simple DTD which looks like the following:
<!ELEMENT root_elem (child_elem+)>
<!ELEMENT child_elem (grandchild_elem*)>
<!ELEMENT grandchild_elem (#PCDATA)>
All you can tell from this DTD is that a valid document in this application has a
root_elem, with one or more
elements, each of which may have 0, 1, or more
grandchild_elem children. The
problem with your question, assuming I'm reading it right, is in the statement, "...for
every DTD there exists a node tree." Actually, no, for every DTD there exists at least
one node tree, but once the DTD introduces occurrence operators like
?, the number of possible node trees increases sharply,
all the way (if the
* operators are used) to infinity.
Your mention of an RDBMS, however, makes me wonder if what you're really after is how to model a database structure based on the general structure described by a DTD -- that is, how to map one to the other. For such questions, it's hard to avoid running into the name Ron Bourret (developer of the XML and Databases informational site). His feature on XML.com (from May, 2001) is a great place to start.
Q: Where can I get XML-based currency quotes?
On my Web site, I publish prices in CHF (Swiss Francs). I also want to publish the prices in Euros. So every time the page is requested the actual quote must be retrieved, but where can I get that quote?
A: There are multiple sources for this kind of information, but a simple one with which I'm familiar is called CEWS (the Currency Exchange Web Service), from duzine.com. (Registered developers can obtain a single-CPU license to use CEWS for development work. This license provides current quotes for 21 international currencies, including Swiss Francs and Euros as well as dollars, pounds, Japanese yen, Dutch guilders, and so on.)
An example posted on the CEWS page illustrates the simple structure of a CEWS message:
<?xml version="1.0" ?> <CEWS> <DATE>21 August 2001</DATE> <BASE>EUR</BASE> <CURRENCY_LIST> <ATS>13.7603</ATS> <AUD>1.7110</AUD> <CAD>1.4107</CAD> <CHF>1.5166</CHF> ... </CURRENCY_LIST> </CEWS>
DATE element indicates the date for which this quote was correct. The
BASE element designates the unit of currency to which the quote applies. The
child elements of the
CURRENCY_LIST element correspond to currency name
abbreviations. Shown above are the Austrian Schilling, Australian Dollar, Canadian
and -- yes -- Swiss Franc, which on the indicated date were worth 13.7603, 1.7110,
and 1.5166 Euros, respectively.
As the CEWS page points out, this message structure is extremely compact and simple to process. It doesn't include a SOAP wrapper, for example, and needn't be validated against a DTD or XML Schema.
Similar solutions are provided by Evergreen.com, Oanda, Cloanto, and numerous other vendors. As you might expect, few of these services are offered for free if you want to do anything with the data other than develop software to use it. You might also take a look at /n Software's IPWorks Internet Toolkit, which is available across a wide variety of platforms and includes a currency-conversion demo. Finally, for SOAP-based currency conversion, see the XMethods "Euro Conversor" site.
John E. Simpson is the author of Just XML (Prentice Hall PTR; 2nd edition forthcoming) and a regular contributor to XML mailing lists, particularly XML-L. He has been an application developer for more than 20 years, and is currently the Webmaster for several non-profit organizations based in Florida. He once closed a bar with Joseph Heller, Kurt Vonnegut Jr., and William Styron.