Return values based on dynamic expression in XmlStarlet

27 Views Asked by At

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
2

There are 2 best solutions below

1
Daniel Haley On BEST ANSWER

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="'&#10;'"/>
    </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()&gt;1]">
      <xsl:value-of select="'&#10;'"/>
      <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".

0
Jack Fleeting On

Another way to get to the same end result, similar to what you were trying to do:

xml sel -t -m //part2/line[@val=//part1/line/@val]/text -v ../@val -o "," -v . -n example.xml