Wednesday, 30 June 2010

JDOM & XPath Contexts

Second post today - its been a week of minor but frustrating issues so far and I want to publish as many solutions as I can find time for.

This issue had me scratching my head for a good few hours, and there didn't seem to be much solid documentation online to help out. I was using a combination of JDOM 1.0 and XPath to manipulate a short XML document (arriving via a SOAP interface). Here's the basic document structure that was the subject (Names have been changed to protect the innocent):

<srv:Root>
<srv:Name>Overall Name</srv:Name>
<srv:Items>
<srv:Item>
<srv:ItemName>Name 1</srv:ItemName>
<srv:ItemPrice>20</srv:ItemPrice>
</srv:Item>
<srv:Item>
<srv:ItemName>Name 2</srv:ItemName>
<srv:ItemPrice>30</srv:ItemPrice>
</srv:Item>
</srv:Items>
</srv:Root>

The above conforms to the schema set out for the message, everything validates fine. I was using the following to extract the Items, then iterate through each Item and print out the ItemName and the ItemPrice:


XPath itemXpression = XPath.newInstance("//srv:Item");
itemXpression.addNamespace(ns);
XPath itemXpression = XPath.newInstance("//srv:ItemName");
itemnameXpression.addNamespace(ns);
XPath itemXpression = XPath.newInstance("//srv:ItemPrice");
itempriceXpression.addNamespace(ns);

List nodes = itemXpression.selectNodes(document);

// Ensure we have some items to iterate through
if(!nodes.isEmpty()) {

Iterator it = nodes.iterator();

//Init list of Item
List items = new ArrayList();

//Iterate through all items
while(it.hasNext()) {

Element item = (Element)it.next();

Item i = new Item();

// Evaluate XPath expressions for values
String itemname = itemnameXpression.valueOf(item);
String itemprice = itempriceXpression.valueOf(item);

System.out.println(itemname);
System.out.println(itemprice);

}
}
}


However, the code always prints out the Name and Price of the first item twice, rather than the details of the first and then the details of the second.
I tried mixing it up with using pure JDOM to no avail, tried numerous different approaches, and ether got NPEs or that first item repeated twice. In the end, the solution was simply to adjust the XPath search expression from "//srv:Item..." to "/srv:Item...". It seems that despite the fact I'm explicitly passing in an Element containing only one Item (and using an XMLOutputter verifies this), preceeding the expression with // resets the search context to the overall document root. Strange behaviour, but there you go.

No comments: