Skip to content

Update empty XML element using XPATH and DOM in Java

I have the following XML document stored as a String and want to update the SubID element that is empty using XPATH and DOM.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://example.com/2.0/" xmlns:pfx="http://example.com/1.0">
<soapenv:Header/>
<soapenv:Body>
    <soapenv:Fault>
        <faultcode>soapenv:Client</faultcode>
        <faultstring>Authentication Failed</faultstring>
        <detail>
        <pfx:Fault>
            <ns2:MessageHeader xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:ns1="example.com/1.0/" xmlns:ns2="http://www.example.com/MessageHeader/1.0/">
                <ns2:SubId></ns2:SubId>
                <ns2:CnsmrId></ns2:CnsmrId>
            </ns2:MessageHeader>
        </pfx:Fault>
        </detail>
    </soapenv:Fault>
</soapenv:Body>
</soapenv:Envelope>

I’m using the following code to fetch the node first using xpath and update it using DOM. As the XML document use namespaces I’m using local-name() in the xpath.

public void updateXML(String xmlString, String textToAdd) {
    Document responseDoc = convertStringToXMLDocument(xmlString.trim());
    responseDoc.getDocumentElement().normalize();

    XPath xpath = XPathFactory.newInstance().newXPath();
    String destinationPath = "//*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='Fault']/*[local-name()='detail']/*[local-name()='Fault']/*[local-name()='MessageHeader']/*[local-name()='SubId']";

    Node destinationNode = (Node) xpath.compile(destinationPath).evaluate(responseDoc, XPathConstants.NODE);
    destinationNode.setNodeValue(textToAdd);
    String respDoc = convertDOMtoXMLString(responseDoc);
    System.out.println(respDoc);
}

private static Document convertStringToXMLDocument(String xmlString)   {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder = null;
    factory.setNamespaceAware(true);
    builder = factory.newDocumentBuilder();
    Document doc = builder.parse(new InputSource(new StringReader(xmlString)));
    return doc;
}

private static String convertDOMtoXMLString(Document doc) {
    DOMSource domSource = new DOMSource(doc);
    StringWriter writer = new StringWriter();
    StreamResult result = new StreamResult(writer);
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer;
    transformer = tf.newTransformer();
    transformer.transform(domSource, result);
    return writer.toString();
}

After setting the value in the destination node I tried to convert it back to string and print it to verify but I see that the SubId field is collapsed and nothing has been added to it. Here’s the output I got (Posting only relevant part of XML output):

<pfx:Fault>
    <ns2:MessageHeader xmlns:ns1="qwerty.com/1.0/" xmlns:ns2="http://www.abcd.com/MessageHeader/1.0/" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        <ns2:SubId />
        <ns2:CnsmrId />
    </ns2:MessageHeader>
</pfx:Fault>

What am I doing wrong?

Answer

I fgured out using setNodeValue will not work if the element is empty, we can create a text node and then append it as a child node in the detination node.

public void updateXML(String xmlString, String textToAdd) {
    Document responseDoc = convertStringToXMLDocument(xmlString.trim());
    responseDoc.getDocumentElement().normalize();

    XPath xpath = XPathFactory.newInstance().newXPath();
    String destinationPath = "//*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='Fault']/*[local-name()='detail']/*[local-name()='Fault']/*[local-name()='MessageHeader']/*[local-name()='SubId']";

    Node destinationNode = (Node) xpath.compile(destinationPath).evaluate(responseDoc, XPathConstants.NODE);
    Text txt = responseDoc.createTextNode("Hello");
    destinationNode.appendChild(txt);
    String respDoc = convertDOMtoXMLString(responseDoc);
    System.out.println(respDoc);
}

Refer: Adding a Text Node to a DOM Document