XSLT and XPATH to find the correct element using data from sibling elements - xslt

I'm having trouble locating the correct syntax for finding the value of an element based on sibling element data.
Given that I have the following XML:
<files>
<file>
<location>location1.txt</location>
<metadata>
<foo>some value 1</foo>
</metadata>
</file>
<file>
<location>location2.txt</location>
<metadata>
<foo>some value 2</foo>
</metadata>
</file>
<file>
<location>location3.txt</location>
<metadata />
</file>
</files>
What is the correct XPATH expression to find the correct location value if I'm looking for only the location where file/metadata/foo = "some value 2"? I can't seem to find the right syntax to locate it if I'm dependent on data in a sibling, or the children of that sibling.
I've found a solution where I use xslt to iterate through the file elements, and find the appropriate value that way, but it's not very elegant.
<xsl:variable name="profileAlias">
<xsl:for-each select="files/file">
<xsl:if test="metadata/foo='some value 2'">
<xsl:value-of select="location">
</xsl:if>
</xsl:for-each>
</xsl:variable>

If you need to take one only value you can try this one, if this is elegent for you :D
<xsl:value-of select="files/file/location[following-sibling::metadata/foo/text()='some value 2']"/>
files/file/location - takes location attribute
[following-sibling::metadata/foo/ - which has next attribute metadata/foo/
text()='some value 2'] - with text equal 'some value 2'

Try
/files/file[metadata/foo='some value 2']/location

Related

Setting a number as element name in XSLT 1.0

In my xslt I have a variable 'number'. I want to use this value as an element's name in my final xml output. How can I achieve this?
XSLT
<input>
<xsl:variable name="number" value="658976"/>
<xsl:element name="{$number}">Hello</xsl:element>
</<input>
Expected Output
<input>
<658976>Hello</658976>
</input>
An XML element's name cannot start with a digit: https://www.w3.org/TR/2008/REC-xml-20081126/#NT-NameStartChar

XSLT: copy few nodes only of xml to a variable

Is there a way to copy 2 or 3 nodes of XML to a variable using XSLT? I'm looking for nodes and not the node values.
My sample XML is:
<node1>
<node2>
<node3>abc</node3>
<node4>def</node4>
</node2>
</node1>
<node1>
<node2>
<node3>123</node3>
<node4>456</node4>
</node2>
</node1>
And my XSLT sample is:
<xsl:for-each select="/node1/node2">
<xsl:if test="current()/node4 ! = '456'">
<xsl:copy-of select="./node3" />
<xsl:copy-of select="./node4" />
</xsl:if>
</xsl:foreach>
The problem with this is that I'm getting node4 everytime as the first node of the XML instead of current one. On node3 I'm getting the current one and there's no problem.
Your problem may be caused by the RTF(Resulting Tree Fragment) problem of XSLT-1.0:
A variable cannot contain a nodeset (but only a RTF)
I explained this problem in this SO answer.
RTFs cannot be queried by XPath-1.0 expressions, so they are only useful in a very limited subset of situations.
One solution would be using the newer XSLT-2.0.
It may help you to just select the nodes that you want all at once.
<xsl:variable name="sample">
<xsl:copy-of select="/node1/node2[node4!='456']/*[name()='node3' or name()='node4']"/>
</xsl:variable>

How can I select a node if there is a choice for an element without repeating the complete path

