All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javanet.staxutils.IndentingXMLEventWriter Maven / Gradle / Ivy

/* Copyright (c) 2004, Sun Microsystems, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *
 *     * Neither the name of Sun Microsystems, Inc. nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package javanet.staxutils;

import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.regex.Pattern;
import javanet.staxutils.events.AbstractCharactersEvent;
import javanet.staxutils.helpers.EventWriterDelegate;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;

/**
 * A filter that indents an XML stream. To apply it, construct a filter that
 * contains another {@link XMLEventWriter}, which you pass to the constructor.
 * Then call methods of the filter instead of the contained stream. For example:
 * 
 * 
 * {@link XMLEventWriter} stream = ...
 * stream = new {@link IndentingXMLEventWriter}(stream);
 * stream.add(...);
 * 
* *

* The filter inserts characters to format the document as an outline, with * nested elements indented. Basically, it inserts a line break and whitespace * before: *

    *
  • each DTD, processing instruction or comment that's not preceded by data
  • *
  • each starting tag that's not preceded by data
  • *
  • each ending tag that's preceded by nested elements but not data
  • *
* This works well with 'data-oriented' XML, wherein each element contains * either data or nested elements but not both. It can work badly with other * styles of XML. For example, the data in a 'mixed content' document are apt to * be polluted with indentation characters. *

* Indentation can be adjusted by setting the newLine and indent properties. But * set them to whitespace only, for best results. Non-whitespace is apt to cause * problems, for example when this class attempts to insert newLine before the * root element. * * @author Kohsuke Kawaguchi * @author John Kristian */ public class IndentingXMLEventWriter extends EventWriterDelegate implements Indentation { public IndentingXMLEventWriter(XMLEventWriter out) { super(out); } /** How deeply nested the current scope is. The root element is depth 1. */ private int depth = 0; // document scope /** stack[depth] indicates what's been written into the current scope. */ private int[] stack = new int[] { 0, 0, 0, 0 }; // nothing written yet private static final int WROTE_MARKUP = 1; private static final int WROTE_DATA = 2; /** An object that produces a line break and indentation. */ private final PrefixCharacters newLineEvent = new PrefixCharacters(); public void setIndent(String indent) { newLineEvent.setIndent(indent); } public void setNewLine(String newLine) { newLineEvent.setNewLine(newLine); } public String getIndent() { return newLineEvent.getIndent(); } public String getNewLine() { return newLineEvent.getNewLine(); } /** * @return System.getProperty("line.separator"); or * {@link #NORMAL_END_OF_LINE} if that fails. */ public static String getLineSeparator() { return IndentingXMLStreamWriter.getLineSeparator(); } public void add(XMLEvent event) throws XMLStreamException { switch (event.getEventType()) { case XMLStreamConstants.CHARACTERS: case XMLStreamConstants.CDATA: case XMLStreamConstants.SPACE: out.add(event); afterData(); return; case XMLStreamConstants.START_ELEMENT: beforeStartElement(); out.add(event); afterStartElement(); return; case XMLStreamConstants.END_ELEMENT: beforeEndElement(); out.add(event); afterEndElement(); return; case XMLStreamConstants.START_DOCUMENT: case XMLStreamConstants.PROCESSING_INSTRUCTION: case XMLStreamConstants.COMMENT: case XMLStreamConstants.DTD: beforeMarkup(); out.add(event); afterMarkup(); return; case XMLStreamConstants.END_DOCUMENT: out.add(event); afterEndDocument(); break; default: out.add(event); return; } } /** Prepare to write markup, by writing a new line and indentation. */ protected void beforeMarkup() { int soFar = stack[depth]; if ((soFar & WROTE_DATA) == 0 // no data in this scope && (depth > 0 || soFar != 0)) // not the first line { try { newLineEvent.write(out, depth); if (depth > 0 && getIndent().length() > 0) { afterMarkup(); // indentation was written } } catch (Exception e) { } } } /** Note that markup or indentation was written. */ protected void afterMarkup() { stack[depth] |= WROTE_MARKUP; } /** Note that data were written. */ protected void afterData() { stack[depth] |= WROTE_DATA; } /** Prepare to start an element, by allocating stack space. */ protected void beforeStartElement() { beforeMarkup(); if (stack.length <= depth + 1) { // Allocate more space for the stacks: int[] newWrote = new int[stack.length * 2]; System.arraycopy(stack, 0, newWrote, 0, stack.length); stack = newWrote; } stack[depth + 1] = 0; // nothing written yet } /** Note that an element was started. */ protected void afterStartElement() { afterMarkup(); ++depth; } /** Prepare to end an element, by writing a new line and indentation. */ protected void beforeEndElement() { if (depth > 0 && stack[depth] == WROTE_MARKUP) { // but not data try { newLineEvent.write(out, depth - 1); } catch (Exception ignored) { } } } /** Note that an element was ended. */ protected void afterEndElement() { if (depth > 0) { --depth; } } /** Note that a document was ended. */ protected void afterEndDocument() { depth = 0; if (stack[0] == WROTE_MARKUP) { // but not data try { newLineEvent.write(out, 0); } catch (Exception ignored) { } } stack[0] = 0; // start fresh } private static class PrefixCharacters extends AbstractCharactersEvent implements Indentation { PrefixCharacters() { super((String) null); } /** String used for indentation. */ private String indent = DEFAULT_INDENT; /** String for EOL. */ private String newLine = NORMAL_END_OF_LINE; /** * Various combinations of newLine and indent, used to begin and indent * a line. The appropriate prefix for a given depth is prefixes[depth % * prefixes.length]. This array is managed as a ring buffer, containing * the prefix for the current depth and recently used adjacent prefixes. *

* This structure uses memory proportional to the maximum depth. * Retaining prefixes for all depths would be faster, but require memory * proportional the square of the maximum depth. */ private final String[] prefixes = new String[] { null, null, null, null, null, null }; /** * The depth of the shortest String in prefixes (which is located at * prefixes[minimumPrefix % prefixes.length]). */ private int minimumPrefix = 0; public String getIndent() { return indent; } public String getNewLine() { return newLine; } public void setIndent(String indent) { if (!indent.equals(this.indent)) { Arrays.fill(prefixes, null); } this.indent = indent; } public void setNewLine(String newLine) { if (!newLine.equals(this.newLine)) { Arrays.fill(prefixes, null); } this.newLine = newLine; } void write(XMLEventWriter out, int depth) throws XMLStreamException { this.depth = depth; // so getData knows what to do. out.add(this); } /** An implicit parameter to getData(). */ private int depth = 0; public String getData() { while (depth >= minimumPrefix + prefixes.length) { prefixes[minimumPrefix++ % prefixes.length] = null; } while (depth < minimumPrefix) { prefixes[--minimumPrefix % prefixes.length] = null; } final int p = depth % prefixes.length; String data = prefixes[p]; if (data == null) { StringBuffer b = new StringBuffer(newLine.length() + (indent.length() * depth)); b.append(newLine); for (int d = 0; d < depth; ++d) { b.append(indent); } data = prefixes[p] = b.toString(); } return data; } public int getEventType() { // it's not clear if we are supposed to return SPACES return XMLStreamConstants.CHARACTERS; } public Characters asCharacters() { return this; } public boolean isCData() { return false; } public boolean isIgnorableWhiteSpace() { return isWhiteSpace(); } private static final Pattern ENCODABLE = Pattern.compile("[&<>]"); public void writeAsEncodedUnicode(Writer writer) throws XMLStreamException { // Similar to super.writeAsEncodedUnicode; // but '\r' is not encoded. // TODO? memoize the encoded string. try { String s = getData(); if (!ENCODABLE.matcher(s).find()) { writer.write(s); } else { final char[] data = s.toCharArray(); int first = 0; for (int d = first; d < data.length; ++d) { switch (data[d]) { case '&': writer.write(data, first, d - first); writer.write("&"); first = d + 1; break; case '<': writer.write(data, first, d - first); writer.write("<"); first = d + 1; break; case '>': writer.write(data, first, d - first); writer.write(">"); first = d + 1; break; default: } } writer.write(data, first, data.length - first); } } catch (IOException e) { throw new XMLStreamException(e); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy