2

I would like to copy multiple XML nodes from a source XML file to a target file. Both source and target files are very large, so I will use StAX. Typically the file I'm trying to process looks as follows:

<root>
  <header>
    <title>A List of persons</title>
  </header>
  <person>
    <name>Joe</name>
    <surname>Bloggs</surname>
  </person>  
  <person>
    <name>John</name>
    <surname>Doe</surname>
  </person>  
  .
  .
  etc...
</root>

The target files should be in the following format:

<root>
  <header>
    <title>A List of persons</title>
  </header>
  <person>
    <name>Joe</name>
    <surname>Bloggs</surname>
  </person>
</root>

where each file should contain the header node, exactly one person node all enclosed within the root node.

Now my problem is the following: I'm trying to read in the source file through a XMLStreamReader, and writing it using a XMLStreamWriter, both of which are wired into a Transformer instance which copies fragments from the source file into the target file. The transformer is created as follows:

TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

StAXSource stAXSource = new StAXSource(reader);
StAXResult stAXResult = new StAXResult(writer);

I also have a custom made method which moves the cursor to the desired fragment in the XML input stream:

// Moves XMLStreamReader cursor to the next fragment. 
moveCursorToNextFragment(XMLStreamReader reader, String fragmentNodeName)

So that in the end I end up with the following:

// Open file as usual...

// Advance cursor to <header> node, and copy fragment till
// </header> to the output writer. 
moveCursorToNextFragment(reader, "header");
transformer.transform(stAXSource, stAXResult);

// Advance cursor to <person> node, and copy fragment till
// </person> to the output writer.
moveCursorToNextFragment(reader, "person");
transformer.transform(stAXSource, stAXResult);

The problem is that the resultant XML file contains 2 XML declaration sections, one for each invocation of

transformer.transform(stAXSource, stAXResult);

I have tried using StreamResult to transform the output, as follows:

transformer.transform(stAXSource, new StreamResult(myStream));

and the XML declaration is omitted, but when I reverted back to using StAXResult, the XML declaration is back again. I also noticed that OutputKeys.OMIT_XML_DECLARATION has no effect whether it is on or off (as are other settings such as OutputKeys.STANDALONE with a value of "yes").

So in short, it seems that these settings set globally on the Transformer are being disregarded when a StAXResult as a destination result.

My question is this: is there any way in which this can be achieved, so that the Transformer does not emit XML declarations upon each invocation of Transformer.transform() (i.e write fragments without the XML declaration)?

Your help is much appreciated and needed.

Duncan Paul
  • 485
  • 5
  • 13

2 Answers2

2

Xalan's SAX2StAXStreamWriter is doing this. Another XSLT implementation may behave differently. To get around this, you may wrap your writer and force the startDocument(...) methods to do nothing. The StAXON library provides the StreamWriterDelegate utility class which helps to keep the necessary code short:

writer = new StreamWriterDelegate(writer) {
  @Override public void writeStartDocument() {}
  @Override public void writeStartDocument(String version) {}
  @Override public void writeStartDocument(String encoding, String version) {}
};

should do the trick.

chris
  • 3,573
  • 22
  • 20
0

Based on @chris's answer, I implemented a version that does not depend on StAXON. I tested this with Zulu, OpenJDK, Java 11.

import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

/**
 * Implementation of XMLStreamWriter that will not write the XML tags
 * allowing us to use a transformer to write DOM objects using STAX
 * but avoid having <?xml version="1.0" ?> tags generated for each 
 * invocation of the transformer.
 */
public class ContinueDocXMLStreamWriter implements XMLStreamWriter {
    private XMLStreamWriter writer;

    ContinueDocXMLStreamWriter(XMLStreamWriter writer) {
        this.writer = writer;
    }
    
    @Override
    public void writeStartDocument() throws XMLStreamException {
        // writer.writeStartDocument();
    }

    @Override
    public void writeStartDocument(String version) 
            throws XMLStreamException {
        // writer.writeStartDocument(version);
    }

    @Override
    public void writeStartDocument(String encoding, String version) 
            throws XMLStreamException {
        // writer.writeStartDocument(encoding, version);
    }

    @Override
    public void writeEndDocument() throws XMLStreamException {
        // writer.writeEndDocument();
    }

    @Override
    public void writeStartElement(String localName) 
            throws XMLStreamException {
        writer.writeStartElement(localName);
    }

    @Override
    public void writeStartElement(String namespaceURI, String localName)
            throws XMLStreamException {
        writer.writeStartElement(namespaceURI, localName);
    }

