使用StAXResult调用Transformer时省略XML声明

时间:2012-03-07 08:54:54

标签: xml-parsing stax transformer

我想将多个XML节点从源XML文件复制到目标文件。源文件和目标文件都非常大,所以我将使用StAX。通常,我正在尝试处理的文件如下所示:

<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>

目标文件应采用以下格式:

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

其中每个文件应包含标头节点,只有一个 person 节点全部包含在 root 节点中。

现在我的问题如下:我正在尝试通过XMLStreamReader读取源文件,并使用XMLStreamWriter编写它们,两者都连接到一个复制片段的Transformer实例从源文件到目标文件。变换器创建如下:

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);

我还有一个自定义方法,它将光标移动到XML输入流中的所需片段:

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

所以最后我得到了以下结论:

// 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);

问题是生成的XML文件包含2个XML声明部分,每次调用一个

transformer.transform(stAXSource, stAXResult);

我尝试使用 StreamResult 转换输出,如下所示:

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

并省略了XML声明,但当我恢复使用 StAXResult 时,XML声明又回来了。我还注意到OutputKeys.OMIT_XML_DECLARATION无论是打开还是关闭都没有效果(如其他设置,如OutputKeys.STANDALONE,值为“是”)。

简而言之,当StAXResult作为目标结果时,似乎忽略了在Transformer上全局设置的这些设置。

我的问题是:有没有办法可以实现这一点,以便Transformer在每次调用Transformer.transform()时都不会发出XML声明(即写入没有XML声明的片段)?

非常感谢您的帮助。

2 个答案:

答案 0 :(得分:0)

Xalan的SAX2StAXStreamWriter正在这样做。另一个XSLT实现可能表现不同。为了解决这个问题,您可以包装您的编写器并强制startDocument(...)方法不执行任何操作。 StAXON库提供了StreamWriterDelegate实用程序类,它有助于保持必要的代码简短:

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

应该这样做。

答案 1 :(得分:0)

基于@chris 的回答,我实现了一个不依赖于 StAXON 的版本。我使用 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);
    }

}

这是我的测试程序:

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();
    }
}