java use StAX to get children elements in a generic fashion

Cratylus picture Cratylus · Nov 24, 2010 · Viewed 11.5k times · Source

I am trying to use StAX (I already dislike it....)
It seems that the only way to use it is by continuous if-else conditions.
But most important it seems there is no way to associate an element with its children unless one knows beforehand the structure of the xml document being parsed.Is this correct?
I have tried the following: I have this xml in a String

<ns1:Root xmlns:ns1=\"http://rootNameSpace.com/\">
<ns1:A/>
<ns1:B>
        <Book xmlns=\"http://www.myNameSpace.com\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
            <Data>
                <Author>John</Author>
                <Edition>1</Edition>
                <PubHouse>Small Publishing House</PubHouse>
                <Price>37.8</Price>
            </Data>
        </Book>
</ns1:B>
</ns1:Root>

I would like to use StAX to get the Book element, but it seems I can only write code that has hardcoded all the structure.
I.e. Use XMLEventReader and once you get Book, start looping for Data,Author etc.
Is there a generic solution on this?
I tried the following to get arround this: I tried to go from String to XMLEventReader and back to String but I can not get the exact String representation that I originally used (the namespaces are in brackets, extra colons etc).

StringBuilder xml = new StringBuilder();
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
String msg = "<ns1:Root xmlns:ns1=\"http://rootNameSpace.com/\"><ns1:A/><ns1:B><Book xmlns=\"http://www.myNameSpace.com\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><Data><Author>John</Author><Edition>1</Edition><PubHouse>Small Publishing House</PubHouse><Price>37.8</Price></Data></Book></ns1:B></ns1:Root>";
InputStream input = new ByteArrayInputStream(msg.getBytes("UTF-8"));
XMLEventReader xmlEventReader = inputFactory.createXMLEventReader(input);
while (xmlEventReader.hasNext())
{

    XMLEvent event = xmlEventReader.nextEvent();
    StringWriter sw = new StringWriter();
    event.writeAsEncodedUnicode(sw);
   xml.append(sw);

}
System.out.println(xml);

I get the following:

<?xml version="1.0" encoding='UTF-8' standalone='no'?><['http://rootNameSpace.com/']:ns1:Root xmlns:ns1='http://rootNameSpace.com/'><['http://rootNameSpace.com/']:ns1:A></ns1:A><['http://rootNameSpace.com/']:ns1:B><['http://www.myNameSpace.com']::Book xmlns:='http://www.myNameSpace.com' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'><['http://www.myNameSpace.com']::Data><['http://www.myNameSpace.com']::Author>John</Author><['http://www.myNameSpace.com']::Edition>1</Edition><['http://www.myNameSpace.com']::PubHouse>Small Publishing House</PubHouse><['http://www.myNameSpace.com']::Price>37.8</Price></Data></Book></ns1:B></ns1:Root>

Can this case be addressed via StAX or DOM is the only solution?

Answer

gustafc picture gustafc · Nov 24, 2010

I don't really understand what you're trying to do, but if you want the local name of the tag causing a START_ELEMENT event, you can do it like this:

if (event.getEventType() == START_ELEMENT) {
    QName qname = event.asStartElement().getName()
    System.out.println("Start of element " + qname.getLocalPart());
}

Likewise, asEndElement, asCharacters, etc provide access to other types of nodes.

Personally, I usually find that the XMLStreamReader is handier for me in most situations, but I suppose that depends on the use case, as well as your own personal preferences. A pro tip is that the stricter the schema, the easier the data is to parse with StAX.

You may also want to look at JAX-B for automatic XML data binding.

Edit: Here's a naïve recursive descent StAX parser for the XML in the OP:

@Test
public void recursiveDescentStaxParser( ) throws XMLStreamException,
        FactoryConfigurationError
{
    String msg = "<ns1:Root xmlns:ns1=\"http://rootNameSpace.com/\"><ns1:A/><ns1:B><Book xmlns=\"http://www.myNameSpace.com\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><Data><Author>John</Author><Edition>1</Edition><PubHouse>Small Publishing House</PubHouse><Price>37.8</Price></Data></Book></ns1:B></ns1:Root>";
    XMLStreamReader reader = XMLInputFactory.newFactory( )
            .createXMLStreamReader( new StringReader( msg ) );

    reader.nextTag( );
    readRoot( reader );

}

private void readRoot( XMLStreamReader reader ) throws XMLStreamException
{
    while ( reader.nextTag( ) == XMLEvent.START_ELEMENT )
    {
        QName name = reader.getName( );
        if ( "B".equals( name.getLocalPart( ) ) )
            readBooks( reader );
        else
            reader.nextTag( ); // Empty <A>

    }
}

private void readBooks( XMLStreamReader reader ) throws XMLStreamException
{
    while ( reader.nextTag( ) == XMLEvent.START_ELEMENT )
    {
        QName name = reader.getName( );
        if ( !"Book".equals( name.getLocalPart( ) ) )
            throw new XMLStreamException( name.toString( ) );
        reader.nextTag( ); // Jump to <Data>
        readBook( reader );
        reader.nextTag( ); // Jump to </B>
    }
}

private void readBook( XMLStreamReader reader ) throws XMLStreamException
{
    reader.nextTag( ); // Skip to <Author>
    System.out.println( "Author: " + reader.getElementText( ) );
    reader.nextTag( ); // Skip to <Edition>
    System.out.println( "Edition: " + reader.getElementText( ) );
    reader.nextTag( ); // Skip to <PubHouse>
    System.out.println( "Publisher: " + reader.getElementText( ) );
    reader.nextTag( ); // Skip to <Price>
    System.out.println( "Price: " + reader.getElementText( ) );
    reader.nextTag( ); // Skip to </Book>

}

Writing stuff like this doesn't only make the code a lot easier to read and reason about, but also the stack traces when errors pop up.