I have the following XSLT template
<xsl:template match="/root">
<r>
<xsl:value-of select="transport/route[not(enddate)]/ship|car/fuel/litres"/>
</r>
</xsl:template>
Which I use to translate the following example XML
<root>
<transport>
<route>
<ship> <!-- this can be a car -->
<fuel>
<litres>
42
</litres>
</fuel>
</ship>
</route>
<route>
<enddate>2015-08-21</enddate>
<car>
<fuel>
<litres>
42
</litres>
</fuel>
</car>
</route>
</transport>
</root>
Notice that under route I can have a ship OR a car.
I can't find a way to reduce the xpath exression to only cover for the choice between ship and car
<xsl:value-of select="transport/route[not(enddate)]/ship|car/fuel/litres"/>
To make the above work I have to change it to this:
<xsl:value-of
select="transport/route[not(enddate)]/ship/fuel/litres |
transport/route[not(enddate)]/car/fuel/litres"/>
but I that feels as I'm copying to much of the expression and violates my DRY nature. I tried transport/route[not(enddate)][car|ship]/fuel/litres but without success.
What should the expression be if I want to get rid of the duplication of the xpath?
One way to write your expression is as follows:
<xsl:value-of select="transport/route[not(enddate)]/*[self::car or self::ship]/fuel/litres"/>
Alternatively, if a route element can contain only only child element (whether ship, car or something else, you could also write this:
<xsl:value-of select="transport/route[not(enddate)][ship or car]/*/fuel/litres"/>
Using an XSLT 2.0 or 3.0 processor you can use <xsl:value-of
select="transport/route[not(enddate)]/(ship | car)/fuel/litres"/> but be aware that value-of with a version="2.0" or version="3.0" stylesheet outputs a sequence of values of the selected sequence and not the first value in the selected sequence like version="1.0" does.

Xpath test if is numeric OR valid numeric expression

It is quite simple to find out if a node value is numeric by for example:
<xsl:if test="string(number(myNode)) != 'NaN'">
<!-- myNode is numeric -->
</xsl:if>
But, what if myNode contains a fraction or other valid numeric expression? What is the best way to find that out?
Example XML:
<data id="1">34</data>
<data id="2">52.3</data>
<data id="3">5/9</data>
<data id="4">10*20</data>
<data id="5">foo</data>
Example XSL:
<xsl:for-each select="//data">
<xsl:if test="string(number(.)) != 'NaN'">
<isNumeric><xsl:value-of select="#id"/></isNumeric>
</xsl:if>
</xsl:if>
Will generate the output:
<isNumeric>1</isNumeric>
<isNumeric>2</isNumeric>
But what is the easiest way to write the xpath expression (or xsl code) to generate the following output:
<isNumeric>1</isNumeric>
<isNumeric>2</isNumeric>
<isNumeric>3</isNumeric>
<isNumeric>4</isNumeric>
...since data id 3 and 4 also are valid numeric expressions.
It is ok to assume the node value does not contain any spaces as this is controlled by the schema used.
Assuming you don't need to validate your expressions so 4/0 or even 4/+/9 are OK then
<xsl:if test="translate(.,'0123456789.+-/* ','')=''">
<isNumeric><xsl:value-of select="#id"/></isNumeric>
</xsl:if>

xpath for selecting elements having a child value specified in a list

Consider the following case:
<xsl:variable name="list">
<row>
<webid>2</webid>
</row>
<row>
<webid>3</webid>
</row>
<row>
<webid>4</webid>
</row>
</xsl:variable>
<!--pseudo code, not sure if it will work in real time-->
<xsl:variable name="addr" select="addresses/item[not(id=$list/webid)]"/>
I want to filter those addresses items which have an id not in the $list webid collection. Will the expression I posted do? Please advice.
In XSLT 1.0 a variable declaration like that is a result tree fragment, not a node set, and the only thing you can do with it is copy-of or value-of - you can't navigate into it with XPath expressions. You need to either
Use an extension function such as exslt:node-set to turn the RTF into a real node set or
Use a trick with document('') to access the style sheet itself as an XML document
Option 2 only works in cases like your example, where the variable value is static XML. If you have a variable whose value is built using xsl: commands or attribute value templates, e.g.
<xsl:variable name="allNames">
<xsl:for-each select="name">
<person name="{.}" />
</xsl:for-each>
</xsl:variable>
then you'd have to use a node-set function. The document('') approach would give you the actual xsl:for-each element and a literal value of {.} for the name attribute, rather than the result of evaluating it.
Option 1
Add xmlns:exslt="http://exslt.org/common" exclude-result-prefixes="exslt" to your <xsl:stylesheet> tag, then use
<xsl:variable name="addr" select="addresses/item[
not(id=exslt:node-set($list)/row/webid)]"/>
Option 2
<xsl:variable name="addr" select="addresses/item[
not(id=document('')//xsl:variable[name='list']/row/webid)]"/>
I put not(addresses/item[id=$list/webid])