IVar
IVar

Reputation: 73

Get specific node from XML with XPath

I have a XML-document like this:

<food>
<fruit>
    <apple>
        <product>
            <title>Apple 1</title>
            <price>7</price>
        </product>

        <product>
            <title>Apple 2</title>
            <price>4</price>
        </product>
    </apple>

    <grapes>
        <product>
            <title>Red grapes </title>
            <price>4</price>
        </product>
        <product>
            <title>Green grapes</title>
            <price>6</price>
        </product>
    </grapes>
</fruit>
<drink>
    <water>
        <product>
            <title>Water 1</title>
            <price>1</price>
        </product>
        <product>
            <title>Water 2</title>
            <price>6</price>
        </product>
    </water>
    <soda>
        <product>
            <title>Coca-Cola</title>
            <price>10</price>
        </product>
        <product>
            <title>Sprite</title>
            <price>4</price>
        </product>
    </soda>
</drink>
</food>

I have an XML structure like this.

I want to pick up the product name and price of all products that cost above 5. How do I write an XPath expression that does this?

Upvotes: 1

Views: 4522

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243459

Use:

  /*/*/*/product[price > 5]/title 
|
  /*/*/*/product/price[. > 5]

This selects the union of:

  1. All title elements that have a product parent whose price child's string value when treated as a number is greater than 5 that is a great-grandson of the top element of the XML document.

  2. All price elements whose string value when treated as a number is greater than 5, and whose parent is a product that is a great-grandson of the top element of the XML document.

The elements selected are provided in a node-set (typically) in document order, which means that a title and its corresponding price are next to each other in whatever collection of nodes is produced by the corresponding XPath API.

In XSLT one would use a very simple transformation like this:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="product[price > 5]">
  Name: <xsl:value-of select="title"/>
  <xsl:text>, price </xsl:text>
  <xsl:value-of select="price"/>
 </xsl:template>
 <xsl:template match="text()"/>
</xsl:stylesheet>

when applied on the provided XML document:

<food>
<fruit>
    <apple>
        <product>
            <title>Apple 1</title>
            <price>7</price>
        </product>

        <product>
            <title>Apple 2</title>
            <price>4</price>
        </product>
    </apple>

    <grapes>
        <product>
            <title>Red grapes </title>
            <price>4</price>
        </product>
        <product>
            <title>Green grapes</title>
            <price>6</price>
        </product>
    </grapes>
</fruit>
<drink>
    <water>
        <product>
            <title>Water 1</title>
            <price>1</price>
        </product>
        <product>
            <title>Water 2</title>
            <price>6</price>
        </product>
    </water>
    <soda>
        <product>
            <title>Coca-Cola</title>
            <price>10</price>
        </product>
        <product>
            <title>Sprite</title>
            <price>4</price>
        </product>
    </soda>
</drink>
</food>

the wanted, correct result is produced:

  Name: Apple 1, price 7
  Name: Green grapes, price 6
  Name: Water 2, price 6
  Name: Coca-Cola, price 10

It is even better to specify the price limit as a global parameter that is externally passed to the transformation.

In this case an XSLT 2.0 transformation is a bit simpler:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pLimit" select="5"/>

 <xsl:template match="product[price > $pLimit]">
  Name: <xsl:value-of select="title"/>
  <xsl:text>, price </xsl:text>
  <xsl:value-of select="price"/>
 </xsl:template>
 <xsl:template match="text()"/>
</xsl:stylesheet>

A corresponding XSLT 1.0 transformation is:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pLimit" select="5"/>

 <xsl:template match="product">
  <xsl:if test="price > $pLimit">
      Name: <xsl:value-of select="title"/>
      <xsl:text>, price </xsl:text>
      <xsl:value-of select="price"/>
  </xsl:if>
 </xsl:template>
 <xsl:template match="text()"/>
</xsl:stylesheet>

Upvotes: 2

fflorent
fflorent

Reputation: 1646

I suppose that your document ends with </food>. Is that what you want ?

//product[price > 5]

EDIT:

If you do not want the <product> tag, you can do that :

//product[price > 5]/*

However, I find this last solution less convenient, since we do not delimit the products any more.

Upvotes: 3

Related Questions