2

I'm currently having an issue with my xpath expressions in java. I'm trying to get a list of shopNames!

I got the following XML;

<?xml version="1.0" encoding="UTF-8"?>
<w:shops xmlns:w="namespace">
    <w:shop>
        <w:shopID>1</w:shopID>
        <w:shopName>ShopName</w:shopName>
        <w:shopURL>ShopUrl</w:shopURL>
    </w:shop>
    <w:shop>
        <w:shopID>2</w:shopID>
        <w:shopName>ShopNames</w:shopName>
        <w:shopURL>ShopUrl</w:shopURL>
    </w:shop>
</w:shops>

And I'm feeding this in a Document to a function alike this:

List<String> getShops(Document d)
    throws Exception
{
    List<String> shopnames = new ArrayList<String>();

    XPath xpath = XPathFactory.newInstance().newXPath();

    XPathExpression expr = xpath.compile("/descendant::w:shop/descendant::w:shopName");
    NodeList nodes = (NodeList) expr.evaluate(d, XPathConstants.NODESET);

    for(int x=0; x<nodes.getLength(); x++)
    {
        shopnames.add("" + nodes.item(x).getNodeValue());
    }
    return shopnames;
}

However the issue is that it simply returns an empty list, I'm suspecting it to be my xpath expression, but I'm not sure about it.

Anyone see the issue here?

Skeen
  • 4,614
  • 5
  • 41
  • 67

4 Answers4

4

The root Element is not shop but shops. I think, you have to compile this expression:

xpath.compile("/descendant::w:shops/descendant::w:shop/descendant::w:shopName");

You may have to set a namespace context:

xpath.setNamespaceContext(new NamespaceContext() {

   public String getNamespaceURI(String prefix) {
    if (prefix.equals("w")) return "namespace";
    else return XMLConstants.NULL_NS_URI;
   }

   public String getPrefix(String namespace) {
    if (namespace.equals("namespace")) return "w";
    else return null;
   }

   public Iterator getPrefixes(String namespace) {return null;}

});

and parse so that the document is aware of namespaces

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);  // <----
DocumentBuilder db = dbf.newDocumentBuilder();
Document xmlDom = db.parse("./shops.xml");
Andreas Dolk
  • 113,398
  • 19
  • 180
  • 268
  • Negative, still returns empty. – Skeen Feb 17 '11 at 10:32
  • Where do I set the dbf.setNamespaceAware() ? on the document that builded my document d? – Skeen Feb 17 '11 at 10:46
  • @Skeen - added the full snippet for the parsing part. But maybe its enough to just set the namespace context. Give that a try first and investigate in the parsing process, if it still doesn't work – Andreas Dolk Feb 17 '11 at 10:50
  • I'll need few minuts, to get this specific module out of my servlet. - To be able to do some useful testing. - I'll return by then. – Skeen Feb 17 '11 at 10:54
  • Okay, this kinda works, now I'm getting a list of 5 nulls, instead of 5 shopNames. - So apperently its finding it now, eh? – Skeen Feb 17 '11 at 11:15
  • I got it working now. - I however had to use the following, on the nodelist returned by evaluating the expression: "nodes.item(x).getFirstChild().getNodeValue());" Unless I add the getFirstChild() I'm just getting null :) – Skeen Feb 17 '11 at 11:32
  • You could also add "/text()" to the end of the XPath statement to select the Text nodes instead of the elements holding the text nodes. – bdoughan Feb 17 '11 at 13:36
  • @Andreas_D: Please amend your answer with a proper XPath expression like `/w:shops/w:shop/w:shopName`. –  Feb 17 '11 at 14:03
  • 3
    @Andreas_D: Also this sentence *You may have to set a namespace context* should be *You **have to** set a namespace context* –  Feb 17 '11 at 14:18
  • @Andreas_D: The name of the root element doesn't matter here, since OP used the `descendant` axis. The key thing in this problem was the lack of a namespace context, like Alejandro pointed. – jasso Feb 17 '11 at 17:58
  • @jasso - I'm not a xpath expert - I was learning while answering ;) – Andreas Dolk Feb 18 '11 at 07:01
2

This one also works: //w:shopName/text() is not as "selective", but I think it's more readable. And returns a list of strings, rather than a list of nodes, which might be better or not, depending what you need.

Augusto
  • 28,839
  • 5
  • 58
  • 88
  • Negative, still returns empty. – Skeen Feb 17 '11 at 10:31
  • it does work, you can try it here http://www.futurelab.ch/xmlkurs/xpath.en.html, if it doesn't work, then the problem is not the xpath, but the libraries / code you're using. – Augusto Feb 17 '11 at 10:33
  • 1
    The libary is the javax standard one. – Skeen Feb 17 '11 at 10:35
  • Your expression doesn't return strings but a node set of text nodes. Selecting text nodes is not the same as the more semantic selection of elements **because mixed content**. –  Feb 17 '11 at 14:17
2

Don't you need to set a NamespaceContext on your XPath instance ? I think you have to so your 'w' ns is recognized.

proactif
  • 11,331
  • 1
  • 17
  • 11
1

You don't have to specificate nscontext, your XPath expr will be little longer but will say everything alone:

/*[namespace-uri()='namespace'  and local-name()='shops']/*[namespace-uri()='namespace'  and local-name()='shop']/*[namespace-uri()='namespace'  and local-name()='shopName']

so in Java:

        XPathFactory factory = XPathFactory.newInstance();
        XPath xp = factory.newXPath();
        String xpath = 
        "/*[namespace-uri()='namespace'  and local-name()='shops']/*[namespace-uri()='namespace'  and local-name()='shop']/*[namespace-uri()='namespace'  and local-name()='shopName']";
        XPathExpression expr = xp.compile(xpath);
        NodeList nlist = (NodeList) expr.evaluate(e, XPathConstants.NODESET);
        ArrayList<String> shopNamesList = new ArrayList<String>();
        for (int i = 0; i < nlist.getLength(); i++) {
            shopNamesList.add(((Element) nlist.item(i)).getNodeValue());
        }

This should work. Regards

Daniel Kec
  • 529
  • 2
  • 8