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

humkins
  • 9,635
  • 11
  • 57
  • 75

2 Answers2

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

Community
  • 1
  • 1
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • Hi JLRishe, unfortunately this does not work for me. BTW, I have to use JDK1.6. – humkins Apr 21 '14 at 20:10
  • `NamespaceContext` appears to be available starting with JDK1.5: http://docs.oracle.com/javase/7/docs/api/javax/xml/namespace/NamespaceContext.html – JLRishe Apr 22 '14 at 04:02
  • 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. – humkins Apr 22 '14 at 11:29
  • 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. – JLRishe Apr 22 '14 at 13:06
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()"
Sean F
  • 2,352
  • 3
  • 28
  • 40
  • 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? – humkins Apr 22 '14 at 11:38