0

There is "original" XML

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
    <soap:Header>
        <context xmlns="urn:zimbra">
            <session id="555">555</session>
            <change token="333"/>
        </context>
    </soap:Header>
    <soap:Body>
        <AuthResponse xmlns="urn:zimbraAccount">
            <lifetime>172799999</lifetime>
            <session id="555">555</session>
            <skin>carbon</skin>
        </AuthResponse>
    </soap:Body>
</soap:Envelope>

The XML is parsed in this way

// javax.xml.parsers.*
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(pathToXml);

Then I'm trying to extract session id by XPath

// javax.xml.xpath.*;
XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
// next xpath does not work with Java and online xpath tester
//XPathExpression expr = xpath.compile("/soap:Envelope/soap:Header/context/session/text()");
// this xpath works with online xpath tester but does not with in Java
XPathExpression expr = xpath.compile("/soap:Envelope/soap:Header/*[name()='context']/*[name()='session']/text()");
String sessionId = (String)expr.evaluate(doc, XPathConstants.STRING);

Tested here http://www.xpathtester.com/xpath/678ae9388e3ae2fc8406eb8cf14f3119

When the XML is simplified to this

<Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
    <Header>
        <context>
            <session id="555">555</session>
            <change token="333"/>
        </context>
    </Header>
    <Body>
        <AuthResponse xmlns="urn:zimbraAccount">
            <lifetime>172799999</lifetime>
            <session id="555">555</session>
            <skin>carbon</skin>
        </AuthResponse>
    </Body>
</Envelope>

This XPath does its job

XPathExpression expr = xpath.compile("/Envelope/Header/context/session/text()");

How to extract session id from "original" XML with Java?

UPDATE: JDK 1.6

1

2 Answers 2

2

The answer is that you need to correctly use namespaces and namespace prefixes:

First, make your DocumentBuilderFactory namespace aware by calling this before you use it:

factory.setNamespaceAware(true); 

Then do this to retrieve the value you want:

XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
xpath.setNamespaceContext(new NamespaceContext() {
    @Override
    public String getNamespaceURI(String prefix) {
        if (prefix.equals("soap")) {
            return "http://www.w3.org/2003/05/soap-envelope";
        }
        if (prefix.equals("zmb")) {
            return "urn:zimbra";
        }

        return XMLConstants.NULL_NS_URI;
    }

    @Override
    public String getPrefix(String namespaceURI) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Iterator getPrefixes(String namespaceURI) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
});

XPathExpression expr = 
       xpath.compile("/soap:Envelope/soap:Header/zmb:context/zmb:session");
String sessionId = (String)expr.evaluate(doc, XPathConstants.STRING);

You may need to add a line to the beginning of your file to import the NamespaceContext class:

import javax.xml.namespace.NamespaceContext;

http://ideone.com/X3iX5N

Sign up to request clarification or add additional context in comments.

4 Comments

Hi JLRishe, unfortunately this does not work for me. BTW, I have to use JDK1.6.
NamespaceContext appears to be available starting with JDK1.5: docs.oracle.com/javase/7/docs/api/javax/xml/namespace/…
When I mentioned about JDK1.6 I implied that "switch" construction is not applicable for String object type. But that is trivial. "this does not work" - it means that when I replaced the switch with "if->return, if->return" the sessionId was empty after XPathExpression evaluation. And even when I tried to add "/text()" or "/@id" at the end of the XPath the sessionId was still empty.
Ok, after 20 minutes of frustration, I finally remembered that the DocumentBuilderFactory has the infuriating behavior of ignoring namespaces unless you tell it to pay attention to them. Please see my edited answer above.
2

You can always do it by ignoring namespace, not the ideal method but works.

 "/*[local-name()='Envelope']/*[local-name()='Header']/*[local-name()='context']/*[local-name()='session']/text()"

1 Comment

Thanks Sean F, your solution does work. I think two XML nodes with the same name on the same level but with different namespace is not good design, right?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.