Longer node in XPath - xpath

I'd like to use XPath to retrieve the longer of two nodes.
E.g., if my XML is
<record>
<url1>http://www.google.com</url1>
<url2>http://www.bing.com</url2>
</record>
And I do document.SelectSingleNode(your XPath here)
I would expect to get back the url1 node. If url2 is longer, or there is no url1 node, I'd expect to get back the url2 node.
Seems simple but I'm having trouble figuring it out. Any ideas?

This works for me, but it is ugly. Cannot you do the comparison outside XPath?
record/*[starts-with(name(),'url')
and string-length(.) > string-length(preceding-sibling::*[1])
and string-length(.) > string-length(following-sibling::*[1])]/text()

<xsl:for-each select="*">
<xsl:sort select="string-length(.)" data-type="number"/>
<xsl:if test="position() = last()">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
Even works in XSLT 1.0!

Use this single XPath expression:
/*/*[not(string-length(preceding-sibling::*|following-sibling::*)
>
string-length()
)
]
XSLT - based verification:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:copy-of select=
"/*/*[not(string-length(preceding-sibling::*|following-sibling::*)
>
string-length()
)
]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<record>
<url1>http://www.google.com</url1>
<url2>http://www.bing.com</url2>
</record>
the Xpath expression is evaluated and the result of this evaluation (the selected element) is copied to the output:
<url1>http://www.google.com</url1>

Related

Constructing variable with template in XSLT and then applying xpath

I am using following xslt
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.test.com/services/test/test/v1">
<xsl:output method="xml" encoding="UTF-8"
omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/">
<xsl:variable name="mytree">
<xsl:call-template name="myvariable">
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="count($mytree/foos/foo) > 1">
<xsl:copy-of select="$mytree"/>
</xsl:when>
<xsl:otherwise>
<error>test</error>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="myvariable">
<foos>
<foo>bar1</foo>
<foo>bar2</foo>
<foo>bar3</foo>
<foo>bar4</foo>
</foos>
</xsl:template>
</xsl:stylesheet>
When i use above xslt it should be following output
<foos xmlns="http://www.test.com/services/test/test/v1">
<foo>bar1</foo>
<foo>bar2</foo>
<foo>bar3</foo>
<foo>bar4</foo>
</foos>
but it is
<error xmlns="http://www.test.com/services/test/test/v1">test</error>
when i remove the xmlns="http://www.test.com/services/test/test/v1" output is proper. Not sure what is happening?
Well, with any XML, whether constructed inside of your XSLT or read from a source, if you have elements in a certain namespace, then, to select them with XPath in XSLT, in XSLT 2 you have two options, either use xpath-default-namespace="http://www.test.com/services/test/test/v1" (e.g. <xsl:when test="count($mytree/foos/foo) > 1" xpath-default-namespace="http://www.test.com/services/test/test/v1">) or bind the namespace to a prefix (e.g. <xsl:when xmlns:v1="http://www.test.com/services/test/test/v1" test="count($mytree/v1:foos/v1:foo) > 1">).
You can use these approaches on an ancestor element, for instance the root element of the stylesheet, if it does not interfere with other selections you want to make.
You have to specify qualified element names in your XPath expression to address the foos and foo elements in your default namespace http://www.test.com/services/test/test/v1:
Register the default namespace once more with a namespace prefix (e.g. myns): xmlns:myns="http://www.test.com/services/test/test/v1"
Use that namepace prefix in your XPath expressions to address nodes in that namespace (e.g. myns:foos/myns:foo).
Add exclude-result-prefixes="myns" to suppress the myns prefix in your result document.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.test.com/services/test/test/v1"
xmlns:myns="http://www.test.com/services/test/test/v1"
exclude-result-prefixes="myns">
…
<xsl:template match="/">
…
<xsl:choose>
<xsl:when test="count($mytree/myns:foos/myns:foo) > 1">
<xsl:copy-of select="$mytree"/>
</xsl:when>
<xsl:otherwise>
<error>test</error>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
…
</xsl:stylesheet>
see XSLT Fiddle
If you only had an XSLT 1.0 processor at hand, you would need the EXSLT node-set function to access the $mytree variables from the result tree:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.test.com/services/test/test/v1"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
xmlns:myns="http://www.test.com/services/test/test/v1"
exclude-result-prefixes="myns">
…
<xsl:template match="/">
…
<xsl:choose>
<xsl:when test="count(exsl:node-set($mytree)/myns:foos/myns:foo) > 1">
<xsl:copy-of select="$mytree"/>
</xsl:when>
<xsl:otherwise>
<error>test</error>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
…
</xsl:stylesheet>
Use code for remove namespace
<xsl:template match="#*[namespace-uri() = 'http://www.test.com/services/test/test/v1']"/>

xslt Merge children of 2 parents and Store in a variable

