415

Given an XML structure like so:

<?xml version="1.0" encoding="ISO-8859-1"?>

<bookstore>

<book>
  <title lang="eng">Harry Potter</title>
  <price>29.99</price>
</book>

<book>
  <title lang="eng">Learning XML</title>
  <price>39.95</price>
</book>

</bookstore>

How could I get the value of lang (where lang is eng in book title), for the first element?

Amal Murali
  • 75,622
  • 18
  • 128
  • 150
GurdeepS
  • 65,107
  • 109
  • 251
  • 387
  • 5
    very good link when using xpaths http://test-able.blogspot.ie/2016/04/xpath-selectors-cheat-sheet.html –  May 30 '17 at 14:35

8 Answers8

578

How could I get the value of lang (where lang=eng in book title), for the first element?

Use:

/*/book[1]/title/@lang

This means:

Select the lang attribute of the title element that is a child of the first book child of the top element of the XML document.

To get just the string value of this attribute use the standard XPath function string():

string(/*/book[1]/title/@lang)
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • I used the string method and got the value in square brackets `[attribute_value]`. Is there a way to get the attribute value without the square brackets? – Abhishek Asthana Mar 05 '14 at 16:34
  • 3
    @AbhishekAsthana, The result of evaluating the XPath expression produces exactly the string value of the `lang` attribute. If the attribute doesn't contain any square brackets, they will not be part of the result of the evaluation of the XPath expression. My guess is that these are added by an (inappropriate) tool you are using. – Dimitre Novatchev Mar 05 '14 at 17:00
  • 6
    yeah i figured out the problem..that's just how soapUI displays it but those brackets are not used when i use the xpath value. I have see this a lot of time. The problem is not with the tool..its between the chair and the keyboard. – Abhishek Asthana Mar 05 '14 at 18:51
  • What is returned if I do not use the string()? I do not understand the difference between the 2 answers you provided. What exactly is returned in first situation and what is returned in second? – Koray Tugay Oct 22 '14 at 03:48
  • 7
    @KorayTugay, the XPath expression `/*/book[1]/title/@lang` **selects** a node-set of 0 or more attribute nodes, while the XPath expression `string(/*/book[1]/title/@lang)` when evaluated, produces the string value of this node-set -- and this is the string value of the first (in document order) node from this node-set. – Dimitre Novatchev Oct 22 '14 at 05:25
  • So the first expression would return lang="eng" and the second eng only? – Koray Tugay Oct 22 '14 at 05:52
  • 5
    @KorayTugay, No, the first expression **selects**, doesn't "return" -- a set of nodes, and this set of nodes is not a string. A node is not a string -- a node is a node in a **tree**. An XML document is a tree of nodes. `lang="eng"` is just one of many textual representations of an attribute node that has a name "lang", doesn't belong to a namespace, and has a string value the string "eng" – Dimitre Novatchev Oct 22 '14 at 06:17
  • what if we have prefix? `Learning XML` – Vladimir Jun 10 '20 at 14:25
  • 2
    @Vladimir, If the v corresponds to a namespace-uri of say: "my:vvv", then one can create in the host of the XPath engine being used a mapping associating myPrefix (may be v but not necessary) to the same namespace-uri "my:vvv". And then the attribute will be selected using: title/@myPrefix:lang. How such a mapping is created is implementation-specific and one has to read in the documentation of the host of the XPath engine. This is done in a specific way in .NET and in another way in let's say in Saxon. If you don't have any such mapping, use: title/@*[name()='v:lang'] – Dimitre Novatchev Jun 10 '20 at 15:07
  • I am doing this in the Firefox console (with the purpose of copy-paste the result into a text document). If I use the first example with string I get an array with multiple entries back, where every line looks something like this `12: lang="eng"` (12 is the position in the array). I am just looking for the "eng"-part. When I try using string I only get "eng" - but just the first entry in the array. I would like to get something like `12: eng` (i.e. position and value). How can this be achieved? Thank you. – d-b Feb 11 '23 at 20:56
  • @d-b, You can get the total number of @lang attributes that have the value `eng` with this XPath expression: `count(//*[@lang = 'eng']` . Thus every found occurrence will have a position starting from 1 and increasing up to this count. – Dimitre Novatchev Feb 11 '23 at 22:21
  • @DimitreNovatchev I am not interested in the count but a list of values, including duplicates (e.g., "eng", "fr", "eng", "esp", "fr", "eng" etc). In the specific example in the question it would be "eng", "eng". – d-b Feb 11 '23 at 22:24
  • @d-b. In XPath 1.0 you will first get the count with the expression `count(//*/@lang)` and then for each number `$k` in the range 1 to this count, you will issue this XPath expression: `string((//*/@lang)[$k])` which will give you the string value (such as "eng", "fr", etc.) of the $k-th occurrence of the `@lang` attribute. If the browser had support for XPath 2.0 and above, one would simply use: `for $k in 1 to count(//*/@lang) return ($k, string((//*/@lang) [$k]))` – Dimitre Novatchev Feb 11 '23 at 22:41
  • "Simply" :-) Do you think you could write out the full XPath 1.0 expression? Thank you. – d-b Feb 11 '23 at 23:10
  • 1
    @d-b That is the full XPath expression. But AFAIK browsers only support XPath 1.0 at present. – Dimitre Novatchev Feb 12 '23 at 01:23
  • 1
    @d-b I don't think the result you want can be obtained evaluating just a single XPath 1.0 expression. You will need to first evaluate the expression that gives you the count of all `@lang` attributes, then for each possible position of any such attribute you will need to evaluate a separate XPath 1.0 expression that gives you the string -value of that attribute. That is a total of `1 + count(//*/@lang)` XPath 1.0 expressions. – Dimitre Novatchev Feb 12 '23 at 06:04
