Declaring multiple XSLT variables of the same name in the same scope - xslt

I know that XSLT variables once initialized cannot change their respective values.
Saxon-PE 9.2.0.6 allows the following syntax:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:variable name="myVar" select="'asdex upgrade'" />
<xsl:value-of select="$myVar" /><xsl:text>
</xsl:text>
<xsl:variable name="myVar" select="'Wendelstein'" />
<xsl:value-of select="$myVar" /><xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
with this output:
asdex upgrade
Wendelstein
So what is happening here? Obviously this code is not in contradiction to the fact that variables are immutable in XSLT, or else the Saxon processor would not process it. Am I creating two variables with the same name here (with their scopes local to the given template)? If so, do I have means to access the first variable after the second one was created, or did it cease to exist, or is it overshadowed in the XSLT processor's symbol tables? Or what gives?
Thanks for any enlightenment!

See https://www.w3.org/TR/xslt20/#scope-of-variables on details, it explains
For any variable-binding element, there is a region (more
specifically, a set of element nodes) of the stylesheet within which
the binding is visible. The set of variable bindings in scope for an
XPath expression consists of those bindings that are visible at the
point in the stylesheet where the expression occurs.
and
A local variable binding element is visible for all following siblings
and their descendants, with two exceptions: it is not visible in any
region where it is shadowed by another variable binding, and it is not
visible within the subtree rooted at an xsl:fallback instruction that
is a sibling of the variable binding element. The binding is not
visible for the xsl:variable or xsl:param element itself.
[Definition: A binding shadows another binding if the binding occurs
at a point where the other binding is visible, and the bindings have
the same name. ] It is not an error if a binding established by a
local xsl:variable or xsl:param shadows a global binding. In this
case, the global binding will not be visible in the region of the
stylesheet where it is shadowed by the other binding.
So the second xsl:variable name="myVar" shadows the first one.

Related

Allowing an attribute-set to set a namespace required by an attribute