I receive an xml input like this:
<root>
<Tuple1>
<child11></child11>
<child12></child12>
<child13></child13>
</Tuple1>
<Tuple1>
<child11></child11>
<child12></child12>
</Tuple1>
<Tuple2>
<child21></child21>
<child22></child22>
</Tuple2>
<Tuple2>
<child21></child21>
<child22></child22>
<child23></child23>
</Tuple2>
</root>
How can I merge the children of each Tuple1 with children of Tuple2 and store them in a variable that will be used in the rest of xslt document?
First tuple1 will be merged with first Tuple2 and second Tuple1 will be merged with 2nd Tuple2 and so on. The merged output that should be stored in variable would look like this in memory:
<root>
<Tuple1>
<child11></child11>
<child12></child12>
<child13></child13>
<child21></child21>
<child22></child22>
</Tuple1>
<Tuple1>
<child11></child11>
<child12></child12>
<child21></child21>
<child22></child22>
<child23></child23>
</Tuple1>
</root>
Is variable the best option? If we use variable, is it created once or it is created every time called?
I use xslt 3.0 so solution for any version can help.
Thanks and I appreciate your help)
Here is a minimal XSLT 3 approach:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="root">
<xsl:variable name="temp1">
<xsl:copy>
<xsl:apply-templates select="Tuple1"/>
</xsl:copy>
</xsl:variable>
<xsl:copy-of select="$temp1"/>
</xsl:template>
<xsl:template match="Tuple1">
<xsl:copy>
<xsl:copy-of select="*, let $pos := position() return ../Tuple2[$pos]/*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Online at https://xsltfiddle.liberty-development.net/bdxtqg, I have used XPath's let instead of XSLT's xsl:variable to store the position to access the specific Tuple2.

XSLT 2.0 dynamic XPATH expression

I have one XML file that I need to transform based on a mapping file with XSLT 2.0. I'm using the Saxon HE processor.
My mapping file:
<element root="TEST">
<childName condition="/TEST/MyElement/CHILD[text()='B']>/TEST/MyElement/CHILD</childName>
<childBez condition="/TEST/MyElement/CHILD[text()='B']>/TEST/MyElement/CHILDBEZ</childBez>
</element>
I have to copy the elements CHILD and CHILDBEZ plus the parent and the root elements when the text of CHILD equals B.
So with this Input:
<?xml version="1.0" encoding="UTF-8"?>
<TEST>
<MyElement>
<CHILD>A</CHILD>
<CHILDBEZ>ABEZ</CHILDBEZ>
<NotInteresting></NotInteresting>
</MyElement>
<MyElement>
<CHILD>B</CHILD>
<CHILDBEZ>BBEZ</CHILDBEZ>
<NotInteresting2></NotInteresting2>
</MyElement>
</TEST>
the desired output:
<TEST>
<MyElement>
<childName>B</childName>
<childBez>BBEZ</childBez>
</MyElement>
</TEST>
what I have so far (based on this solution XSLT 2.0 XPATH expression with variable):
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:param name="mapping" select="document('mapping.xml')"/>
<xsl:key name="map" match="*" use="."/>
<xsl:template match="/">
<xsl:variable name="first-pass">
<xsl:apply-templates mode="first-pass"/>
</xsl:variable>
<xsl:apply-templates select="$first-pass/*"/>
</xsl:template>
<xsl:template match="*" mode="first-pass">
<xsl:param name="parent-path" tunnel="yes"/>
<xsl:variable name="path" select="concat($parent-path, '/', name())"/>
<xsl:variable name="replacement" select="key('map', $path, $mapping)"/>
<xsl:variable name="condition" select="key('map', $path, $mapping)/#condition"/>
<xsl:choose>
<xsl:when test="$condition!= ''">
<!-- if there is a condition defined in the mapping file, check for it -->
</xsl:when>
<xsl:otherwise>
<xsl:element name="{if ($replacement) then name($replacement) else name()}">
<xsl:attribute name="original" select="not($replacement)"/>
<xsl:apply-templates mode="first-pass">
<xsl:with-param name="parent-path" select="$path" tunnel="yes"/>
</xsl:apply-templates>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[#original='true' and not(descendant::*/#original='false')]"/>
</xsl:stylesheet>
but the problem is that it's impossible to evaluate dynamic XPATH expressions with XSLT 2.0. Does anyone knows a workaround for that? Plus I have a problem with the mapping file. When there is only one element in it, it's not working at all.
If dynamic XPath evaluation isn't an option in your chosen processor, then generating an XSLT stylesheet is often a good alternative. In fact, it's often a good alternative anyway.
One way of thinking about this is that your mapping file is actually a program written in a very simple transformation language. There are two ways of executing this program: you can write an interpreter (dynamic XPath evaluation), or you can write a compiler (XSLT stylesheet generation). Both work well.

XSLT concatenate input from several nodes in a single output

I'm trying to work out a transformation that will process an input with several Flights with Departure and Arrival into a single output with the complete route for the flights.
Input is as follows:
<FlightTrip>
<flights>
<departureAirport>
<airportCode>LocB</airportCode>
</departureAirport>
<departureTime>2013-03-28T10:00:00.000</departureTime>
<arrivalAirport>
<airportCode>LocC</airportCode>
</arrivalAirport>
</flights>
<flights>
<departureAirport>
<airportCode>LocA</airportCode>
</departureAirport>
<departureTime>2013-03-27T15:00:00.000</departureTime>
<arrivalAirport>
<airportCode>LocB</airportCode>
</arrivalAirport>
</flights>
<flights>
<departureAirport>
<airportCode>LocC</airportCode>
</departureAirport>
<departureTime>2013-03-30T14:00:00.000</departureTime>
<arrivalAirport>
<airportCode>LocD</airportCode>
</arrivalAirport>
</flights>
</FlightTrip>
The desired output would be this:
<FullTrip>LocA LocB LocC LocD</FullTrip>
I've tried to use foreach inside the output variable but I can't get it right. I also need to sort the input based on the departure date as the Flights can be in a different order (as per the sample input).
Any ideas of how to achieve this?
Thanks a lot!
Bruno
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="FlightTrip">
<FullTrip>
<xsl:apply-templates select="flights">
<xsl:sort select="departureTime"/>
</xsl:apply-templates>
</FullTrip>
</xsl:template>
<xsl:template match="flights">
<xsl:value-of select="departureAirport/airportCode"/><xsl:text> </xsl:text>
<xsl:if test="position()=last()">
<xsl:value-of select="arrivalAirport/airportCode"/>
</xsl:if>
</xsl:template>
</xsl:transform>
Will produce:
<FullTrip>LocA LocB LocC LocD</FullTrip>
Working example
Thanks to Joepie for the enlightenment. I had to modify it a bit to get it to work in my environment, ended up using foreach as below:
<xsl:template match="/">
<xsl:variable name="locations">
<xsl:for-each select="/FlightTrip/flights">
<xsl:sort select="departureTime" order="ascending" data-type="text"/>
<xsl:value-of select="concat(departureAirport/airportCode,' - ')"/>
<xsl:if test="position() = last()">
<xsl:value-of select="arrivalAirport/airportCode"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<FullTrip>
<xsl:value-of select="$locations"/>
</FullTrip>
</xsl:template>
When applied to the example produces the output below:
<FullTrip>LocA - LocB - LocC - LocD</FullTrip>
Thanks again!

Navigate HTML table columns with XPath 1.0

Using only an XPath expression (and not in XSLT or DOM - just pure XPath), I'm trying to create a relative path from the current node (in a td) to an associated td in the same column of the same HTML table.
For example, suppose I have this type of data:
<table>
<tr> <td><a>Blue Jeans</a></td> <td><a>Shirt</a></td> </tr>
<tr> <td><span>$21.50</span></td> <td><span>$18.99</span></td> </tr>
</table>
and I'm on the a with "Blue Jeans" and want to find the price ($21.50). In XSLT, I could use the current() function to get the answer like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:apply-templates select="//a" />
</xsl:template>
<xsl:template match="a">
Name: <xsl:value-of select="."/>
Price: <xsl:value-of select="../../following-sibling::tr[1]/td[position() = count(current()/../preceding-sibling::td) + 1]" />
</xsl:template>
</xsl:stylesheet>
But the problem I'm running into is that there is no current() defined in XPath 1.0. I tried using the self:: axis, but like the "." shorthand, that only points to the "context" node, not the "current" node. The language that I'm seeing in the XPath standard suggests that XPath doesn't have a concept of "current node."
Is there perhaps another way to form this path or is this a limitation of XPath?
In XPath 1.0 you could do:
/table/tr/td/a[.='Blue Jeans']/following::td[count(../td)]/span
Of course, this assumes there is no colspan.
EDIT: The proof. This stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:param name="pProduct" select="'Blue Jeans'"/>
<xsl:template match="/">
<xsl:value-of select="/table/tr/td/a[.=$pProduct]
/following::td[count(../td)]/span"/>
</xsl:template>
</xsl:stylesheet>
Output:
$21.50
With param pProduct set to 'Shirt', output:
$18.99
Note: Of course, you need the a element in context in order to select the span element. So, with your stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="text()"/>
<xsl:template match="a">
Name: <xsl:value-of select="."/>
Price: <xsl:value-of select="following::td[count(../td)]/span" />
</xsl:template>
</xsl:stylesheet>
Output:
Name: Blue Jeans
Price: $21.50
Name: Shirt
Price: $18.99
This cannot be achieved with a single XPath 1.0 expression.
In XPath 2.0 one could write:
for $vPreceeding in count(../preceding-sibling::td)
return ../../following-sibling::tr[1]/td[$vPreceeding]

Resources