XSL: Select current node - xslt

I'm trying to go through a set of nodes thanks to a for-each, and I'd like to select the current node in a variable when the parameters in this node validate few conditions.
The problem is that it seems to select the value in the node, and not the actual node itself. And I'd like to be able to use this variable as a nodeset later.
XML ($parameterFile) :
<?xml version="1.0" encoding="UTF-8"?>
<Cases>
<Case>
<Rule LineNumber="10" PartNumber="FT40X40"/>
<Template>
<Drawings>
test
</Drawings>
</Template>
</Case>
<Case>
<Rule LineNumber="10" PartNumber="FT40X46k"/>
<Template>
<Drawings>
test2
</Drawings>
</Template>
</Case>
XSLT :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="xs"
version="2.0">
<xsl:variable name="GoodCase">
<xsl:for-each select="$parameterFile/Cases/Case">
<xsl:choose>
<xsl:when test="(Rule/#LineNumber = $markerNbLines) and
(Rule/#PartNumber = $markerPartNumber)">
<xsl:value-of select="current()"/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="$parameterFile/$GoodCase/Template/Drawings">
test
</xsl:for-each>
Output expected :
test
Thanks !

The pb [problem?] is that it seems to select the value in the node, and not the
actual node itself.
Well, you are using:
<xsl:value-of select="current()"/>
so that's only expected. You could use <xsl:copy-of>to write the entire node. However, the resulting variable would hold a result-tree-fragment, not a node-set, so if:
And I'd like to be able to use this variable as a nodeset later.
you would be much better off populating your variable in this manner (untested, because no testing is possible with partial code):
<xsl:variable name="GoodCase" select="$parameterFile/Cases/Case[Rule/#LineNumber=$markerNbLines and Rule/#PartNumber=$markerPartNumber]"/>
This stores only a reference to the original nodes and thus is a node-set by itself.

Related

Using XSLT 2.0 to parse the values of multiple attributes into an array-like structure

I'd like to be able to select all the attributes of a certain type in a document (for example, //#arch) and then take that node set and parse the values out into second node set. When I say "parse", in specific I mean I want to turn a node set like this:
arch="value1;value2;value3"
arch="value1:value4"
into a node set like this:
arch="value1"
arch="value2"
arch="value3"
arch="value1"
arch="value4"
or something like that; I want to get the individual values out of the attributes and into their own node.
If I can get it to that state, I've got plenty of methods for sorting and duplicate removal, after which I'd be using the finished node set for a publishing task.
I'm not so much looking for an tidy answer here as an approach. I know that XSLT cannot do dynamic arrays, but that's not the same as not being able to do something like dynamic arrays or something that mimics the important part of the functionality.
One thought that has occurred to me is that I could count the nodes in the first node set, and the number of delimiters, calculate the number of entries that the second node set would need and create it (somehow), and use the substring functions to parse out the first node set into the second node set.
There's usually a way working around XSLT's issues; has anyone worked their way around this one before?
Thanks for any help,
Jeff.
I think what you're looking for is a sequence. A sequence can be either nodes or atomic values (see http://www.w3.org/TR/xslt20/#constructing-sequences).
Here's an example showing the construction of a sequence and then iterating over it. The sequence is the atomic values from #arch, but it could also be nodes.
XML Input
<doc>
<foo arch="value1;value2;value3"/>
<foo arch="value1:value4"/>
</doc>
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="archSequence" as="item()*">
<xsl:for-each select="//#arch">
<xsl:for-each select="tokenize(.,'[;:]')">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/*">
<sequence>
<xsl:for-each select="$archSequence">
<item><xsl:value-of select="."/></item>
</xsl:for-each>
</sequence>
</xsl:template>
</xsl:stylesheet>
XML Output
<sequence>
<item>value1</item>
<item>value2</item>
<item>value3</item>
<item>value1</item>
<item>value4</item>
</sequence>
Example of a sequence of elements (same output):
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="archSequence" as="element()*">
<xsl:for-each select="//#arch">
<xsl:for-each select="tokenize(.,'[;:]')">
<item><xsl:value-of select="."/></item>
</xsl:for-each>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/*">
<sequence>
<xsl:for-each select="$archSequence">
<xsl:copy-of select="."/>
</xsl:for-each>
</sequence>
</xsl:template>
</xsl:stylesheet>
You can use the tokenize function in a for expression to get a sequence of the separate values, then create an attribute node for each one. However, since XSLT doesn't let you create a bare attribute node with no element parent, you'll have to use a trick like this:
<xsl:variable name="archElements">
<xsl:for-each select="for $attr in $initialNodeSet
return tokenize($attr, '[:;]')">
<dummy arch="{.}" />
</xsl:for-each>
</xsl:variable>
and then $archElements/dummy/#arch should be the set of separated arch attribute nodes that you require.
Complete example:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes" />
<xsl:template match="/">
<xsl:variable name="inputData">
<a arch="value1;value2;value3" />
<a arch="value1:value4" />
</xsl:variable>
<!-- create an example node set containing the two arch attribute nodes -->
<xsl:variable name="initialNodeSet" select="$inputData/a/#arch" />
<!-- tokenize and generate one arch attribute node for each value -->
<xsl:variable name="archElements">
<xsl:for-each select="for $attr in $initialNodeSet
return tokenize($attr, '[:;]')">
<dummy arch="{.}" />
</xsl:for-each>
</xsl:variable>
<!-- output to verify -->
<r>
<xsl:for-each select="$archElements/dummy/#arch">
<c><xsl:copy-of select="."/></c>
</xsl:for-each>
</r>
</xsl:template>
</xsl:stylesheet>
When run over any input document (the content is ignored) this produces
<?xml version="1.0" encoding="UTF-8"?>
<r>
<c arch="value1"/>
<c arch="value2"/>
<c arch="value3"/>
<c arch="value1"/>
<c arch="value4"/>
</r>

XSL condition to check if node exists

I want to check if in my XML exists node that has type attribute containing string type_attachment_.
Is it a correct way to check it?
<xsl:if test="count(*[contains(#Type, 'type_attachment_')]) > 0">
something
</xsl:if>
I don't know how nested can this node be. It can be for example as simple as that:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl"?>
<hello-world>
<greeter>
<dsdsds>An XSLT Programmer
<greeting type = 'type_attachment_'>Hello, World!
</greeting>
</dsdsds>
</greeter>
</hello-world>
but can also contain this node nested in different other elements.
Expressions that match existing nodes are truthy. Expressions that do not match any nodes are falsy.
Therefore, you don't need to count the set of nodes returned. Simply test to see if anything matches.
<xsl:if test="*[contains(#Type, 'type_attachment')]">
something
</xsl:if>
Find out an example:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:param name="filt">
<filters>
<ritem type="type_attachment_" relateditemnumber="8901037"/>
<ritem relateditemnumber="8901038"/>
<ritem type="type_attachment_" relateditemnumber="8901039"/>
<ritem relateditemnumber="8901040"/>
</filters>
</xsl:param>
<xsl:template match="/">
<xsl:for-each select="$filt/filters/ritem[#type='type_attachment_']">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
OUTPUT:
<ritem type="type_attachment_" relateditemnumber="8901037"/>
<ritem type="type_attachment_" relateditemnumber="8901039"/>

XPath relative path in expression

I am in 'group' node. From it, I want to find such 'item' node, that has 'id' attribute equals to current's 'group' node 'ref_item_id' attribute value. So in my case, by being in 'group' node B, I want 'item' node A as output. This works:
<xsl:value-of select="preceding-sibling::item[#id='1']/#description"/>
But this doesn't (gives nothing):
<xsl:value-of select="preceding-sibling::item[#id=#ref_item_id]/#description"/>
When I type:
<xsl:value-of select="#ref_item_id"/>
I have '1' as result. So this attribute is for sure accessible, but I can't find path to it from XPath expression above. I tried many '../' combinations, but couldn't get it work.
Code to test: http://www.xmlplayground.com/7l42fo
Full XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item description="A" id="1"/>
<item description="C" id="2"/>
<group description="B" ref_item_id="1"/>
</root>
Full XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="group">
<xsl:value-of select="preceding-sibling::item[#id=#ref_item_id]/#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This has to do with context. As soon as you enter a predicate, the context becomes the node currently being filtered by the predicate, and no longer the node matched by the template.
You have two options - use a variable to cache the outer scope data and reference that variable in your predicate
<xsl:variable name='ref_item_id' select='#ref_item_id' />
<xsl:value-of select="preceding-sibling::item[#id=$ref_item_id]/#description"/>
or make use of the current() function
<xsl:value-of select="preceding-sibling::item[#id=current()/#ref_item_id]/#description"/>
Your expression searches for an item whose id attribute matches its own ref_item_id. You need to capture the current ref_item_id in an xsl:variable and refer to that xsl:variable in the expression.
One more possible solution using xsl:key
<xsl:key name="kItemId" match="item" use="#id" />
<xsl:template match="root">
<xsl:for-each select="group">
<xsl:value-of select="key('kItemId', #ref_item_id)[1]/#description"/>
</xsl:for-each>
</xsl:template>
Looking at the XML, if I assume that you have <item> and <group> as siblings and in any order.
Then a sample input XML would look like the following.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item description="A" id="1"/>
<item description="C" id="2"/>
<group description="B" ref_item_id="1"/>
<item description="D" id="1"/>
<group description="E" ref_item_id="2"/>
</root>
Now, if the goal is to extract the description of all the <item> nodes whose id is matching with corresponding <group> *nodes ref_item_id*. Then we can simply loop over only such <item> nodes and get their description.
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="//item[(./#id=following-sibling::group/#ref_item_id) or (./#id=preceding-sibling::group/#ref_item_id)]">
<xsl:value-of select="./#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Since you say that nodes are having unique id and all nodes are placed before nodes.
I would recommend you to use the following XSL and loop over specific nodes instead of nodes.
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="//item[./#id=following-sibling::group/#ref_item_id]">
<xsl:value-of select="./#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Looping over distinct values

Given a variable which returns a list of distinct States using the distinct-values() function, is there a way to tokenize the variable in a for-each loop?
<States>
<State>AL</State>
<State>AL</State>
<State>NM</State>
</States>
The following variable returns AL and NM, but I can't iterate over it using for-each. Is there a way around this?
<xsl:variable name="FormStates" select="distinct-values(States/State)"/>
<xsl:for-each select="$FormStates">
XSLT 2.0 ok.
The distinct-values() function returns a sequence of values which you should be able to iterate over. The result is so to speak "tokenized".
fn:distinct-values('AL', 'AL', 'NL') returns the sequence ('AL', 'NL').
If you output the variable with xsl:value-of it will return the string "AL NL" only because the default sequence separator for xsl:value-of is a single space character. This is something you could change with the #separator attribute:
Input
<?xml version="1.0" encoding="UTF-8"?>
<States>
<State>AL</State>
<State>AL</State>
<State>NM</State>
</States>
XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<xsl:variable name="FormStates" select="distinct-values(States/State)"/>
<xsl:comment>xsl:value-of</xsl:comment>
<xsl:value-of select="$FormStates" separator=":"/>
<xsl:comment>xsl:for-each</xsl:comment>
<xsl:for-each select="$FormStates">
<xsl:value-of select="."/>
<xsl:text>:</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output
<?xml version="1.0" encoding="UTF-8"?>
<!--xsl:value-of-->
AL:NM
<!--xsl:for-each-->
AL:NM:
Here's an XSLT 1.0 solution that I've used in the past.
<xsl:template match="/">
<ul>
<xsl:for-each select="//State[not(.=preceding::*)]">
<li>
<xsl:value-of select="."/>
</li>
</xsl:for-each>
</ul>
</xsl:template>
Returns:
<ul xmlns="http://www.w3.org/1999/xhtml">
<li>AL</li>
<li>NM</li>
</ul>
In theory it should work; are you sure the XPath given to the distinct-values function is correct? The code you've given requires that the States element is a sibling of the forms element.
You could insert <xsl:value-of select="count($FormStates)"> immediately after the variable declaration to confirm if it is being set properly.

How can I select nodes from a tree whose markup is stored in a variable?

Consider the following XSLT script:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1"/>
<xsl:variable name="stringmap">
<map>
<entry><key>red</key><value>rot</value></entry>
<entry><key>green</key><value>gruen</value></entry>
<entry><key>blue</key><value>blau</value></entry>
</map>
</xsl:variable>
<xsl:template match="/">
<!-- IMPLEMENT ME -->
</xsl:template>
</xsl:stylesheet>
I'd like this script to print redgreenblue.
Is there any way to treat the XML markup which is stored in the stringmap variable as a document of its own which I can run XPath queries on? I'm basically looking for something like
<xsl:for-each select="document($stringmap)/map/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
(except that the document() function expects an URI).
Motivation: I have various long <xsl:choose> elements which map a given string to another string. I'd like to replace all those with a single template which takes a 'map' argument (which is a simple XML document). My hope is that I can then replace the <xsl:choose> with a simple statement like <xsl:value-of select="$stringmap/map/entry/value[../key='$givenkey']"/>
I'm using XSLT 1.0 using xsltproc.
You're almost right, using document('') will allow you to process node sets inside the current stylesheet:
<xsl:for-each select="document('')/xsl:stylesheet/xsl:variable[#name='stringmap']/map/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
It's not necessary to define the map node set as a variable in this case:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet xmlns:data="some.uri" version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<data:map>
<entry><key>red</key><value>rot</value></entry>
<entry><key>green</key><value>gruen</value></entry>
<entry><key>blue</key><value>blau</value></entry>
</data:map>
<xsl:template match="/">
<xsl:for-each select="document('')/xsl:stylesheet/data:map/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
If you do not use xsl:variable as a wrapper, you must remember that a top level elements must have a non null namespace URI.
In XSLT 2.0 it would've been possible to just iterate over the content in a variable:
<xsl:variable name="map">
<entry><key>red</key><value>rot</value></entry>
<entry><key>green</key><value>gruen</value></entry>
<entry><key>blue</key><value>blau</value></entry>
</xsl:variable>
<xsl:template match="/">
<xsl:for-each select="$map/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
</xsl:template>
A posting by M. David Peterson just taught me how to make this work:
It's not necessary to have an <xsl:variable> for this case. Instead, I can embed the data document directly into the XSL stylesheet (putting it into a namespace for sanity) and then select elements from that. Here's the result:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="uri:map">
<xsl:output method="text" encoding="iso-8859-1"/>
<map:colors>
<entry><key>red</key><value>rot</value></entry>
<entry><key>green</key><value>gruen</value></entry>
<entry><key>blue</key><value>blau</value></entry>
</map:colors>
<xsl:template match="/">
<xsl:for-each select="document('')/*/map:colors/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This generates the expected output redgreenblue.
The trick is to use document('') to get a handle to the XSLT document itself, then * to get into the toplevel xsl:stylesheet element and from there I can access the color map.