Search code examples
javaxmljdom-2

jdom2 xPathExpression pull node fail


I try to parse xml which look like

<EvaluateHandRequest xmlns="http://www.aaa.com/aaa/schemas">
    <card>
        <suit>HEARTS</suit>
        <face>TEN</face>
    </card>
    <card>
        <suit>SPADES</suit>
        <face>KING</face>
    </card>
    <card>
        <suit>HEARTS</suit>
        <face>KING</face>
    </card>
    <card>
        <suit>DIAMONDS</suit>
        <face>TEN</face>
    </card>
    <card>
        <suit>CLUBS</suit>
        <face>TEN</face>
    </card>
</EvaluateHandRequest>

And to do it i use XPathExpression, but i can't pull the result.

SAXBuilder jdomBuilder = new SAXBuilder();
Document jdomDocument = jdomBuilder.build(xmlSource);
Element element = jdomDocument.getRootElement(); 
XPathFactory xFactory = XPathFactory.instance();
XPathExpression xExpression = xFactory.compile("/*/*");
List<Element> list  = xExpression.evaluate(element);
System.out.println(list.size() + " "  + list.get(0).getName());//5 card
for (Element element2 : list) {
   System.out.println(element2.getValue());  //proper result
}

If i use /*/* expression during compile i get the all cards and them values, where the card are in the top of hierachy. But when i use /*/card i don't get any element from there. And i can't pull any result if i write any name of any node in expression at all. What the problem i have ?


Solution

  • XPath expressions are always namespace aware. That is how they are specified (section 2.3 - emphasis mine):

    A QName in the node test is expanded into an expanded-name using the namespace declarations from the expression context. This is the same way expansion is done for element type names in start and end-tags except that the default namespace declared with xmlns is not used: if the QName does not have a prefix, then the namespace URI is null (this is the same way attribute names are expanded). It is an error if the QName has a prefix for which there is no namespace declaration in the expression context.

    Thus, you need to specify the namespace you want to use.... before we solve that, though, let's look at your usage for the XPath you show working:

    XPathExpression xExpression = xFactory.compile("/*/*");
    List<Element> list  = xExpression.evaluate(element);
    

    That should not compile.... the XPathExpression is a generic-typed class. You want it to return Elements... To do this right, you need to add a filter to the results in the compile method. Consider your current line:

    XPathExpression xExpression = xFactory.compile("/*/*");
    

    This should be:

    XPathExpression<Element> xExpression = xFactory.compile("/*/*", Filters.element());
    

    This will make everything compile without any errors or warnings...

    Now, to extend the XPath expression to pull just the card elements, we need to include the namespace:

    Namespace aaa = Namespace.getNamespace("aaa", "http://www.aaa.com/aaa/schemas");
    

    For example, to get just the suit elements:

        Namespace aaa = Namespace.getNamespace("aaa", "http://www.aaa.com/aaa/schemas");
        XPathExpression<Element> xExpression = xFactory.compile(
                "/*/aaa:card/aaa:suit", Filters.element(), null, aaa);
    

    If there are multiple namespaces required, you can add them.

    Note that the namespace declaration uses the prefix aaa and, even though there is no prefix used in your XML document, you still need one to reference the namespace in the XPath. Just because there is no prefix in your document does not mean there is no namespace.....

    Read up on the Javadoc for compile(...)