I want to get the text element based on the values given in "part1".
In below example XML, I want Text2
and Text5
to be returned, because the values in part1
refer to 2
and 5
.
How can this be done using XmlStarlet, preferably under Windows.
I started with:
xml sel -t -m //part1/line -v @val -o "," -c ../../part2/line[@val='3']/text -n example.xml
giving:
2,<text>Text 3</text>
5,<text>Text 3</text>
Which is, of course not correct because I do want want the fixed 3
, but that should be dynamic, like:
xml sel -t -m //part1/line -v @val -o "," -c concat('../../part2/line[@val=',@val,']/text') -n example.xml
which returns, the Xml-path, and not the value which I was expecting:
2,../../part2/line[@val=2]/text
5,../../part2/line[@val=5]/text
I am expecting the next text to be returned.
2,<text>Text 2</text>
5,<text>Text 5</text>
(The <text>
and </text>
are not really needed here...)
Can the be done using XmlStarlet (preferably under Windows)?
The example.xml is:
<root>
<part1>
<line val="2"></line>
<line val="5"></line>
</part1>
<part2>
<line val="1">
<text>Text 1</text>
</line>
<line val="2">
<text>Text 2</text>
</line>
<line val="3">
<text>Text 3</text>
</line>
<line val="4">
<text>Text 4</text>
</line>
<line val="5">
<text>Text 5</text>
</line>
<line val="6">
<text>Text 6</text>
</line>
</part2>
</root>
EDIT: As always, on gets an idea after posting the question
I would appreciate an easier solution than (which produces the correct results!):
for /f "usebackq tokens=*" %f in (`xml sel -t -m //part1/line -v @val -n example.xml`) do @xml sel -t -m //part2/line[@val=%f]/text -v "%f" -o "," -v . -n example.xml
The thing to remember when using sel
in xmlstarlet is that it's used to create XSLT to do the query. This means we have current()
available to us.
Try:
xml sel -t -m "//part1/line/@val" -v "concat(.,',',//part2/line[current()=@val]/text)" -n example.xml
It produces:
2,Text 2
5,Text 5
If we add -C
to the command line, we can see the XSLT that was produced...
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common" version="1.0" extension-element-prefixes="exslt">
<xsl:output omit-xml-declaration="yes" indent="no"/>
<xsl:template match="/">
<xsl:for-each select="//part1/line/@val">
<xsl:call-template name="value-of-template">
<xsl:with-param name="select" select="concat(.,',',//part2/line[current()=@val]/text)"/>
</xsl:call-template>
<xsl:value-of select="' '"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="value-of-template">
<xsl:param name="select"/>
<xsl:value-of select="$select"/>
<xsl:for-each select="exslt:node-set($select)[position()>1]">
<xsl:value-of select="' '"/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The unfortunate thing is that there is no way to use xsl:key
which would be the most efficient way to do this query. You could however write the XSLT using xsl:key
and execute it with xmlstarlet. Although more efficient, it's a little more complex and you were looking for "easier".