xslt variables, changing
1. | Variables aren't variable? |
XSLT is using "variable" in the true sense of the word, as it was originally used in mathematics (consider the formula area = length × width, where length and width are variables); its use in procedural programming languages is a corruption. As for why the language is designed this way, I refer you to the essay in Chapter 9 of my book "XSLT Programmer's Reference". The short answer is that it's based on the theory of functional, declarative programming, and that this is a Good Thing because (a) it reduces your scope for making errors and (b) it provides greatly increased scope for XSLT processors to execute your stylesheet in clever optimized ways. The downside is that if (like me) you've been using procedural programming languages all your life, then it takes a bit of learning. The upside of that is that you will reach an "aha" point where you realize that the rest of your life has been misspent. | |
2. | Can I change the value of a variable? |
A non-reassignable variable is still a variable. It is not a constant. See the archive In a traditional mathematical formula such as area = length * breadth "length" and "breadth" are referred to as variables, because they can take different values each time you apply the formula. The use of the word "variable" to refer to a storage location whose contents could be modified during the execution of an algorithm came later. XSLT is true to the original mathematical meaning of the term. | |
3. | No, you can't modify a variables value |
Colin Adams This is the normal situation for functional (as opposed to imperative) languages. Benefits: 1) Transparency - it is far easier to reason about a program that is free from side effects. So you can prove your program is correct with less effort. 2) Scalability - pure (side-effect free) programs are much easier to parallelize 3) Learning - variables in functional programs are the same as variables in mathematics - you don't have to learn a new concept (assingment) 4) Debugging - no need to monitor when a variable changes value. 5) Power - you can bind a variable to a function definition, then it can be passed as the argument of a function (see FXSL) Downsides: 1) Performance - functional languages are stil slower than imperative languages, although the gap is closing. 2) Large scale program structure - OO programming seems to have the advantage here. In answer to the question For the declarative case, where is the Dijkstra paper, and is there a spaghetti code analogy? Those made a lot of sense to me. (Dijkstra wrote a seminal paper in 1968 "Go To Statement Considered Harmful" which also proved that it was not necessary.) Paul quotes Then references chalmers.se as supportive evidence. David C adds Variables are just as modifiable in declarative languages as they are in imperative languages. The difference is not whether or not they may be modified, but what they hold. In declarative languages a variable is bound to a _value_ such as "2", you can use it where can use the value and if you change it it's essentially a different variable, although it may have the same name. In imperative languages variables are not bound to values, but rather to (conceptual) machine addresses, so x refers to a particular storage location, and of course you can change the value at that storage location without changing the binding of x (which process may loosely be called modifying the variable, although, as you see the variable hasn't changed at all, it's still bound to the same storage location. | |
4. | Variable has only one node? |
If you use xsl:variable with content rather than a select attribute it _always_ generates a result tree fragment corresponding to a root node (/) with children containing whatever is generated, so if you use xx:node-set() on that you always get back a node set consisting of exactly one node, /. You presumably want to access xx:node-set($sorted_legislators)/* Ednote: E.g. <xsl:variable name="sorted_legislators"> <xsl:value-of select="...." </xsl:variable> vs <xsl:variable name="sorted_legislators"> <xsl:value-of .... </xsl:variable> | |
5. | Assign a variable based on a condition |
You merely need to restate the problem. In a procedural language you think of the problem as a choice of variable assignments based on conditions: if (a): But in a declarative, functional language like XSLT, you must think of the problem as a single variable assignment where the value is chosen based on conditions: x = { if (a): z elif (b): zz else: zzz } The actual code in XSLT would be as follows: <xsl:variable name="x"> <xsl:choose> <xsl:when test="a">z</xsl:when> <xsl:when test="b">zz</xsl:when> <xsl:otherwise>zzz</xsl:otherwise> </xsl:choose> </xsl:variable> One could argue that this is simpler and more efficient for an XSLT engine to process, and is therefore more elegant, although it may not have been the natural solution for you. The only issue you need to be aware of with this example is that the data type of $x is now a result tree fragment; if you needed it to be a node-set, you would have to assign a separate variable that does the conversion. Other data types usually don't need such conversion. | |
6. | Understanding variables |
<xsl:variable name="sectnum"> <xsl:value-of select="child::title/attribute::id"/> </xsl:variable> Let's go through what this variable declaration is doing so that you can understand why it isn't working. First, look at the value that you're trying to give to the variable. The variable is being defined at the top level, which means that the current node is the 'root node' of the input (the node representing the input document). The current node is used when interpreting XPaths - your XPath is thus retrieving the 'id' attributes of the 'title' children of the *root node*. From the sounds of it, your document element isn't a 'title' element, so this XPath won't find any 'id' attributes. What you were aiming to do, I think, was find any 'id' attribute of a 'title' element that is a child of a any element within your input document. To do that, you need an XPath that looks something like: //child::title/attribute::id or, more simply: //title/@id However, when you give a variable a value by putting it as the content of the xsl:variable element, the value is interpreted as a 'result tree fragment', which essentially means that you can't process that information any more (unless you use an extension function). With the variable definition: <xsl:variable name="sectnum"> <xsl:value-of select="//title/@id"/> </xsl:variable> you will just get a concatenated string of ids, which is probably next to useless. If you want to do any kind of processing on a variable, then you should specify its value using the 'select' attribute instead. For example: <xsl:variable name="sectnum" select="//title/@id" /> will set the variable $sectnum to be a node list of all the 'id' attribute nodes of 'title' elements within your document. You could then iterate over them to give your table of contents. Having said all that, I'm not sure this is going to be a good approach given your description of what you're after. What is wrong with something along the lines of: <xsl:template match="/"> ... <!-- this does the table of contents --> <xsl:apply-templates mode="TOC" select="//*[title]"> <!-- this does the body of the book --> <xsl:apply-templates /> ... </xsl:template> <xsl:template mode="TOC" match="*[title]"> <!-- link to the anchor with the id of the 'id' attribute of the 'title' child element --> <a href="#{title/@id}"> <xsl:value-of select="title" /> </a> </xsl:template> | |
7. | How do I print a variable |
<xsl:variable name="x" select="3"/> <text x="{$x}"/> Works with the July WD. If you are, however, transforming into HTML and looking at your result using a browser, you might not see it, since the browser (at least some browsers) ignore tags they don't know (and the respective attributes also). | |
8. | How to use a string variable as part of a pattern |
If the variable isn't a node set, then it cannot be used directly as a location step in a location path in this fashion, therefore, the XT behaviour is correct. String variables are allowed in *predicates*, so you could select all element children and then filter based on the element type name: <xsl:variable name="enums" select="document( '../common/enum.xml')/enums/*[local-name(.)=$enum]"/> | |
9. | How can you tell if a variable exists |
I would declare a global parameter <xsl:param name="v"/> and then in your code you can do <xsl:choose> <xsl:when test="not($v)"> <!-- parameter has not been supplied --> </xsl:when> <xsl:otherwise> <!--parameter has been supplied --> </xsl:otherwise> </xsl:choose> The reason this works is that when no actual parameter v is supplied, it takes its default value, which in this case is an empty string (because the xsl:param has no select expression or content), and the test not($v) returns true if $v is an empty string. | |
10. | xsl:variable behavior |
<xsl:template match="/"> <xsl:variable name="x"> <xsl:choose> <xsl:when test="false()">X</xsl:when> </xsl:choose> </xsl:variable> <xsl:if test="$x">x</xsl:if> </xsl:template> This (unexpectedly) prints "x" using jclark's xt while <xsl:template match="/"> <xsl:variable name="x"></xsl:variable> <xsl:if test="$x">x</xsl:if> </xsl:template> Does not (as expected). The value of the variable "is a result tree fragment equivalent to a node-set containing just a single root node having as children the sequence of nodes produced by instantiating the template" [11.2]. I.e. $x contains 1 node (which happens to be empty, because your template produces no nodes), so it converts to true. Mike Kay adds: Weird, but correct. This language is full of surprises. If xsl:variable (without select) has children, the value is a result tree fragment, regardless of the fact that the children produce no output; and in this case the equivalent node set to the result tree fragment is a root node with no children of its own; the test xsl:if test=$x tests whether the equivalent node set contains any nodes, which it does, because it contains a root node with no children. That one is a bit counter-intuitive, I think. > <xsl:template match="/"> > <xsl:variable name="x"></xsl:variable> > <xsl:if test="$x">x</xsl:if> > </xsl:template> > > Does not (as expected). "If the variable-binding element has empty content and does not have a select attribute, then the value of the variable is an empty string" [11.2]. And an empty string converts to false. | |
11. | xsl:variable |
>I don't understand how one is supposed to access the value of variables. >Why does this not put A in the result: > > <xsl:template match="/"> > <xsl:variable name="test" select="A"/> > <xsl:value-of select="$test" /> > </xsl:template> Because with the XPath expression you are using you are asking to assign the value of the child node named "A" to the variable named "test". I gather the document element of your document isn't named "A". The XPath expression for the string "A" is "A", so you'll have to do: <xsl:variable name="test" select="'A'"/> or <xsl:variable name="test select='"A"'/> if you want the string "A" to show up in your result. Phil Lanch Adds: The problem isn't with accessing the variable's value: it's with initializing it. select="A" is looking for <A> _elements_ (and not finding any) to get the _string_ 'A', you need to say: <xsl:variable name="test" select="'A'"/> Mike Brown adds: A is an expression that means 'child nodes of the current node that are elements named A'. What you want is 'A' (in single quotes). | |
12. | Can I change the value of global variables |
> Is it possible to assigne or change the value of a global variable or param > within a template? variables never change their value, parameters can be given new values when the template is called (not inside the template) > <xsl:variable name="variable.lang"/> There's no point doing this as this variable will always have value the empty string. Perhaps you wanted a top level xsl:param in which case the value could be set when the stylesheet is called. <xsl:template name="i18n"> <xsl:choose> <xsl:when test="$variable.lang='en'" >Abstract</xsl:when> <xsl:when test="$variable.lang='fr'" >Resume</xsl:when> ... </xsl:choose> </xsl:template> This doesn't work as the variable.lang is the variable declared at the top level, which is always ''. If you want the template to take a parameter called variable.lang, you must have a <xsl:param statement at the start of _this_ template. <xsl:template match="langset"> <!-- Set the value of language --> <xsl:param name="variable.lang"><value-of select="@xml:lang"/></xsl:param> ... <call-template name="i18n"/> ... </xsl:template> you see variable.lang need not be a parameter of your langset template as this template does not depend on this parameter, but you could pass on the value as a parameter to the cal-template <xsl:template match="langset"> <call-template name="i18n"> <xsl:with-param name="variable.lang" select="@xml:lang"/> </call-template> ... </xsl:template> However there is no real need to pass this information down as a parameter, as all templates can get the information in the original source tree. so you could not bother with parameters and just have <xsl:template name="i18n"> <xsl:variable name="variable.lang" select= "ancestor-or-self::*/@xml:lang"/> <xsl:choose> <xsl:when test="$variable.lang='en'" >Abstract</xsl:when> <xsl:when test="$variable.lang='fr'" >Resume</xsl:when> ... </xsl:choose> </xsl:template> | |
13. | Variables and qname |
> <xsl:call-template name="$v1"/> <!-- this causes a trouble to XT --> You can't do that. The name has to be a `qname'. Variable values are never qnames. The value of your $v1 in this case is a result tree fragment consisting of a text node with value `called' that is not the same as the name called. For similar reasons as select="called" isn't the same as select="'called'". You have to do <xsl:choose> <xsl:when test="$v1='called'"> <xsl:call-template name="called"/> | |
14. | Setting xsl:variable |
The trick that made things click for me was to realize that you can do: <xsl:variable name="var"> <xsl:choose> <xsl:when test="...expr1...">10</xsl:when> <xsl:when test="...expr2...">10</xsl:when> <xsl:otherwise>15</xsl:otherwise> </xsl:choose> </xsl:variable> That is, kind of "turn the thing inside out". Instead of <xsl:choose>-ing to set the variable, you set the variable to the <xsl:choose>-ed option... David Carlisle adds: if you use the non empty form of xsl:variable then its content is treated pretty much exactly the same as the content of xsl:template, so you can use xsl:choose, literal result elements, xsl:apply-templates, whatever. Then the result tree fragment, that if it was xsl:template would go into the result tree, instead gets bound to the variable as a value of type `result tree fragment'. It's all quite logical really. Anyway its all so much more natural to do <xsl:variable <xsl:choose than <xsl:choose <xsl: variable As you definitely want to set the variable, it's just the value that you want to choose. > What is the real difference between a > variable and a named template? > It seems like they can both do the exact > same thing. named templates accept data from parameters, and from select expressions relative to the point they are called. (ie they correspond to functions in other languages). variables are evaluated at the point of the xsl:variable and then are just fixed data. Mike Kay adds: > am I right in presuming that *whatever* is within > the <xsl:variable> container is assigned to the > variable? Absolutely. And not just what's within the <xsl:variable>: any output from <xsl:call-template> or <xsl:apply-template> instructions within the body of the <xsl:variable> is also written to the result tree fragment that is assigned to the variable. Actually, the spec doesn't explain this at all well. Section 7 says it is about how to create nodes in "the result tree", when in fact it is about writing to any output destination, which may be the final result tree or may be a result tree fragment. And "result tree fragment" is a very poor choice of name, since an RTF is not part of the result tree, it is a separate tree which can be copied into the result tree (or into another RTF) if you choose. | |
15. | How to collect nodes in a variable or param |
> Is it possible to "collect" nodes in a variable or param, > that can later be processed using > <xsl:apply-templates select="$varname">, Yes, provided $varname is a node-set. For example, you can write: <xsl:variable name="v" select="//para[starts-with(., "The ")]"/> <xsl:apply-templates select="$v"> to process all paragraphs whose text starts with "The ". You can't do this if $varname is a result tree fragment, except by using an extension function such as the node-set() function provided by both xt and Saxon. | |
16. | How to find the longest node in a node-set |
> Does anyone know a way I could define a variable that would > contain the number of characters in the longest node in a > node-set? Let the node set in question be > file://DIV[@type='Chapter']: if I have three, with string > lengths 88888, 99999, and 111110, I want my variable to be > 111110. <xsl:template name="getlongest"> <xsl:param name="nodeset"/> <xsl:param name="longest" select="0"/> <xsl:choose> <xsl:when test="$nodeset"> <xsl:choose> <xsl:when test="string-length($nodeset[1]) > $longest"> <xsl:call-template name="getlongest"> <xsl:with-param name="nodeset" select="$nodeset[position() > 1]"/> <xsl:with-param name="longest" select="string-length($nodeset[1])"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:call-template name="getlongest"> <xsl:with-param name="nodeset" select="$nodeset[position() > 1]"/> <xsl:with-param name="longest" select="$longest"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <xsl:value-of select="$longest"/> </xsl:otherwise> </xsl:choose> </xsl:template> | |
17. | How to build Variables Based on Multiple Elements |
> Is there a way for me to build upon a variable string based on > multiple elements of the same name? Sure. An xsl:variable can be assigned the result of a 'select' or can be used as a container for template instantiation. In the latter case, you can roam around, generating any kind of output you want, and instead of going directly into the result tree ("output") it goes into the variable, which is then of type Result Tree Fragment (RTF). An RTF can be used like a string, which is adequate for your purposes. So, you can just do the following: test.xml: <test> <elem>This</elem> <elem>is</elem> <elem>a</elem> <elem>test</elem> </test> concat.xsl: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:variable name="elements"> <xsl:text>Value of $elements: </xsl:text> <xsl:for-each select="/test/elem"> <xsl:value-of select="."/> <xsl:if test="position() < last()"> <xsl:text> </xsl:text> </xsl:if> </xsl:for-each> <xsl:text>.</xsl:text> </xsl:variable> <xsl:template match="/"> <xsl:value-of select="$elements"/> </xsl:template> </xsl:stylesheet> | |
18. | How to add HTML code to variables |
Q Expansion <xsl:template match="FreeText"> <xsl:variable name="somevalue"> <b>Hello</b> </xsl:variable> <xsl:call-template name="TableRow"> <xsl:with-param name="label"><xsl:value-of select="$somevalue"/></xsl:with-param> </xsl:call-template> </xsl:template> This just produces "Hello" without bold tags. you need to use <xsl:copy-of select="$somevalue"/> instead of <xsl:value-of/>. | |
19. | How to put HTML code into variables |
> <xsl:template name="TableRow"> > <tr> > <td><xsl:value-of select="$label"/></td> > </tr> > </xsl:template> xsl:value-of creates a single text node in the result tree. If $label is a result tree fragment (yours is), then the text node that is created will contain the concatenation of all the text nodes in the fragment. When you set $label to be the result tree fragment, <b>Hello</b> It is a node structure (not counting namespace nodes): element 'b' |___text 'Hello' It is *not* the character sequence < b > H e l l o < / b > You will get the results you want if you simply replace xsl:value-of with xsl:copy-of. | |
20. | xsl:variable or constant |
> Clearly this was an intentional design decision, > but it makes it impossible > to do any kind of simplistic calculation > (e.g. totalling a shopping cart > that has price and quantity information for each item) > without resorting to recursion From David Carlisle: But `resorting to recursion' isn't so bad and is no worse than `resorting to a loop'. A loop being just a special case of recursion. Having side effect free semantics makes programs far easier to understand, and (in principle) far easier for a system to re-order or parallelise. Consider an expression such as f(x) + f(y) If + is commutative then either f(x) or f(y) may be evaluated first, or on a suitable architechture, they may be evaluated at the same time. But if functions can have side effects, so evaluating f(x) can change the value of y then re-ordering is impossible and the semantics of the expression and thus the whole program are far harder to understand due to hidden dependencies. This is why functional programming style generally is a GoodThing. I think the reasoning behind using this style for styling langages is that if there is no global state then if you jump to page 1001 you can start to render that page immediately without having to process the entire document in order to find the values of all global variables. Due to the way xslt evolved this particular reason probaly does not apply anymore as the power of the xpath queries mean that you are more or less obliged to have the whole document in memory anyway. But still, you are not obliged to actually process templates for nodes earlier in the document unless _explictly_ accessed via a select atribute. and from Mike Kay I wasn't involved in the decision, but I've read some of the early working papers, and as far as I can see the principal rationale was that a language without side-effects would be capable of incremental rendering, e.g. starting to display the output before all the input has arrived. I suspect that in those early days most people expected the language to have a lot less computational power than it ended up with. The desire to make it declarative and side-effect free was generally coupled with a (contradictory) requirement to enable it to call external functions or scripts. There is also an argument that a language without side-effects is capable of a higher degree of optimisation, is less error prone, and so on. The one argument I haven't seen in anything I've read is any discussion of ease of use, ease of learning, or (sacrilege) "why not ask the users what they want?" | |
21. | Variables and constants |
can anyone explain to me the rationale for not having true variables a'la procedural programming languages (i.e. you can re-assign the value of an existing variable)? XSLT is not alone in not having assignment, in fact there is a whole family of programming languages called the functional programming languages that work this way. The best-known are perhaps Standard ML and Haskell. (No, Lisp does not belong here. Lisp is imperative, just like the mainstream languages.) The difference between the traditional imperative languages and the functional ones is a deep one and not easily understood. At the deepest level it has to do with whether change (that is, time) is allowed in a program or not. If change is banished, functions always return the same values and reasoning about what is going on in the program becomes enormously much simpler. The best description you are likely to find of what this really means appears in 'The Structure and Interpretation of Computer Programs', by Abelson, Abelson and Sussman. (The book is definitely recommended for anyone who wants to to serious programming, BTW.) David Carlisle exemplifies The fact that some calculations are rather awkward in xslt is not really due so much to the functional style, as to the fact that the main `function' expression that you have available, namely the template returns a result of a type `result tree fragment' that is opaque to the expression language. If the restrictions on querying into rtf were not there or (equivalently) a function is provided to coerce an rtf back to a node set so that it may be queried, then many things become much simpler. So here is your basket calculation sans recursion but with xt:node-set <x> <thing><quantity> 1</quantity><price> 2</price></thing> <thing><quantity> 4</quantity><price> 5</price></thing> <thing><quantity> 3</quantity><price>10</price></thing> <thing><quantity> 2</quantity><price> 1</price></thing> </x> <total xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xsl:version="1.0" xmlns:xt="http://www.jclark.com/xt" > <xsl:variable name="x"> <xsl:for-each select="x/thing"> <a><xsl:value-of select="quantity * price"/></a> </xsl:for-each> </xsl:variable> <xsl:value-of select="sum(xt:node-set($x)/a)"/> </total> xt basket.xml basket.xsl <?xml version="1.0" encoding="utf-8"?> <total>54</total> | |
22. | Understanding Variables |
If you use a select attribute to obtain a node list, the variable element will contain a node list and you can query the path of this node list using /, // and []. If you do not use a select statement and instead copy the contents of a node list in the content of the variable, you obtain a result tree fragment which does not support path based queries. Therefore, in order to sort the content of the variable, you cannot use a select statement. | |
23. | Concatenate several element values in a variable |
> I want to put into a variable the content of > several node which are html code, how to concat two > strings <xsl:variable name="global"> <xsl:value-of select="/the/first/node/you/want"/> <xsl:value-of select="/the/second/node/you/want"/> <xsl:value-of select="/the/third/node/you/want"/> </xsl:variable> Then you can use <xsl:value-of select="$global"/> in any template to get the concatenation of the string values of the nodes. | |
24. | Variable in the match part of a template |
It isn't a blanket truth that a variable name cannot be used at all in the match part of a template. Consider the example below where a top-level variable (the only variables that can be referenced in template match expressions), parameterized from the command line, is used to match only those elements named by the operator invoking the stylesheet. Note that while variables cannot be used as a node test in a location step, they can be used in a predicate. Interestingly, Saxon gives an error on this stylesheet that works fine with XT. I think Saxon is in error here because XSLT [5] allows predicates in match patterns. T:\ftemp>type test.xml <?xml version="1.0"?> <test> <a>This is a</a> <b>This is b</b> <c>This is c</c> <d>This is d</d> </test> T:\ftemp>type test.xsl <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="text"/> <xsl:param name="which"/> <!--require the operator to specify--> <!--push all children for illustration--> <xsl:template match="/test"> <xsl:apply-templates/> </xsl:template> <!--only interested in the ones indicated by the operator--> <xsl:template match="*[name(.)=$which]" priority="1"> <xsl:value-of select="."/> </xsl:template> <!--eat all other output--> <xsl:template match="*|text()" priority="0"/> </xsl:stylesheet> T:\ftemp>xt test.xml test.xsl which=a This is a T:\ftemp>xt test.xml test.xsl which=c This is c T:\ftemp> | |
25. | Variables - Including files into a stylesheet > Rather than copying the variable evaluation into the new > stylesheet, is it > possible to define the variables in a file and then include > them into the stylesheet. |
Yes. They just need to be global variables rather than being local to the root (template match="/") template. The xsl:variable declarations you want to use in more than one place should be top-level elements, defined outside any template rule. You can include them in a stylesheet module that only contains variable declarations if you wish, i.e. <xsl:stylesheet ...> <xsl:variable .../> <xsl:variable .../> <xsl:variable .../> </xsl:stylesheet> People often fail to realise that global variable definitions can reference the source document, they can even call xsl:call-template and xsl:apply-templates | |
26. | Variable testing |
> <xsl:variable name="account">on</xsl:variable> > <xsl:if test="'{$account}'='on'"> Chris Bayes offers Try <xsl:variable name="account" select="on" /> ... <xsl:if test="$account='on'"> Mike Kay get rid of the curly brackets and the first pair of quotes: <xsl:if test="$account='on'"> (Curly brackets are never used inside an XPath expression, they are only used to nest an XPath expression within an attribute value that is designated as an attribute value template). get rid of the leading and trailing spaces, either by declaring the "constant" as <xsl:variable name="account">on</account> or by using normalize-space() in the comparison. | |
27. | Conditional variable assignment |
Using <xsl:choose> to conditionally assign a variable, your first instinct would likely compel you to do the following: <!--Variable n does not exist here since it's not been set yet --> <xsl:choose> <xsl:when test="b > 45"> <xsl:variable name="n" select="15"/> <!--Variable n has value "15"inside this <xsl:when>--> </xsl:when> <xsl:otherwise> <xsl:variable name="n" select="20"/> <!--Variable n has value "20"inside this <xsl:otherwise>--> </xsl:otherwise> </xsl:choose> <!--Variable n does not exist here since it's out of scope --> While this is completely legal XSLT, it is probably not what you intended. It creates a variable n with value 15 that is scoped to the part of the stylesheet tree nested inside the <xsl:when> element where the variable is set. So the variable n comes into existence, but no other element nested within the <xsl:when> element makes use of its value. It dies a silent death, unnoticed. Then within the <xsl:otherwise> a new variable -- coincidentally, also named n -- is bound to the value 20; however, it meets a similar fate. If the developer tries to reference the value of n outside of this <xsl:choose>, it will be undefined because of the scoping rules for XSLT variables. Rather than thinking of conditionally assigning a variable, in XSLT you need to think instead of assigning a variable a conditional value. An example will make things clear. Here is the right way to assign the variable n to the conditional value 15 or 20: <xsl:variable name="n"> <!--Conditionally instantiate a value to be assigned to the variable --> <xsl:choose> <xsl:when test="b > 45"> <xsl:value-of select="15"/><!-- We either instantiate a "15" --> </xsl:when> <xsl:otherwise> <xsl:value-of select="20"/><!-- ...or a "20" --> </xsl:otherwise> </xsl:choose> </xsl:variable> <!--Value of n is visible here and will be either 15 or 20 --> | |
28. | Output an element whose value is in a variable |
>Is there a valid synatx for outputting an element whose value is in a >variable ? > >I want to say <xsl:variable name="style" select="'TABLE'" /> > <xsl:element name="$style" /> <xsl:element name="{$style}">.....</xsl:element> | |
29. | Can I really update variables in a for-each |
<TABLE> <xsl:for-each select="COMPONENT[position() <= $half]"> <xsl:variable name="here" select="position()"/> <TR> <TD><xsl:value-of select="."/><xsl:value-of select="$here"/></TD> <TD> <xsl:choose> <xsl:when test="../COMPONENT[$here+$half]"> <xsl:value-of select="../COMPONENT[$here+$half]"/> </xsl:when> <xsl:otherwise></xsl:otherwise> </xsl:choose> </TD> </TR> </xsl:for-each> </TABLE> </xsl:template> What I'm not clear about is why the 'here' variable is updatable as the for-each iterates, it was my understanding that variable values cannot be changed. Could someone explain ? Essentially you can't change the value of a variable once it's bound, but you can bind a new variable that has the same name. (XSLT imposes some restrictions on when you can rebind, to avoid confusion, one assumes) The body of the for-each is a template, like the body of xsl:template each time you go round the loop the template body is instantiated again and you get a new variable called here, its scope ends at the end of that template body, ie effectively you get a new variable created and discarded each time round the loop. Note this is completely different from the use of variables in loops in procedural languages where the _same_ variable survives throughout the loop, carrying state information (like how many times the loop has been executed). The "variable value can't change" mantra refers to the fact that there is not an analogue of x=x+1 which some people, corrupted by procedural languages seem to feel is a natural thing to write, whereas it is obviously an affront to the laws of nature, unless x happens to be 0:-) | |
30. | Can not convert STRING to a NodeList |
| You can't use a variable as a select expression: | <xsl:value-of select="$b_level_total/CNT"/> | is illegal. The problem is not that it is a *variable*, but that it is a variable whose value is not of type node-set. So, if $foo contains a string, boolean, number, or result-tree-fragment, doing select="$foo/bar" is an error. However, if $foo contains a node-set, then select="$foo/bar" is fine. In the original authors example, we'd have to see what he was passing to the "b_level_total" parameter in his <xsl:with-param> in the calling template to see the culprit, but if he's passing a string-valued parameter, this $b_level_total/FOO will generate the error he's seeing. | |
31. | White space in xsl variable values. |
> From: Mike Hostetler [mailto:mikeh@nukeroad.net] > Sent: 20 December 2000 19:45 > 1. When doing this: > <xslvariable name="name"> > chap<xsl:number count="chapter"/> > </xsl:variable> > > <a name="{$name}"> > [....] > I get control characters in the HTML, a la: > <a name="%A0%20chap1"> The newline and spaces before the "chap" are part of the same text node, so they are part of the value of the variable. Remove them from the stylesheet, or use <xsl:text>, or use <a name="normalize-space($name)">. | |
32. | Variable or parameter: true or false |
This is very tricky. Problem is, your declaration <xsl:variable name="selectUser"/> isn't as "empty" as it looks. Absent a select expression, the variable contains an empty string. XSLT (11.1) says about this special data type that it "is treated equivalently to a node-set that contains just a single root node" (going on to qualify this considerably). That is, if you had the declaration <xsl:variable name="selectUser"> <holla/> </xsl:variable> the variable $selectUser would be bound to a result tree fragment with a structure containing two nodes, a root and a single child (an element named 'holla'). But yours contains just that single root. Alas, when this is treated as a node-set containing a root for purposes of evaluating the boolean() function, this comes out as true(). If you'd said <xsl:variable name="selectUser" select="''"/> (setting the value to be the empty string), or <xsl:variable name="selectUser" select="false()"/> (a boolean false), or even <xsl:variable name="selectUser" select=""/> (the null expression evaluates to an empty node set), ...*then* you'd have gotten false as you expected. This is only one reason result tree fragments are troublesome. Since they are also of questionable utility, rumor has it they may be invited to leave, disappearing from future versions of XSLT. Then you'll get an (empty) node set as you expected. Jeni Tennison expands a little > I want to determine if a param passed into a stylesheet has been > assigned a value. In a perfect world, should I still be able to do > the simple true/false test? My guess from looking at the spec is > that "selectUser" should be an empty string if it is not assigned, > just like the variable case. That's right. xsl:param and xsl:variable work in a very similar way, it's only that xsl:params can have values assigned to them from 'outside' whereas xsl:variables can't. There isn't a way to check whether a parameter has been passed a value, but you can usually get around it by making the default value of the parameter something that it will never be. For example: <xsl:param name="selectUser" select="'a-random-string'" /> If you set up this default, then you can test whether the $selectUser parameter has that value: <xsl:if test="$selectUser = 'a-random-string'"> ... </xsl:if> Of course if you're writing a utiltiy template, often it's possible for people to pass in parameter values that are just plain illegal, and you need to check for these illegal values and do something appropriate anyway. You might find it more convenient to share this processing, and set the default to an illegal value. If you only want to send a message (and possibly terminate the transformation) if the parameter isn't specified, then another method is to use the xsl:message element in the content of the xsl:param: <xsl:param name="selectUser"> <xsl:message terminate="yes"> This template must be passed a value for the 'selectUser' parameter. </xsl:message> </xsl:param> [It's not clear to me where in the spec it says that the default value of the parameter will not be evaluated if the template is passed a parameter value, but it seems sensible and Saxon and MSXML do it.] | |
33. | Argument for id function and unique id values |
> 1. From the XSLT 1.0, XSLT grammer production 3 under section > 5.2 indicates > the argument > to id function has to be a literal, i.e. a string enclosed > in single or > double quotes. > > 2. From the XPath 1.0, section 4.1 indicates the argument can > be other than > a > string. > > It seems that XSLT 1.0 says that the argument to id function must be > a literal in syntax while XPath says it doesn't have to be. Which is > correct? Both are correct. In a pattern, the argument to id() must be a literal. In an XPath expression, it can be any expression. Jeni adds a twist > keeping in the mind that i have to make sure all my session elements > have to different, is there any way to get around this? Well, twisting the question so that it's actually relevant to the group (i.e. about XSLT), you can test within XSLT whether there is more than one element with a particular id attribute, even if that attribute isn't (recognised as) an ID attribute. You can define a key to index the Session elements by their id attribute. XSLT doesn't care what kind of value the id attribute has (it's the validating XML parser that the XSLT processor uses that will). So you can make the id attribute a CDATA attribute, as David said, and then use it with a key: <xsl:key name="Sessions" match="Session" use="@id" /> Then, if you have a list of the ids (which you can get by going through all the Session elements in the document and retrieving their ids), you can see whether the key returns more than one Session with that id. You can get all the Session ids with //Session/@id (although there may well be a more efficient path - you don't say what your source looks like). Then you can iterate over them and test whether there's more than one Session with that id: <xsl:for-each select="//Session/@id"> <xsl:if test="key('Sessions', .)[2]"> <xsl:message terminate="yes"> ERROR: There is more than one Session with the same id. </xsl:message> </xsl:if> </xsl:for-each> There are also schema languages that will allow you to test for uniqueness with values that aren't ID attributes, including XML Schema and Schematron. | |
34. | Variable values in predicates. |
> Could someone explain me the following example of the W3C specification > > <xsl:variable name="n">2</xsl:variable> > ... > <xsl:value-of select="item[$n]"/> > > This will output the value of the first item element, because the > variable n will be bound to a result tree fragment, not a number. > (See chapter 11.2) > > What is the reason for outputing the value of only the first item > element ? The definition of xsl:value-of indicates that it evaluates the expression in its select attribute, and converts that to a string in the same way as the string() function (see Section 7.6.1 of the XSLT Recommendation). When you convert a node set to a string with the string function, you always get the value of the *first* node in that node set (see Section 4.2 of the XPath Recommendation). The XPath 'item[$n]' gets a node set of item elements that are children of the current node. The item elements that are chosen depend on the data type of the predicate. If the predicate were a number (e.g. item[2]) then it would get the item element that had that position amongst its sibling item elements (i.e. the second item element). Otherwise, the expression is converted to boolean in the same way as the boolean() function (see Section 2.4 of the XPath Recommendation). In this case, and the point of the example, the variable $n is set to a result tree fragment. Evaluating the variable gives a result tree fragment, not a number, so it's converted to a boolean. Converting a result tree fragment to a boolean always results in true() because a result tree fragment is treated, in this context, like a node set with a single root node (which has children as defined by the content of the variable). As the predicate always evaluates to true, the node set returned by 'item[$n]' holds all the item children of the current node. When this node set is evaluated as a string, then you get the string value of the first of the item elements. | |
35. | Modifying variables the XSLT way |
Once a variable or parameter has been assigned within its scope, it cannot be changed. Once created, it is read-only, as you have found. But that does not mean you cannot achieve what you want. You say that you want to assign the value of the parameter in a template and pass it to another template. You can do that without using a global template. A parameter can be declared again each time a template is instantiated because it is in a different scope each time (that is, the scope of the instantiated template). A typical way to do this is like so: <xsl:template match='...'> The point to to always create and pass a parameter each time you need it. This even works when you call a template recursively (i.e., a template calls itself). This approach does what you said you want to do. | |
36. | xsl:variable syntax |
You are being misled a bit by the syntax -- this can happen easily, given how concise and compact XPath is. But "a path with a single variable" is not resolved in the way you are thinking. Consider these two variable declarations:
These variables both have values assigned by their declarations, but the resemblance stops there. The first variable is assigned a node set, consisting of all the elements called 'node' in the document. The second variable (despite its misleading name) is assigned a string value, "//node". In this situation, it's possible to say (in XPath/XSLT 1.0), for example, <xsl:for-each select="$nodeset1"> but not <xsl:for-each select="$nodeset2"/>. This is because in XSLT 1.0, it's not possible to iterate over and process a string, although you can do this with node sets. You can also say "$nodeset1/*" and get back the children of the nodes in variable nodeset1. But you can't say "*/$nodeset1" even though $nodeset1 is a node set. This is because since it's a node set already, you can't use it as a step in an XPath expression. Again despite appearances, "$nodeset1/*" is really an *operation* performed on $nodeset as an argument, not an XPath "Location Path" of the classic sort. A string doesn't magically turn into a set of nodes (for that, use an extension function to evaluate the string as XPath). Nor can you treat a nodeset as a string -- at least if the string you want is a string that would work as an XPath to select just that set of nodes. (If you treat a node set as a string, you get the string value of the first node in the set in document order.) I hope that helps. Once you understand how XPath works and what's a node and what's a string in the data model (and how you can assign either to variables and parameters), XSLT gets a lot easier. | |
37. | Modifying variables. |
Side effect free programming is not unique to XSLT, it is a long established programming paradigm, designed to make things easier and more natural for people. It allows many optimisation strategies that are not available in imperative programming languages as code may be re-written and re-arranged by the optimiser with much more freedom. the use of "variable" in functional languages such as XSLT matches the use of the word in mathematics (where it originates). there is nothing at all natural in variables that change their value, that was an introduction in imperative programming languages which (due to limitations at the time) are designed to more closely model the machine implementation than the natural specification of a problem.
Not at all. If for example you could set a variable in one template and use its value in another, it would constrain the processor to evaluate templates in a specific order, which is not the case now, evaluation might be in any order, or parallelised across different threads, the fact that each template only returns a single result (like a mathematical function0 not change the state of arbitrary number of otehr variables makes this much easier to understand. | |
38. | Variable visibility |
If you have: <xsl:template match="A"> <z> <a> <b> <c> <xsl:variable name="x"/> <d/> <e> <f/> </e> <g/> </c> </b> </a> </z> </xsl:template> then the places where variable x is visible are the instructions d, e, f, and g: that is, the following siblings of the xsl:variable element, and their descendants. | |
39. | using variables in a sort |
You cannot use variables like this with select attribute of xsl:sort You have to resort to something like <xsl:for-each select="something/*[name() = $firstValue]"> <xsl:sort select="." order="{$ord}"/> <xsl:value-of .. (display data) <xsl:value-of .. (display data) </xsl:for-each> | |
40. | Conditionally setting variables |
This is a bit tricky in XSLT 1.0. The usual recommendation for creating a node-set conditinoally is: <xsl:variable name="selectclause" select="(//client)[$clientid=''] | (//client[$clientid!=''])"/> or more generically select = then-part[condition] | else-part[not(condition)] In XSLT 2.0 of course you can do select="if (condition) then expr-1 else expr-2" | |
41. | Defining variables |
Obligatory daily comment on this list:-) don't do this: <xsl:variable name="actualelectricity"> <xsl:value-of select="sum(/statement/item[$datetest and $electricitytest]/amount)"/> </xsl:variable> do this <xsl:variable name="actualelectricity" select="sum(/statement/item[$datetest and $electricitytest]/amount)"/> as it's less to type and loads more efficient as the variable then holds a number not a result tree fragment with a root node and a text node with string value the decimal representation of a number. | |
42. | Assigning value to variable based on xsl:choose test |
The problem with your code is that instead of creating one variable with two possible values, it creates two different variables, and of course they both go out of scope. The solution is <xsl:variable name="X"> <xsl:choose> <xsl:when test="....">8</xsl:when> <xsl:when test="....">4</xsl:when> </xsl:choose> </xsl:variable> In XSLT 2.0 you can also write <xsl:variable name="X" select="if (...) then 8 else 4"/> | |
43. | Using Variables |
Never use an xsl:variable with content like this unless you really need to generate a new result tree fragment 9which is essentially a new node tree with a root node (/) a text node with string value. that's expensive to build and has to be coersed back to a string when used. You just want a string here so <xsl:variable name="identifier"> select="normalize-space(dc:identifier)"/> which is less code to type and a lot more efficient (athough in this case you don't really need a variable at all). In xslt1 that will use the first dc:identifier, in xslt2 it will generate an error that there is more than one. To use the second you can use <xsl:variable name="identifier"> select="normalize-space(dc:identifier[2])"/> or [last()] or whatever predicate you need to specify. <rdf:Description> <xsl:attribute name="rdf:about"><xsl:value-of select="$identifier"/></xsl:attribute> could be written more simply as <rdf:Description rdf:about="{$identifier}"> or just inline the variable if it is only used once: <rdf:Description rdf:about="{normalize-space(dc:identifier[2]}"> |