I understand that xsl:attribute-set exists to allow a set of XML attributes to be grouped under a single name, which can then then easily be applied to several similar elements at a later date.
I understand that namespaces are not attributes and cannot be set using this.
However in Saxon9.8EE I note this works and I was wondering if this is safe to use:
<xsl:attribute-set name="swbml.ir" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:attribute name="version">4-2</xsl:attribute>
<xsl:attribute name="xsi:schemaLocation">http://www.fpml.org/2005/FpML-4-2 /path/to/swbml-ird-main-4-2.xsd</xsl:attribute>
</xsl:attribute-set>
By adding the xsi namespace to the xsl:attribute-set itself, it applies this namespace to any element using the swbml.ir attribute set.
(of course it has to because one of the attributes sits in the xsi namespace)
So this:
<SWBML xmlns="http://www.fpml.org/2005/FpML-4-2" xsl:use-attribute-sets="swbml.ir">
Results in:
<SWBML xmlns="http://www.fpml.org/2005/FpML-4-2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="4-2"
xsi:schemaLocation="http://www.fpml.org/2005/FpML-4-2 /path/to/swbml-ird-main-4-2.xsd">
This is exactly what I want. But it feels like I might be stretching the intended use-case for attribute sets?
Specifically if I try to go one step further and add xmlns="http://www.fpml.org/2005/FpML-4-2" like so:
<xsl:attribute-set name="swbml.ir" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.fpml.org/2005/FpML-4-2">
The default xmlns is not applied to <SWBML> - which is kinda what I expect.
So - is the rule that attribute sets will add any namespace that is required in order to qualify any attribute the set contains, BUT will not add any other namespace? Or have I strayed into undefined territory?
Your understanding is basically correct, in that if there is content bound to a namespace, and you include it in your output, then the namespace will come along for the ride. However, the fact that you happen to have declared it on the attribute-set is not critical. It could be declared in other places in the stylesheet, such as on the xsl:stylesheet element, to be in-scope and referenced in the attribute-set.
Building upon the examples that you posed, you could move the declaration of the xsi namespace prefix out of the xsl:attribute-set and up to the xsl:stylesheet element, and it would still appear in your output if the attribute-set were applied to the element:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
exclude-result-prefixes="xsi">
<xsl:output method="xml"/>
<xsl:attribute-set name="swbml.ir">
<xsl:attribute name="version">4-2</xsl:attribute>
<xsl:attribute name="xsi:schemaLocation">http://www.fpml.org/2005/FpML-4-2 /path/to/swbml-ird-main-4-2.xsd</xsl:attribute>
</xsl:attribute-set>
<xsl:template match="/">
<SWBML xmlns="http://www.fpml.org/2005/FpML-4-2" xsl:use-attribute-sets="swbml.ir"/>
</xsl:template>
</xsl:stylesheet>
And it would not appear in the output if the attribute-set is not applied to the content:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
exclude-result-prefixes="xsi">
<xsl:output method="xml"/>
<xsl:attribute-set name="swbml.ir">
<xsl:attribute name="version">4-2</xsl:attribute>
<xsl:attribute name="xsi:schemaLocation">http://www.fpml.org/2005/FpML-4-2 /path/to/swbml-ird-main-4-2.xsd</xsl:attribute>
</xsl:attribute-set>
<xsl:template match="/">
<SWBML xmlns="http://www.fpml.org/2005/FpML-4-2" />
</xsl:template>
</xsl:stylesheet>
Note that I used exclude-result-prefixes in both examples to ensure that the xsi namepsace is pruned from the output if unused. Otherwise, the in-scope namespace might come along for the ride in the output, even if it were not applied to any content.
Yes, this will work: as #MadsHansen points out, when you use <xsl:attribute name="p:u"/> the only thing that really matters is that the prefix p is declared somewhere - on the xsl:attribute element itself, or on one of its ancestors. If it's convenient to declare it at the level of the xsl:attribute-set itself, then fine, do that.
A thing to watch out for here is that this doesn't apply to QName-valued attributes. If you want to do
<xsl:attribute name="xsi:type">xs:date</xsl:attribute>
then you can get the prefix xsi declared in the result document simply by having it in-scope for the xsl:attribute instruction, but for the xs prefix you need to work a bit harder (because the XSLT processor doesn't know that the attribute value xs:date is a QName). In this case you need to explicitly ensure that some containing element in the result tree declares the xs namespace.

Constructing, not selecting, XSL node set variable

I wish to construct an XSL node set variable using a contained for-each loop. It is important that the constructed node set is the original (a selected) node set, not a copy.
Here is a much simplified version of my problem (which could of course be solved with a select, but that's not the point of the question). I've used the <name> node to test that the constructed node set variable is in fact in the original tree and not a copy.
XSL version 1.0, processor is msxsl.
Non-working XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:variable name="entries">
<xsl:for-each select="//entry">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="entryNodes" select="msxsl:node-set($entries)"/>
<xsl:for-each select="$entryNodes">
<xsl:value-of select="/root/name"/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
XML input:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<name>X</name>
<entry>1</entry>
<entry>2</entry>
</root>
Wanted output:
X1X2
Actual output:
12
Of course the (or a) problem is the copy-of, but I can't work out a way around this.
There isn't a "way around it" in XSLT 1.0 - it's exactly how this is supposed to work. When you have a variable that is declared with content rather than with a select then that content is a result tree fragment consisting of newly-created nodes (even if those nodes are a copy of nodes from the original tree). If you want to refer to the original nodes attached to the original tree then you must declare the variable using select. A better question would be to detail the actual problem and ask how you could write a suitable select expression to find the nodes you want without needing to use for-each - most uses of xsl:if or xsl:choose can be replaced with suitably constructed predicates, maybe involving judicious use of xsl:key, etc.
In XSLT 2.0 it's much more flexible. There's no distinction between node sets and result tree fragments, and the content of an xsl:variable is treated as a generic "sequence constructor" which can give you new nodes if you construct or copy them:
<xsl:variable name="example" as="node()*">
<xsl:copy-of select="//entry" />
</xsl:variable>
or the original nodes if you use xsl:sequence:
<xsl:variable name="example" as="node()*">
<xsl:sequence select="//entry" />
</xsl:variable>
I wish to construct an XSL node set variable using a contained
for-each loop.
I have no idea what that means.
It is important that the constructed node set is the original (a
selected) node set, not a copy.
This part I think I understand a little better. It seems you need to replace:
<xsl:variable name="entries">
<xsl:for-each select="//entry">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
with:
<xsl:variable name="entries" select="//entry"/>
or, preferably:
<xsl:variable name="entries" select="root/entry"/>
The resulting variable is a node-set of the original entry nodes, so you can do simply:
<xsl:for-each select="$entries">
<xsl:value-of select="/root/name"/>
<xsl:value-of select="."/>
</xsl:for-each>
to get your expected result.
Of course, you could do the same thing by operating directly on the original nodes, in their original context - without requiring the variable.
In response to the comments you've made:
We obviously need a better example here, but I think I am getting a vague idea of where you want to go with this. But there are a few things you must understand first:
1.
In order to construct a variable which contains a node-set of nodes in their original context, you must use select. This does not place any limits whatsoever on what you can select. You can do your selection all at once, or in stages, or even in a loop (here I mean a real loop). You can combine the intermediate selections you have made in any way sets can be combined: union, intersection, or difference. But you must use select in all these steps, otherwise you will end up with a set of new nodes, no longer having the context they did in the source tree.
IOW, the only difference between using copy and select is that the former creates new nodes, which is precisely what you wish to avoid.
2.
xsl:for-each is not a loop. It has no hierarchy or chronology. All the nodes are processed in parallel, and there is no way to use the result of previous iteration in the current one - because no iteration is "previous" to another.
If you try to use xsl:for-each in order to add each of n processed nodes to a pre-existing node-set, you will end up with n results, each containing the pre-existing node-set joined with one of the processed nodes.
3.
I think you'll find the XPath language is quite powerful, and allows you to select the nodes you want without having to go through the complicated loops you hint at.
It might help if you showed us a problem that can't be trivially solved in XSLT 1.0. You can't solve your problem the way you are asking for: there is no equivalent of xsl:sequence in XSLT 1.0. But the problem you have shown us can be solved without such a construct. So please explain why you need what you are asking for.

How to access global variable value in multiple template tags

I have created a global variable and its been used in two templates I am able to access i first template, not able to get the value in second template . Below are my workings
<xsl:variable name="currentValue"></xsl:variable> //global variable declaration
<xsl:template match="/">
<xsl:variable name="unique-accounts" select="/*/*/*/accountId/text()generate-id()=generate-id(key('account-by-id', .)[1])]"/>
<xsl:for-each select="$unique-accounts">
<xsl:variable name="currentValue" select="current()"/>
<xsl:value-of select="$currentValue"/> //here value is printing
<xsl:apply-templates select="//secondTemplate"/>
</xsl:for-each>
</xsl:template> //close od first template
<xsl:template match="secondTemplate">
<xsl:value-of select="$currentValue"/> //here value is not printing
</xsl:template>
If I follow the logic of your code correctly (which is not at all certain), you have declared a global variable as:
<xsl:variable name="currentValue"></xsl:variable>
i.e. as empty. You are then calling this global variable inside your second template:
<xsl:template match="secondTemplate">
<xsl:value-of select="$currentValue"/>
</xsl:template>
and getting an empty result - which is exactly what you should expect.
Within your first template, the declaration:
<xsl:variable name="currentValue" select="current()"/>
overrides the global variable declaration for the scope of the template (more precisely, for the following siblings of the declaration and their descendants - but since the declaration is the first thing you do in the template, it comes down to the same thing).
In more technical terms, the binding established within the template shadows the binding established by the top-level xsl:variable element:
http://www.w3.org/TR/xslt/#dt-shadows
Variables in XSLT are named values, they are not memory locations in which you can place different values at different times. That's a fundamental difference between declarative and procedural programming.
If you would like to explain the problem you are trying to solve (that is, the input and output of the transformation) then I'm sure we can explain how to write it in XSLT. Reverse-engineering the requirement from a completely wrong approach to the solution isn't possible.

Indirect variable/parameter reference (name in another property / another variable)

Is it possible using XSL to access a variable (or a parameter) whose name is stored in another variable (or parameter)? If no, why?
I am new to xsl, coming from other languages, where this functionality is accessible, like bash, ant. Maybe I was wrong even looking for an answer to this question. But since I didn't find it on SO, I think there should be one.
Two examples. I have parameters p1, p2, p3. Then I have a parameter pname whose value is a string p2. I would like to read the value of p2 using pname, something like $$pname or ${$pname}. Or in a more complicated way. If pnumber is equal to 2, then I would like to read the value of the parameter with name concat('p', $pnumber), something I would code asparam-value(concat('p', $pnumber)).
This is possible whenthe XSLT stylesheet accesses itself as a regular XML document:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="p1" select="'P1-Value'"/>
<xsl:param name="p2" select="'P2-Value'"/>
<xsl:param name="p3" select="'P3-Value'"/>
<xsl:param name="pName" select="'p3'"/>
<xsl:param name="pNumber" select="2"/>
<xsl:variable name="vDoc" select="document('')"/>
<xsl:template match="/">
<xsl:value-of select=
"concat('Param with name ',
$pName,
' has value: ',
$vDoc/*/xsl:param[#name = $pName]/#select
)"/>
<xsl:text>
</xsl:text>
<xsl:variable name="vParam" select=
"$vDoc/*/xsl:param[#name = concat('p', $pNumber)]"/>
<xsl:value-of select=
"concat('Param with name p',
$pNumber,
' has value: ',
$vParam/#select
)"/>
</xsl:template>
</xsl:stylesheet>
produces the wanted result:
Param with name p3 has value: 'P3-Value'
Param with name p2 has value: 'P2-Value'
Explanation:
The expression document('') selects the document node of the current XSLT stylesheet.
A limitation is that the current XSLT stylesheet must have (be accessible via) a URI (such as residing at a given file and accessible by its filename) -- the above code doesn't produce a correct result if the stylesheet is dynamically generated (a string in memory).
In libxslt the thing is possible through dyn:evaluate extension. Here is the description. There is total of 3 processors mentioned which are said to support this function:
Xalan-J from Apache (version 2.4.1) and
4XSLT, from 4Suite. (version 0.12.0a3)
libxslt from Daniel Veillard et al. (version 1.0.19)
A portable workaround. If you control both the application and the stylesheet, you should pass the parameters as an xml document. Most processors give the option to make parameter a node-set. For example in MSXML I did it using:
xslProc.addParameter("params", xmlParams)
where xslProc is of processor type, created from "Msxml2.XSLTemplate.6.0" using createProcessor method and xmlParams is DomDocument. Inside the stylesheet I was accesing my parameters using something like that:
<xsl:variable name="value">
<xsl:value-of select="$params//*[name() = concat('p', $pnumber)]" />
</xsl:variable>
If the processor does not support node-set external parameters, one may always combine the parameters with the data in one xml document. This works well in memory. If access to external files is possible, one may use document('params.xml') syntax to access the parameters stored in a separate file.
I was also looking for a possibility to parse xml string and have a node-set of it, but it is seems to be available only as an extension in some xslt 2.0 parsers. I wanted 1.0 solution.

XSL Reuse? YES! But: Element must not contain an xsl:import element! :-(

I am using a heavy stylesheet with a lot of recurring transformations, so I thought it would be smart to reuse the same chunks of code, so I would not need to make the same changes at a bunch of different places. So I discovered , but -alas- it won't allow me to do it. When trying to run it in Sonic Workbench I get the following error:
An xsl:for-each element must not contain an xsl:import element
This is my stylesheet code:
<xsl:template match="/">
<InboundFargoMessage>
<EdiSender>
<xsl:value-of select="TransportInformationMessage/SenderId"/>
</EdiSender>
<EdiReceiver>
<xsl:value-of select="TransportInformationMessage/RecipientId"/>
</EdiReceiver>
<EdiSource>PORLOGIS</EdiSource>
<EdiDestination>FARGO</EdiDestination>
<Transportations>
<xsl:for-each select="TransportInformationMessage/TransportUnits/TransportUnit">
<xsl:import href="TransportCDMtoFDM_V0.6.xsl"/>
</xsl:for-each>
<xsl:for-each select="TransportInformationMessage/Waybill/TransportUnits/TransportUnit">
<xsl:import href="TransportCDMtoFDM_V0.6.xsl"/>
</xsl:for-each>
</Transportations>
</InboundFargoMessage>
</xsl:template>
</xsl:stylesheet>
I will leave out the child xsl-sheets for now, as the problem appears to be happening at the base.
If I cannot use xsl:import, is there any option of reuse?
If I cannot use xsl:import, is there
any option of reuse?
You can use <xsl:import>.
All <xsl:import> elements must be the first element children of <xsl:stylesheet>
As an alternative, an <xsl:include> element has to be globally defined (a child of <xsl:stylesheet>) but can be preceded by any other xslt instruction that can be placed globally.
You need to be aware of and understand well the rules of using these two XSLT instructions. I'd recommend reading a good book on XSLT.
The main unit of reusability in XSLT is the template (<xsl:template>).
The importing stylesheet can use (via <xsl:call-template> or <xsl:apply-templates>) any template that is defined in any imported stylesheet.
Each of included XSL files should contain a template(s).
The main file includes the others in the beginning and then calls the templates with call-template or apply-templates from various places.
Thanks for all the suggestions, which were somewhat helpful, but allow me formulate a complete answer. As suggested, the answer to the question of re-use lies in the xsl:templates. Templates can be defined by enclosing them within . Then, whereever necessary, they can be summoned by adding a element. Also, they can be put into separate xsl sheets, as long as they are imported at the top of the parent xsl sheet.
Thus, the solution to my questions looks as follows:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:far="http://www.itella.com/fargo/fargogate/" xmlns:a="http://tempuri.org/XMLSchema.xsd" xmlns:p="http://tempuri.org/XMLSchema.xsd">
<xsl:import href="TransportCDMtoFDM_V0.6.xsl"/>
<xsl:template match="/">
<InboundFargoMessage>
<EdiSender>
<xsl:value-of select="TransportInformationMessage/SenderId"/>
</EdiSender>
<EdiReceiver>
<xsl:value-of select="TransportInformationMessage/RecipientId"/>
</EdiReceiver>
<EdiSource>PORLOGIS</EdiSource>
<EdiDestination>FARGO</EdiDestination>
<Transportations>
<xsl:for-each select="TransportInformationMessage/TransportUnits/TransportUnit">
<xsl:call-template name="transport"/>
</xsl:for-each>
<xsl:for-each select="TransportInformationMessage/Waybill/TransportUnits/TransportUnit">
<xsl:call-template name="transport"/>
</xsl:for-each>
</Transportations>
</InboundFargoMessage>
</xsl:template>
Where the file 'TransportCDMtoFDM_V0.6.xsl' contains the template called "transport".
There is just one problem left: Using templates, all the nodes mentioned within the template are used, even if they are empty. So the remaining question is how to leave out the empty nodes?