001: /*
002: * Copyright (c) 2006, John Kristian
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions are met:
007: *
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: *
011: * * Redistributions in binary form must reproduce the above copyright
012: * notice, this list of conditions and the following disclaimer in the
013: * documentation and/or other materials provided with the distribution.
014: *
015: * * Neither the name of StAX-Utils nor the names of its contributors
016: * may be used to endorse or promote products derived from this
017: * software without specific prior written permission.
018: *
019: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
020: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
021: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
023: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
024: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
025: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
026: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
027: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
028: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
029: * POSSIBILITY OF SUCH DAMAGE.
030: *
031: */
032: package javanet.staxutils;
033:
034: import javax.xml.stream.XMLStreamException;
035: import javax.xml.stream.XMLStreamWriter;
036:
037: /**
038: * A filter that indents an XML stream. To apply it, construct a filter that
039: * contains another {@link XMLStreamWriter}, which you pass to the constructor.
040: * Then call methods of the filter instead of the contained stream. For example:
041: *
042: * <pre>
043: * {@link XMLStreamWriter} stream = ...
044: * stream = new {@link IndentingXMLStreamWriter}(stream);
045: * stream.writeStartDocument();
046: * ...
047: * </pre>
048: *
049: * <p>
050: * The filter inserts characters to format the document as an outline, with
051: * nested elements indented. Basically, it inserts a line break and whitespace
052: * before:
053: * <ul>
054: * <li>each DTD, processing instruction or comment that's not preceded by data</li>
055: * <li>each starting tag that's not preceded by data</li>
056: * <li>each ending tag that's preceded by nested elements but not data</li>
057: * </ul>
058: * This works well with 'data-oriented' XML, wherein each element contains
059: * either data or nested elements but not both. It can work badly with other
060: * styles of XML. For example, the data in a 'mixed content' document are apt to
061: * be polluted with indentation characters.
062: *
063: * @author <a href="mailto:jk2006@engineer.com">John Kristian</a>
064: */
065: public class IndentingXMLStreamWriter extends StreamWriterDelegate {
066:
067: public IndentingXMLStreamWriter(XMLStreamWriter out) {
068: super (out);
069: }
070:
071: /** How deeply nested the current scope is. The root element is depth 1. */
072: private int depth = 0; // document scope
073:
074: /** stack[depth] indicates what's been written into the current scope. */
075: private int[] stack = new int[] { 0, 0, 0, 0 }; // nothing written yet
076:
077: private static final int WROTE_MARKUP = 1;
078:
079: private static final int WROTE_DATA = 2;
080:
081: private String indent = DEFAULT_INDENT;
082:
083: private String newLine = NORMAL_END_OF_LINE;
084:
085: /** newLine followed by copies of indent. */
086: private char[] linePrefix = null;
087:
088: /**
089: * Set the characters for one level of indentation. The default is
090: * {@link #DEFAULT_INDENT}. "\t" is a popular alternative.
091: */
092: public void setIndent(String indent) {
093: if (!indent.equals(this .indent)) {
094: this .indent = indent;
095: linePrefix = null;
096: }
097: }
098:
099: /** Two spaces; the default indentation. */
100: public static final String DEFAULT_INDENT = " ";
101:
102: public String getIndent() {
103: return indent;
104: }
105:
106: /**
107: * Set the characters that introduce a new line. The default is
108: * {@link #NORMAL_END_OF_LINE}. {@link #getLineSeparator}() is a popular
109: * alternative.
110: */
111: public void setNewLine(String newLine) {
112: if (!newLine.equals(this .newLine)) {
113: this .newLine = newLine;
114: linePrefix = null;
115: }
116: }
117:
118: /**
119: * "\n"; the normalized representation of end-of-line in <a
120: * href="http://www.w3.org/TR/xml11/#sec-line-ends">XML</a>.
121: */
122: public static final String NORMAL_END_OF_LINE = "\n";
123:
124: /**
125: * @return System.getProperty("line.separator"); or
126: * {@link #NORMAL_END_OF_LINE} if that fails.
127: */
128: public static String getLineSeparator() {
129: try {
130: return System.getProperty("line.separator");
131: } catch (SecurityException ignored) {
132: }
133: return NORMAL_END_OF_LINE;
134: }
135:
136: public String getNewLine() {
137: return newLine;
138: }
139:
140: public void writeStartDocument() throws XMLStreamException {
141: beforeMarkup();
142: out.writeStartDocument();
143: afterMarkup();
144: }
145:
146: public void writeStartDocument(String version)
147: throws XMLStreamException {
148: beforeMarkup();
149: out.writeStartDocument(version);
150: afterMarkup();
151: }
152:
153: public void writeStartDocument(String encoding, String version)
154: throws XMLStreamException {
155: beforeMarkup();
156: out.writeStartDocument(encoding, version);
157: afterMarkup();
158: }
159:
160: public void writeDTD(String dtd) throws XMLStreamException {
161: beforeMarkup();
162: out.writeDTD(dtd);
163: afterMarkup();
164: }
165:
166: public void writeProcessingInstruction(String target)
167: throws XMLStreamException {
168: beforeMarkup();
169: out.writeProcessingInstruction(target);
170: afterMarkup();
171: }
172:
173: public void writeProcessingInstruction(String target, String data)
174: throws XMLStreamException {
175: beforeMarkup();
176: out.writeProcessingInstruction(target, data);
177: afterMarkup();
178: }
179:
180: public void writeComment(String data) throws XMLStreamException {
181: beforeMarkup();
182: out.writeComment(data);
183: afterMarkup();
184: }
185:
186: public void writeEmptyElement(String localName)
187: throws XMLStreamException {
188: beforeMarkup();
189: out.writeEmptyElement(localName);
190: afterMarkup();
191: }
192:
193: public void writeEmptyElement(String namespaceURI, String localName)
194: throws XMLStreamException {
195: beforeMarkup();
196: out.writeEmptyElement(namespaceURI, localName);
197: afterMarkup();
198: }
199:
200: public void writeEmptyElement(String prefix, String localName,
201: String namespaceURI) throws XMLStreamException {
202: beforeMarkup();
203: out.writeEmptyElement(prefix, localName, namespaceURI);
204: afterMarkup();
205: }
206:
207: public void writeStartElement(String localName)
208: throws XMLStreamException {
209: beforeStartElement();
210: out.writeStartElement(localName);
211: afterStartElement();
212: }
213:
214: public void writeStartElement(String namespaceURI, String localName)
215: throws XMLStreamException {
216: beforeStartElement();
217: out.writeStartElement(namespaceURI, localName);
218: afterStartElement();
219: }
220:
221: public void writeStartElement(String prefix, String localName,
222: String namespaceURI) throws XMLStreamException {
223: beforeStartElement();
224: out.writeStartElement(prefix, localName, namespaceURI);
225: afterStartElement();
226: }
227:
228: public void writeCharacters(String text) throws XMLStreamException {
229: out.writeCharacters(text);
230: afterData();
231: }
232:
233: public void writeCharacters(char[] text, int start, int len)
234: throws XMLStreamException {
235: out.writeCharacters(text, start, len);
236: afterData();
237: }
238:
239: public void writeCData(String data) throws XMLStreamException {
240: out.writeCData(data);
241: afterData();
242: }
243:
244: public void writeEntityRef(String name) throws XMLStreamException {
245: out.writeEntityRef(name);
246: afterData();
247: }
248:
249: public void writeEndElement() throws XMLStreamException {
250: beforeEndElement();
251: out.writeEndElement();
252: afterEndElement();
253: }
254:
255: public void writeEndDocument() throws XMLStreamException {
256: try {
257: while (depth > 0) {
258: writeEndElement(); // indented
259: }
260: } catch (Exception ignored) {
261: }
262: out.writeEndDocument();
263: afterEndDocument();
264: }
265:
266: /** Prepare to write markup, by writing a new line and indentation. */
267: protected void beforeMarkup() {
268: int soFar = stack[depth];
269: if ((soFar & WROTE_DATA) == 0 // no data in this scope
270: && (depth > 0 || soFar != 0)) // not the first line
271: {
272: try {
273: writeNewLine(depth);
274: if (depth > 0 && getIndent().length() > 0) {
275: afterMarkup(); // indentation was written
276: }
277: } catch (Exception e) {
278: }
279: }
280: }
281:
282: /** Note that markup or indentation was written. */
283: protected void afterMarkup() {
284: stack[depth] |= WROTE_MARKUP;
285: }
286:
287: /** Note that data were written. */
288: protected void afterData() {
289: stack[depth] |= WROTE_DATA;
290: }
291:
292: /** Prepare to start an element, by allocating stack space. */
293: protected void beforeStartElement() {
294: beforeMarkup();
295: if (stack.length <= depth + 1) {
296: // Allocate more space for the stack:
297: int[] newStack = new int[stack.length * 2];
298: System.arraycopy(stack, 0, newStack, 0, stack.length);
299: stack = newStack;
300: }
301: stack[depth + 1] = 0; // nothing written yet
302: }
303:
304: /** Note that an element was started. */
305: protected void afterStartElement() {
306: afterMarkup();
307: ++depth;
308: }
309:
310: /** Prepare to end an element, by writing a new line and indentation. */
311: protected void beforeEndElement() {
312: if (depth > 0 && stack[depth] == WROTE_MARKUP) { // but not data
313: try {
314: writeNewLine(depth - 1);
315: } catch (Exception ignored) {
316: }
317: }
318: }
319:
320: /** Note that an element was ended. */
321: protected void afterEndElement() {
322: if (depth > 0) {
323: --depth;
324: }
325: }
326:
327: /** Note that a document was ended. */
328: protected void afterEndDocument() {
329: if (stack[depth = 0] == WROTE_MARKUP) { // but not data
330: try {
331: writeNewLine(0);
332: } catch (Exception ignored) {
333: }
334: }
335: stack[depth] = 0; // start fresh
336: }
337:
338: /** Write a line separator followed by indentation. */
339: protected void writeNewLine(int indentation)
340: throws XMLStreamException {
341: final int newLineLength = getNewLine().length();
342: final int prefixLength = newLineLength
343: + (getIndent().length() * indentation);
344: if (prefixLength > 0) {
345: if (linePrefix == null) {
346: linePrefix = (getNewLine() + getIndent()).toCharArray();
347: }
348: while (prefixLength > linePrefix.length) {
349: // make linePrefix longer:
350: char[] newPrefix = new char[newLineLength
351: + ((linePrefix.length - newLineLength) * 2)];
352: System.arraycopy(linePrefix, 0, newPrefix, 0,
353: linePrefix.length);
354: System.arraycopy(linePrefix, newLineLength, newPrefix,
355: linePrefix.length, linePrefix.length
356: - newLineLength);
357: linePrefix = newPrefix;
358: }
359: out.writeCharacters(linePrefix, 0, prefixLength);
360: }
361: }
362:
363: }
|