66

Thanks! This solved a similar problem I had with a data attribute inside a Div.

<div id="prop_sample" data-want="data I want">data I do not want</div>

Use this xpath: //*[@id="prop_sample"]/@data-want

Hope this helps someone else!

Mathias Müller
  • 22,203
  • 13
  • 58
  • 75
smulldino
  • 681
  • 5
  • 5
5

You can try below xPath pattern,

  XPathExpression expr = xPath.compile("/bookstore/book/title[@lang='eng']")
Sharath
  • 407
  • 5
  • 16
  • 7
    That will select any XML **title element**s under /bookstore/book which have a lang Attribute with the value eng, NOT the value of lang. i.e. it selects a list of elements, not a single Attribute – JFK Sep 14 '15 at 14:12
5

The standard formula to extract the values of attribute using XPath is

elementXPath/@attributeName

So here is the xpath to fetch the lang value of first attribute-

//title[text()='Harry Potter']/@lang

PS: indexes are never suggested to use in XPath as they can change if one more title tag comes in.

2

you can use:

(//@lang)[1]

these means you get all attributes nodes with name equal to "lang" and get the first one.

starcwl
  • 396
  • 2
  • 9
2

If you are using PostgreSQL, this is the right way to get it. This is just an assumption where as you have a book table TITLE and PRICE column with populated data. Here's the query

SELECT xpath('/bookstore/book/title/@lang', xmlforest(book.title AS title, book.price AS price), ARRAY[ARRAY[]::TEXT[]]) FROM book LIMIT 1;
Royce
  • 171
  • 2
  • 11
1

You can also get it by

string(//bookstore/book[1]/title/@lang)    
string(//bookstore/book[2]/title/@lang)

although if you are using XMLDOM with JavaScript you can code something like

var n1 = uXmlDoc.selectSingleNode("//bookstore/book[1]/title/@lang");

and n1.text will give you the value "eng"

Vinod Srivastav
  • 3,644
  • 1
  • 27
  • 40
0

Here is the snippet of getting the attribute value of "lang" with XPath and VTD-XML.

import com.ximpleware.*;
public class getAttrVal {
    public static void main(String s[]) throws VTDException{
        VTDGen vg = new VTDGen();
        if (!vg.parseFile("input.xml", false)){
            return ;
        }
        VTDNav vn = vg.getNav();
        AutoPilot ap = new AutoPilot(vn);
        ap.selectXPath("/bookstore/book/title/@lang");
        System.out.println(" lang's value is ===>"+ap.evalXPathToString());
    }
}
vtd-xml-author
  • 3,319
  • 4
  • 22
  • 30