    @Override
    public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
        writer.writeStartElement(prefix, localName, namespaceURI);
    }

    @Override
    public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
        writer.writeEmptyElement(namespaceURI, localName);
    }

    @Override
    public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
        writer.writeEmptyElement(prefix, localName, namespaceURI);
    }

    @Override
    public void writeEmptyElement(String localName) throws XMLStreamException {
        writer.writeEmptyElement(localName);
    }

    @Override
    public void writeEndElement() throws XMLStreamException {
        writer.writeEndElement();
    }

    @Override
    public void close() throws XMLStreamException {
        writer.close();
    }

    @Override
    public void flush() throws XMLStreamException {
        writer.flush();
    }

    @Override
    public void writeAttribute(String localName, String value) throws XMLStreamException {
        writer.writeAttribute(localName, value);
    }

    @Override
    public void writeAttribute(String prefix, String namespaceURI, String localName, String value) throws XMLStreamException {
        writer.writeAttribute(prefix, namespaceURI, localName, value);
    }

    @Override
    public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException {
        writer.writeAttribute(namespaceURI, localName, value);
    }

    @Override
    public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
        writer.writeNamespace(prefix, namespaceURI);
    }

    @Override
    public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException {
        writer.writeDefaultNamespace(namespaceURI);
    }

    @Override
    public void writeComment(String data) throws XMLStreamException {
        writer.writeComment(data);
    }

    @Override
    public void writeProcessingInstruction(String target) throws XMLStreamException {
        writer.writeProcessingInstruction(target);
    }

    @Override
    public void writeProcessingInstruction(String target, String data) throws XMLStreamException {
        writer.writeProcessingInstruction(target, data);
    }

    @Override
    public void writeCData(String data) throws XMLStreamException {
        writer.writeCData(data);
    }

    @Override
    public void writeDTD(String dtd) throws XMLStreamException {
        writer.writeDTD(dtd);
    }

    @Override
    public void writeEntityRef(String name) throws XMLStreamException {
        writer.writeEntityRef(name);
    }

    @Override
    public void writeCharacters(String text) throws XMLStreamException {
        writer.writeCharacters(text);
    }

    @Override
    public void writeCharacters(char[] text, int start, int len) throws XMLStreamException {
        writer.writeCharacters(text, start, len);
    }

    @Override
    public String getPrefix(String uri) throws XMLStreamException {
        return writer.getPrefix(uri);
    }

    @Override
    public void setPrefix(String prefix, String uri) throws XMLStreamException {
        writer.setPrefix(prefix, uri);
    }

    @Override
    public void setDefaultNamespace(String uri) throws XMLStreamException {
        writer.setDefaultNamespace(uri);
    }

    @Override
    public void setNamespaceContext(NamespaceContext context) throws XMLStreamException {
        writer.setNamespaceContext(context);
    }

    @Override
    public NamespaceContext getNamespaceContext() {
        return writer.getNamespaceContext();
    }

    @Override
    public Object getProperty(String name) throws IllegalArgumentException {
        return writer.getProperty(name);
    }

}

Here is my test program:

import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Files;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stax.StAXResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class NoDeclXmlFailed {
    public static void main(String ... args) throws Exception {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        transformer.setOutputProperty(OutputKeys.METHOD, "xml");
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 
        transformer.setOutputProperty(OutputKeys.STANDALONE, "no");

        XMLOutputFactory output = XMLOutputFactory.newInstance();
        File destination = File.createTempFile("example_", ".xml");
        XMLStreamWriter writer = output.createXMLStreamWriter(new FileOutputStream(destination));
        writer.writeStartDocument();
        writer.writeStartElement("test");
        
        Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        doc.setXmlStandalone(true);
        Element foo = doc.createElement("foo");
        foo.setTextContent("bar");
        foo.setAttribute("wibble", "wobble");
        DOMSource source = new DOMSource(foo);
        StAXResult stax = new StAXResult(new ContinueDocXMLStreamWriter(writer));
        transformer.transform(source, stax);

        foo = doc.createElement("foo");
        foo.setTextContent("bar2");
        foo.setAttribute("wibble", "wobble2");
        source = new DOMSource(foo);
        stax = new StAXResult(new ContinueDocXMLStreamWriter(writer));
        transformer.transform(source, stax);

        writer.writeEndDocument();
        writer.flush();
        writer.close();
        
        Files.lines(destination.toPath())
                .forEach(line -> System.out.println(line));
        destination.delete();
    }
}
Tim Perry
  • 3,066
  • 1
  • 24
  • 40