35

is there really no way to directly write formatted XML using javax.xml.stream.XMLStreamWriter (Java SE 6)??? This is really unbelievable, as other XML APIs such as JAXB and some DOM libraries are able to do this. Even the .NET XMLStreamWriter equivalent is able to this AFAIK (if I remember correctly the class is System.Xml.XmlTextWriter).

This means the only option I have is to reparse the XML to generate formatted output??

E.g.:

            StringWriter sw = new StringWriter();
    XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory();
    XMLStreamWriter xmlStreamWriter = xmlOutputFactory.createXMLStreamWriter(sw);
    writeXml(xmlStreamWriter);
    xmlStreamWriter.flush();
    xmlStreamWriter.close();

    TransformerFactory factory = TransformerFactory.newInstance();

    Transformer transformer = factory.newTransformer();
    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

    StringWriter formattedStringWriter = new StringWriter();
    transformer.transform(new StreamSource(new StringReader(sw.toString())), new StreamResult(formattedStringWriter));
    System.out.println(formattedStringWriter);

The problem with this solution is the property "{http://xml.apache.org/xslt}indent-amount". I didn't find any documentation about it and it doesn't seem to be guaranteed to be portable.

So what other options do I have, if I want to do this with standard Java 6 classes? Create a JAXB or DOM object graph just for pretty printing??

Puce
  • 37,247
  • 13
  • 80
  • 152
  • Pardon me but what is `writeXml()`? It would be good to provide all necessary parts of your code so one can try it if he has the same problem as you (my case), but I have no clue what it is (what code is behind it) so I can not try it, unfortunately. – qraqatit Jan 31 '20 at 10:43

6 Answers6

37

This exact question has been answered some months ago and one of the answers is to use the IndentingXMLStreamWriter class:

XMLOutputFactory xmlof = XMLOutputFactory.newInstance();
XMLStreamWriter writer = new IndentingXMLStreamWriter(xmlof.createXMLStreamWriter(out));

It is a neat solution as far as the code goes, but careful: this is a com.sun.* class, there is no guarantee of its general availability...

Mads Hansen
  • 63,927
  • 12
  • 112
  • 147
Tomislav Nakic-Alfirevic
  • 10,017
  • 5
  • 38
  • 51
  • 2
    As you said, unfortunatly this is not a published API. – Puce Mar 24 '14 at 09:27
  • 1
    The `IndentingXMLStreamWriter` class and its base class `DelegatingXMLStreamWriter` can be opened with a decompiler (IntelliJ IDEA nowadays has one built-in) and saved in source form as part of our own project. So, their 'unpublishedness' is not really a problem. If you do this, be sure to fix a bug in sun's code: in the `onEmptyElement()` method, the call to `writeCharacters( "\n" );` should be `super.writeCharacters( "\n" );` instead. – Mike Nakis Mar 05 '15 at 20:01
9

You could add the necessary code to format your document in your writeXml method. Simply maintain a depth counter (to indicate the levels of nesting). Then before you writeStartElement and after you writeEndElement use the depth index to insert an indent.

for(int x=0; x<depth; x++) {
    xmlStreamWriter.writeCharacters("    ");
}
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • I cannot find such a writeText-method?!? http://download.oracle.com/javase/6/docs/api/javax/xml/stream/XMLStreamWriter.html – Puce Jan 06 '11 at 16:38
  • You're correct it should have been writeCharacters. This method can be used to insert text anywhere in a document not just within a single element. – bdoughan Jan 06 '11 at 16:42
  • 2
    I think I would have to call this before any writeXYZ call (e.g. using a wrapper) and, as you said, manage the depth. Probably doable but not as trivial as with JAXB: marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); – Puce Jan 06 '11 at 16:44
3

I have no answer considering "{http://xml.apache.org/xslt}indent-amount" property. But you can use transformers somewhat portably without the need to reparse all output. You can use something like the code below to create pretty-printing XMLStreamWriter.

public static void main(String[] args) {
    XMLStreamWriter xmlStreamWriter = createXMLStreamWriter(new OutputStreamWriter(System.out));
    writeXml(xmlStreamWriter);
}

private static XMLStreamWriter createXMLStreamWriter(Writer textWriter) throws XMLStreamException {
    SAXTransformerFactory xmlTransformerFactory = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
    TransformerHandler transformingSAXHandler;
    try {
        transformingSAXHandler = xmlTransformerFactory.newTransformerHandler();
    } catch (TransformerConfigurationException e) {
        throw new XMLStreamException(e);
    }
    Transformer xmlTransformer = transformingSAXHandler.getTransformer();
    xmlTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
    transformingSAXHandler.setResult(new StreamResult(textWriter));

    XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
    return xmlOutputFactory.createXMLStreamWriter(new SAXResult(transformingSAXHandler));
}
Victor Nazarov
  • 825
  • 8
  • 11
  • The last line causes this: `java.lang.UnsupportedOperationException: Result of type javax.xml.transform.sax.SAXResult@184f6be2 is not supported. Supported result types are: DOMResult, StAXResult and StreamResult`. We use Java-version 1.8u74, you? – sjngm Feb 22 '17 at 09:25
  • I think it should work when underlying implementation is Apache Xerces, but is not guaranteed to work with other implementations... – Victor Nazarov Feb 22 '17 at 12:07
2

With Spring Batch this requires a subclass since this JIRA BATCH-1867

public class IndentingStaxEventItemWriter<T> extends StaxEventItemWriter<T> {

  @Setter
  @Getter
  private boolean indenting = true;

  @Override
  protected XMLEventWriter createXmlEventWriter( XMLOutputFactory outputFactory, Writer writer) throws XMLStreamException {
    if ( isIndenting() ) {
      return new IndentingXMLEventWriter( super.createXmlEventWriter( outputFactory, writer ) );
    }
    else {
      return super.createXmlEventWriter( outputFactory, writer );
    }
  }

}

But this requires an additionnal dependency because Spring Batch does not include the code to indent the StAX output:

<dependency>
  <groupId>net.java.dev.stax-utils</groupId>
  <artifactId>stax-utils</artifactId>
  <version>20070216</version>
</dependency>
Sebastien Lorber
  • 89,644
  • 67
  • 288
  • 419
  • 1
    The interesting part is the stax-utils library, which provides the IndentingXMLEventWriter and seems to be independent of Spring Batch. Thanks for the hint! – Puce Sep 18 '13 at 21:53
2

Just for the record, Saxon allows you to get a serializing XMLStreamWriter through the s9api interface:

Processor p = new Processor();
Serializer s = p.newSerializer();
s.setOutputProperty(Property.INDENT, "yes");
XMLStreamWriter w = s.getXMLStreamWriter();

and the Serializer exposes all the XSLT-defined output properties (including "indent") plus some Saxon-specific ones.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • A slightly more complete version here: http://stackoverflow.com/questions/10105187/java-indentingxmlstreamwriter-alternative/30637501#30637501 – Robert Fey Jun 04 '15 at 07:56
1

You are correct that the standard java.xml interfaces provide little to no control over serialization, even though the underlying implementation (Apache Xerces) does. I've had to solve this problem and the best I came up with was to include a copy of Xerces and use the org.apache.xml.serialize classes.

Jim Garrison
  • 85,615
  • 20
  • 155
  • 190
  • 1
    Yes, I've read about such solutions, but since Xerces 2.9.0 this package has been deprecated: http://xerces.apache.org/xerces2-j/javadocs/other/org/apache/xml/serialize/package-summary.html – Puce Jan 07 '11 at 10:37