001: package jimm.util;
002:
003: import java.awt.Color;
004: import jimm.util.StringUtils;
005: import java.io.PrintWriter;
006: import java.io.Writer;
007: import java.io.OutputStream;
008: import java.util.ArrayList;
009:
010: /**
011: * An XML writer is a print writer that knows how to output XML elements
012: * and make the output look pretty.
013: * <p>
014: * Calling <code>indent</code> and <code>outdent</code> changes the
015: * indentation level. The default indentation width is 4 spaces.
016: *
017: * @author Jim Menard, <a href="mailto:jimm@io.com">jimm@io.com</a>
018: */
019: public class XMLWriter extends PrintWriter {
020:
021: // ================================================================
022: static class ElementInfo {
023: String name;
024: boolean outdentBeforeEnd;
025:
026: ElementInfo(String name) {
027: this .name = name;
028: outdentBeforeEnd = false;
029: }
030: }
031:
032: // ================================================================
033:
034: protected static final int DEFAULT_INDENTATION_WIDTH = 4;
035:
036: protected int width;
037: protected int level;
038: protected boolean newline;
039: protected ArrayList elementStack;
040: protected boolean inElementStart;
041:
042: /**
043: * Constructor, same as the <code>PrintWriter</code> version.
044: *
045: * @param out an output stream
046: */
047: public XMLWriter(OutputStream out) {
048: super (out);
049: init(DEFAULT_INDENTATION_WIDTH);
050: }
051:
052: /**
053: * Constructor, same as <code>PrintWriter</code> version without the
054: * <i>width</i> parameter.
055: *
056: * @param out an output stream
057: * @param autoFlush if <code>true</code>, the <code>println()</code> methods
058: * will flush the output bufferset to flush after every line
059: * @param width indentation width in spaces
060: */
061: public XMLWriter(OutputStream out, boolean autoFlush, int width) {
062: super (out, autoFlush);
063: init(width);
064: }
065:
066: /**
067: * Constructor, same as the <code>PrintWriter</code> version.
068: *
069: * @param out a writer
070: */
071: public XMLWriter(Writer out) {
072: super (out);
073: init(DEFAULT_INDENTATION_WIDTH);
074: }
075:
076: /**
077: * Constructor, same as the <code>PrintWriter</code> version.
078: *
079: * @param out a writer
080: * @param autoFlush if <code>true</code>, the <code>println()</code> methods
081: * will flush the output bufferset to flush after every line
082: * @param width indentation width in spaces
083: */
084: public XMLWriter(Writer out, boolean autoFlush, int width) {
085: super (out, autoFlush);
086: init(width);
087: }
088:
089: /**
090: * Initializes some instance variables. Called from constructors.
091: *
092: * @param indentationWidth number of spaces per indentation level
093: */
094: protected void init(int indentationWidth) {
095: width = indentationWidth;
096: level = 0;
097: newline = true;
098: elementStack = new ArrayList();
099: inElementStart = false;
100: }
101:
102: /**
103: * Increases the indentation level by one.
104: */
105: public void indent() {
106: ++level;
107: }
108:
109: /**
110: * Decreases the indentation level by one.
111: */
112: public void outdent() {
113: if (--level < 0)
114: level = 0;
115: }
116:
117: public void print(boolean b) {
118: doIndent();
119: super .print(b);
120: }
121:
122: public void print(char c) {
123: doIndent();
124: super .print(c);
125: if (c == '\n')
126: newline = true;
127: }
128:
129: public void print(char[] s) {
130: for (int i = 0; i < s.length; ++i)
131: print(s[i]);
132: }
133:
134: public void print(double d) {
135: doIndent();
136: super .print(d);
137: }
138:
139: public void print(float f) {
140: doIndent();
141: super .print(f);
142: }
143:
144: public void print(int i) {
145: doIndent();
146: super .print(i);
147: }
148:
149: public void print(long l) {
150: doIndent();
151: super .print(l);
152: }
153:
154: public void print(Object obj) {
155: print(obj.toString());
156: }
157:
158: /**
159: * This method does not handle newlines embedded in the string.
160: *
161: * @param str the string to output
162: */
163: public void print(String str) {
164: doIndent();
165: super .print(str);
166: }
167:
168: public void println() {
169: super .println();
170: newline = true;
171: }
172:
173: public void println(boolean b) {
174: doIndent();
175: super .println(b);
176: }
177:
178: public void println(char c) {
179: doIndent();
180: super .println(c);
181: if (c == '\n') {
182: newline = true;
183: doIndent();
184: newline = true;
185: }
186: }
187:
188: public void println(char[] s) {
189: for (int i = 0; i < s.length; ++i)
190: print(s[i]);
191: println();
192: }
193:
194: public void println(double d) {
195: doIndent();
196: super .println(d);
197: newline = true;
198: }
199:
200: public void println(float f) {
201: doIndent();
202: super .println(f);
203: newline = true;
204: }
205:
206: public void println(int i) {
207: doIndent();
208: super .println(i);
209: newline = true;
210: }
211:
212: public void println(long l) {
213: doIndent();
214: super .println(l);
215: newline = true;
216: }
217:
218: public void println(Object obj) {
219: println(obj.toString());
220: }
221:
222: // FIX: this is not correct, but it is not worth fixing right now.
223: // It does not handle newlines embedded in the string, but I do not care.
224: public void println(String str) {
225: doIndent();
226: super .println(str);
227: newline = true;
228: }
229:
230: /**
231: * Performs indentation by printing the correct number of tabs and spaces.
232: */
233: protected void doIndent() {
234: if (newline) {
235: int spaces = level * width;
236: while (spaces >= 8) {
237: super .print("\t");
238: spaces -= 8;
239: }
240: super .print(" ".substring(0, spaces));
241: newline = false;
242: }
243: }
244:
245: public void xmlDecl(String encoding) {
246: println("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>");
247: }
248:
249: /** Writes the end of the start of an element. */
250: protected void finishStartElement(boolean outputNewline) {
251: inElementStart = false;
252: print('>');
253: if (outputNewline)
254: println();
255: }
256:
257: /** Writes the end of the start of an element. */
258: protected void finishStartElement() {
259: inElementStart = false;
260: println('>');
261: }
262:
263: protected void parentShouldOutdent() {
264: ElementInfo info = (ElementInfo) elementStack.get(elementStack
265: .size() - 1);
266: info.outdentBeforeEnd = true;
267: }
268:
269: /**
270: * Starts an element. This may be followed by zero or more calls to
271: * <code>attribute</code>. The start-element will be closed by the first
272: * following call to any method other than attribute.
273: */
274: public void startElement(String name) {
275: if (inElementStart) {
276: finishStartElement();
277: indent();
278: parentShouldOutdent();
279: }
280: elementStack.add(new ElementInfo(name));
281: inElementStart = true;
282: doIndent();
283: print("<" + name);
284: }
285:
286: /** Writes an attribute. */
287: public void attr(String name, String value) {
288: print(" " + name + "=\"" + StringUtils.escapeXML(value) + "\"");
289: }
290:
291: public void attr(String name, double value) {
292: print(" " + name + "=\"" + value + "\"");
293: }
294:
295: public void attr(String name, int value) {
296: print(" " + name + "=\"" + value + "\"");
297: }
298:
299: public void attr(String name, char value) {
300: attr(name, "" + value);
301: }
302:
303: public void attr(String name, boolean bool) {
304: attr(name, bool ? "true" : "false");
305: }
306:
307: public void attr(String name, Color c) {
308: attr(name, "" + c.getRed() + ';' + c.getGreen() + ';'
309: + c.getBlue() + ';' + c.getAlpha());
310: }
311:
312: public void attr(String name, Object value) {
313: attr(name, value.toString());
314: }
315:
316: /**
317: * Ends an element. This may output an end-tag or close the current
318: * start-tag as an empty element.
319: */
320: public void endElement() {
321: ElementInfo info = (ElementInfo) elementStack
322: .remove(elementStack.size() - 1);
323: if (info.outdentBeforeEnd)
324: outdent();
325:
326: if (inElementStart) {
327: inElementStart = false;
328: println("/>");
329: } else {
330: doIndent();
331: println("</" + info.name + ">");
332: }
333: }
334:
335: public void cdataElement(String name, String text) {
336: startElement(name);
337: cdata(text);
338: endElement();
339:
340: // boolean in = false;
341: // if (inElementStart) {
342: // finishStartElement();
343: // in = true;
344: // }
345: // if (in) indent();
346: // doIndent();
347: // print("<" + name + ">");
348: // cdata(text);
349: // println("</" + name + ">");
350: // if (in) outdent();
351: }
352:
353: public void cdata(String text) {
354: if (inElementStart)
355: finishStartElement(false);
356: print("<![CDATA[" + (text == null ? "" : text) + "]]>");
357: }
358:
359: public void textElement(String name, String text) {
360: startElement(name);
361: text(text);
362: endElement();
363:
364: // boolean in = false;
365: // if (inElementStart) {
366: // finishStartElement();
367: // in = true;
368: // }
369: // if (in) indent();
370: // doIndent();
371: // print("<" + name + ">");
372: // text(text);
373: // println("</" + name + ">");
374: // if (in) outdent();
375: }
376:
377: public void text(String text) {
378: if (inElementStart)
379: finishStartElement(false);
380: print(StringUtils.escapeXML(text == null ? "" : text));
381: }
382:
383: public void comment(String text) {
384: if (inElementStart)
385: finishStartElement();
386: doIndent();
387: println("<!-- " + text + " -->");
388: }
389:
390: }
|