0

I have a List<String> which contains the XML events created as a part of the output from the JAXB Marshaling approach. After completion of the JAXB Marshaling process this List<String> can contain large amounts of XML.

These XML fragments so are part of a large XML. The large XML has some additional header elements so I am trying to create the large XML using the XMLEventWriter and trying to add the elements from my LIST<String> but it does not work as expected and running into various errors.

I get the following error:

Exception in thread "main" javax.xml.stream.XMLStreamException: Trying to output second root

Following is the code I have:

import javax.xml.namespace.QName;
import javax.xml.stream.*;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

public class TestMain {
    public static void main(String[] args) throws XMLStreamException {
        final XMLEventWriter xmlEventWriter = XMLOutputFactory.newInstance().createXMLEventWriter(System.out);
        final XMLEventFactory events = XMLEventFactory.newInstance();
        List<String> eventList = new ArrayList<>();
        eventList.add("<fragment><data>First Event</data></fragment>");
        eventList.add("<fragment><data>Second Event</data></fragment>");
        eventList.add("<another><data>Third Event</data></another>");

        xmlEventWriter.add(events.createStartDocument());
        xmlEventWriter.add(events.createStartElement(new QName("root"), null, null));
        xmlEventWriter.add(events.createStartElement(new QName("fragments"), null, null));
        for (String event : eventList) {
            final XMLEventReader xer = XMLInputFactory.newInstance().createXMLEventReader(new StringReader(event));
            if (xer.peek().isStartDocument()) {
                xer.nextEvent();
                xmlEventWriter.add(xer);
            }
        }
        xmlEventWriter.add(events.createEndDocument());
        xmlEventWriter.add(events.createEndDocument());
        xmlEventWriter.close();


    }
}

Following is the output I am expecting:

<root>
    <fragments>
        <fragment>
            <data>First Event</data>
        </fragment>
        <fragment>
            <data>Second Event</data>
        </fragment>
        <another>
            <data>Third Event</data>
        </another>
    </fragments>
</root>

I looked into XMLStreamWriter but I got to know that this cannot be done using that. I am not particular about XMLEventWriter. All I want to is get the required output using any of the approaches/libraries.

Can someone please help me with this? I just want to add the XML from my List to XMLEventWriter which has been created already with few Nodes.

BATMAN_2008
  • 2,788
  • 3
  • 31
  • 98
  • I am not sure I see why you think you'd need (or want) to use XMLEventWriter (or -Reader); XMLStreamWriter and XMLStreamReader should work just fine here and have bit lower overhead too. Approach @andreas outlines should work. Event-approach is mostly useful if you need to buffer events for storage or transformations; but for as-you-go generation/translation there's not much value. – StaxMan Jun 15 '21 at 15:48

1 Answers1

1

First, your ending events are wrong:

xmlEventWriter.add(events.createEndDocument());
xmlEventWriter.add(events.createEndDocument());

They should be:

xmlEventWriter.add(events.createEndElement(new QName("fragments"), null));
xmlEventWriter.add(events.createEndElement(new QName("root"), null));
xmlEventWriter.add(events.createEndDocument());

Second, the issue is that the xer event reader ends with an "End Document" event that you don't want copied, so you need to filter it out.

To do that, wrap xer with a delegate that ends the event stream when the "End Document" event is reached:

xer = new EventReaderDelegate(xer) {
    @Override
    public boolean hasNext() {
        if (! super.hasNext())
            return false;
        try {
            return ! super.peek().isEndDocument();
        } catch (@SuppressWarnings("unused") XMLStreamException ignored) {
            return true;
        }
    }
};
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Thanks a lot for the quick response. Indeed it worked. I have been trying to rectify it since afternoon without any progress. Just out of curiosity do you have some sort of documentation where you found this? or with your knowledge? I just wanted to know where can I find something like this. Thanks a lot again, you deserve the upvote and accepted answer. Have a nice day :) – BATMAN_2008 Jun 14 '21 at 19:26
  • Also, one more question. I am using the `XMLEventWriter` to create a large XML. The `List eventList` can contain 1000s of entries is this the best choice or there something better than this that I can use? I was previously using `XMLStreamWriter` but later I found that I cannot do something similar using that so I switched to `XMLEventWriter`. Just wanted to confirm if this is the most efficient and effective for handling large XML. – BATMAN_2008 Jun 14 '21 at 19:30
  • @BATMAN_2008 I just looked at the javadoc of [`XMLEventReader`](https://docs.oracle.com/javase/8/docs/api/javax/xml/stream/XMLEventReader.html) and saw the `EventReaderDelegate` listed as a known implementing class. Then I created a test program (copy of your code), wrapped `xer` and implemented some of the methods with a print statement, so I could see how it was used. Only `hasNext()` and `nextEvent()` was called, so it was easy to figure out that simply tweaking `hasNext()` could be used to prevent the EndDocument event from being returned. – Andreas Jun 14 '21 at 19:38
  • @BATMAN_2008 Using `XMLEventWriter` is good. But where does `eventList` come from. You mentioned that the strings are a result of JAXB Marshaling, and the [`Marshaller`](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/Marshaller.html) can [`marshal`](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/Marshaller.html#marshal-java.lang.Object-javax.xml.stream.XMLEventWriter-) directly to an `XMLEventWriter`, so it might be possible to skip the entire intermediate XML String. Alternative, marshall to string, but then do the `xer` thing immediately, instead of building a list. – Andreas Jun 14 '21 at 19:44
  • Thanks for detailed explanation. I will also try to figure out the same using methods you have mentioned just to learn. With regards to `List` question, yes its created after marshaling but after marshaling actually i need to verify if it matches the prescribed `XSD`. Hence, I am using `Stringwriter` within `Marshaling`. After which this StringWriter content validated against XSD. If it validates I am writing into `ArrayList`. This happens for large number of events. Basically I am converting JSON to XML using marshling approach. – BATMAN_2008 Jun 14 '21 at 21:01
  • So after all the events are read from JSON and Converted to XML, I need to create the Header for the XML and write these Matshaled events one by one. Hence I am creating the List first and then XMLEventReader for creating final XML using the fragments of XML in the List. Is there better approach? I hope i have explained correctly so you can understand whats the background. Looking forward to your suggestions or improvements – BATMAN_2008 Jun 14 '21 at 21:05
  • 2
    @BATMAN_2008 I already told you the better approach: Write it as you go. Marshall the first XML, validate it, write it to final XML stream. Repeat for next XML. No list build-up = reduced memory use. – Andreas Jun 14 '21 at 23:20
  • I am running into an issue while using the `XMLEventWriter`, If you get a chance can you please have a look at this issue and provide me with some resolution? https://stackoverflow.com/q/69028368/7584240 – BATMAN_2008 Sep 02 '21 at 11:22