2

I got some trouble parsing an XML document. For some reason, there are text nodes where I would not expect them to be and therefore my test turns red. The XML file looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<RootNode>
  <PR1>PR1</PR1>
  <ROL>one</ROL>
  <ROL>two</ROL>
  <DG1>DG1</DG1>
  <ROL>three</ROL>
  <ZBK>ZBK</ZBK>
  <ROL>four</ROL>
</RootNode>

Now I have this snippet of code which can reproduce the error:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(TestHL7Helper.class.getResourceAsStream("TestHL7HelperInput.xml"));
Node root = doc.getFirstChild();
Node pr1 = root.getFirstChild();

Inspecting the root variable yields [RootNode: null] which seems to be right, but then it somehow goes all wrong. The pr1 variable turns out to be a text node [#text:\n ] - but why does the parser think that the new line and the spaces are a text node? Shouldn't that be ignored? I tried changing the encoding but that did not help either. Any ideas on that?

If I remove all new lines and space and have my XML document in just one line it all works fine...

Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
Viciouss
  • 283
  • 4
  • 20
  • Here is a [Dom parsing Sample][1] Maybe it will help you. [1]: http://stackoverflow.com/a/7902162/529543 –  Apr 26 '13 at 08:21
  • Maybe the question title should be changed. I went over it to get some info about mixed content and DOM parsing and got answers there, but the question title did not attract me at first glance. Something like "Problem parsing XML with mixed content with Java DOM". – dodecaplex Oct 25 '16 at 06:52

3 Answers3

2

Actually all text between other nodes forms a text-node itself. So, if you use getFirstChild() you will also retrieve those text-nodes.

In your case all non-text child-nodes have a unique name, so you can get them individually by using getElementsByTagName():

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(TestHL7Helper.class.getResourceAsStream("TestHL7HelperInput.xml"));
Node root = doc.getFirstChild();
Node pr1 = (root.getElementsByTagName( "PR1" ))[0];

In general I would not rely on the position within the XML-document, but on stuff like tag-names, attributes or ids.

Sirko
  • 72,589
  • 19
  • 149
  • 183
  • I need to rely on this, as I have to check the siblings in one of the tests. For example I grab the PR1 node with getElementsByTagName and I then want to check how many ROL nodes are right behind. For this task, I need to use getNextSibling() which seems to have exactly the same problem. – Viciouss Apr 26 '13 at 08:27
  • @Viciouss Maybe this question will help you then: http://stackoverflow.com/q/978810/1169798 – Sirko Apr 26 '13 at 08:31
  • I guess I'll do it just the other way round. I'll use xpath to select the siblings matching a certain name. The other methods just don't seem to be satisfying. – Viciouss Apr 26 '13 at 08:40
2

XML supports mixed content meaning elements can have both text and element child nodes. This is to support use cases like the following:

<text>I've bolded the <b>important</b> part.</text>

input.xml

This means that by default a DOM parser will treat the whitespace nodes in the following document as significant (below is a simplified version of your XML document):

<RootNode>
  <PR1>PR1</PR1>
</RootNode>

Demo Code

If you have an XML schema you can set the ignoringElementContentWhitespace property on the DocumentBuilderFactory since then the DOM parser will know if and when the whitespace is significant.

import java.io.File;
import javax.xml.XMLConstants;
import javax.xml.parsers.*;
import javax.xml.validation.*;

import org.w3c.dom.Document;

public class Demo {

    public static void main(String[] args) throws Exception {
        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema s = sf.newSchema(new File("src/forum16231687/schema.xsd"));

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setSchema(s);
        dbf.setIgnoringElementContentWhitespace(true);

        DocumentBuilder db = dbf.newDocumentBuilder();
        Document d = db.parse(new File("src/forum16231687/input.xml"));
        System.out.println(d.getDocumentElement().getChildNodes().getLength());
    }

}

schema.xsd

If you create schema.xsd that looks like the following then the demo code will report that the root element has 1 child node.

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
    <element name="RootNode">
        <complexType>
            <sequence>
                <element name="PR1" type="string"/>
            </sequence>
        </complexType>
    </element>
</schema>

If you change schema.xsd so that the RootNode has mixed content the demo code will report that the RootNode has 3 child nodes.

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
    <element name="RootNode">
        <complexType mixed="true">
            <sequence>
                <element name="PR1" type="string"/>
            </sequence>
        </complexType>
    </element>
</schema>
bdoughan
  • 147,609
  • 23
  • 300
  • 400
0

You can solve this general issue by checking the type of the node:

if (someNode instanceof Element) {
  // ...
}

This can easily form part of a loop, such as:

NodeList childNodes = root.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
  if (childNodes.item(i).getNodeType() == Node.ELEMENT) {
    Element childElement = (Element) childNodes.item(i);
    // ...
  }
}

Alternatively, use something like XMLBeans to reduce the likelihood of introducing bugs when manually parsing XML. Get a well-tested library to do the work for you!

Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
  • That is a solution I thought about but it's kind of ugly. There has to be something that does not fill up my code with all those ifs and elses just to check whether there is something wrong with the parsing. – Viciouss Apr 26 '13 at 08:24
  • @Viciouss The [solution from Sirko](http://stackoverflow.com/a/16231783/474189) is more straightforward, if you have unique names. Alternatively, you can consider using something like [XMLBeans](http://xmlbeans.apache.org/documentation/tutorial_getstarted.html) to generate classes designed specifically to read XML files that match a XSD/DTD. – Duncan Jones Apr 26 '13 at 08:28
  • I'd agree with @Duncan - use an existing library to handle the parsing for you. I've used JDOM in the past and find it easy and intuitive, with not much of a learning curve – DaveH Apr 26 '13 at 08:35
  • 1
    I would recommend using `someNode.getNodeType() == Node.ELEMENT` instead of `someNode instanceof Element`. I've seen DOM implementations where the underlying `Node` impl classes implement more than 1 interface (i.e. `Document` and `Element`) and the `instance of` check can return false positives. – bdoughan Apr 26 '13 at 10:58
  • 1
    @BlaiseDoughan Good to know, thanks. In fact, found a nice link about the issue: http://kingsfleet.blogspot.co.uk/2008/10/java-xdk-dom-trees-and-instanceof.html – Duncan Jones Apr 26 '13 at 11:16