001: /**
002: * Copyright (c) 2004-2006 Regents of the University of California.
003: * See "license-prefuse.txt" for licensing terms.
004: */package prefuse.util.io;
005:
006: import java.io.PrintWriter;
007: import java.util.ArrayList;
008:
009: /**
010: * Utility class for writing XML files. This class provides convenience
011: * methods for creating XML documents, such as starting and ending
012: * tags, and adding content and comments. This class handles correct
013: * XML formatting and will properly escape text to ensure that the
014: * text remains valid XML.
015: *
016: * <p>To use this class, create a new instance with the desired
017: * PrintWriter to write the XML to. Call the {@link #begin()} or
018: * {@link #begin(String, int)} method when ready to start outputting
019: * XML. Then use the provided methods to generate the XML file.
020: * Finally, call either the {@link #finish()} or {@link #finish(String)}
021: * methods to signal the completion of the file.</p>
022: *
023: * @author <a href="http://jheer.org">jeffrey heer</a>
024: */
025: public class XMLWriter {
026:
027: private PrintWriter m_out;
028: private int m_bias = 0;
029: private int m_tab;
030: private ArrayList m_tagStack = new ArrayList();
031:
032: /**
033: * Create a new XMLWriter.
034: * @param out the print writer to write the XML to
035: */
036: public XMLWriter(PrintWriter out) {
037: this (out, 2);
038: }
039:
040: /**
041: * Create a new XMLWriter.
042: * @param out the print writer to write the XML to
043: * @param tabLength the number of spaces to use for each
044: * level of indentation in the XML file
045: */
046: public XMLWriter(PrintWriter out, int tabLength) {
047: m_out = out;
048: m_tab = 2;
049: }
050:
051: /**
052: * Print <em>unescaped</em> text into the XML file. To print
053: * escaped text, use the {@link #content(String)} method instead.
054: * @param s the text to print. This String will not be escaped.
055: */
056: public void print(String s) {
057: m_out.print(s);
058: }
059:
060: /**
061: * Print <em>unescaped</em> text into the XML file, followed by
062: * a newline. To print escaped text, use the {@link #content(String)}
063: * method instead.
064: * @param s the text to print. This String will not be escaped.
065: */
066: public void println(String s) {
067: m_out.print(s);
068: m_out.print("\n");
069: }
070:
071: /**
072: * Print a newline into the XML file.
073: */
074: public void println() {
075: m_out.print("\n");
076: }
077:
078: /**
079: * Begin the XML document. This must be called before any other
080: * formatting methods. This method prints an XML header into
081: * the top of the output stream.
082: */
083: public void begin() {
084: m_out.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
085: println();
086: }
087:
088: /**
089: * Begin the XML document. This must be called before any other
090: * formatting methods. This method prints an XML header into
091: * the top of the output stream, plus additional header text
092: * provided by the client
093: * @param header header text to insert into the document
094: * @param bias the spacing bias to use for all subsequent indenting
095: */
096: public void begin(String header, int bias) {
097: begin();
098: m_out.print(header);
099: m_bias = bias;
100: }
101:
102: /**
103: * Print a comment in the XML document. The comment will be printed
104: * according to the current spacing and followed by a newline.
105: * @param comment the comment text
106: */
107: public void comment(String comment) {
108: spacing();
109: m_out.print("<!-- ");
110: m_out.print(comment);
111: m_out.print(" -->");
112: println();
113: }
114:
115: /**
116: * Internal method for printing a tag with attributes.
117: * @param tag the tag name
118: * @param names the names of the attributes
119: * @param values the values of the attributes
120: * @param nattr the number of attributes
121: * @param close true to close the tag, false to leave it
122: * open and adjust the spacing
123: */
124: protected void tag(String tag, String[] names, String[] values,
125: int nattr, boolean close) {
126: spacing();
127: m_out.print('<');
128: m_out.print(tag);
129: for (int i = 0; i < nattr; ++i) {
130: m_out.print(' ');
131: m_out.print(names[i]);
132: m_out.print('=');
133: m_out.print('\"');
134: escapeString(values[i]);
135: m_out.print('\"');
136: }
137: if (close)
138: m_out.print('/');
139: m_out.print('>');
140: println();
141:
142: if (!close) {
143: m_tagStack.add(tag);
144: }
145: }
146:
147: /**
148: * Print a closed tag with attributes. The tag will be followed by a
149: * newline.
150: * @param tag the tag name
151: * @param names the names of the attributes
152: * @param values the values of the attributes
153: * @param nattr the number of attributes
154: */
155: public void tag(String tag, String[] names, String[] values,
156: int nattr) {
157: tag(tag, names, values, nattr, true);
158: }
159:
160: /**
161: * Print a start tag with attributes. The tag will be followed by a
162: * newline, and the indentation level will be increased.
163: * @param tag the tag name
164: * @param names the names of the attributes
165: * @param values the values of the attributes
166: * @param nattr the number of attributes
167: */
168: public void start(String tag, String[] names, String[] values,
169: int nattr) {
170: tag(tag, names, values, nattr, false);
171: }
172:
173: /**
174: * Internal method for printing a tag with a single attribute.
175: * @param tag the tag name
176: * @param name the name of the attribute
177: * @param value the value of the attribute
178: * @param close true to close the tag, false to leave it
179: * open and adjust the spacing
180: */
181: protected void tag(String tag, String name, String value,
182: boolean close) {
183: spacing();
184: m_out.print('<');
185: m_out.print(tag);
186: m_out.print(' ');
187: m_out.print(name);
188: m_out.print('=');
189: m_out.print('\"');
190: escapeString(value);
191: m_out.print('\"');
192: if (close)
193: m_out.print('/');
194: m_out.print('>');
195: println();
196:
197: if (!close) {
198: m_tagStack.add(tag);
199: }
200: }
201:
202: /**
203: * Print a closed tag with one attribute. The tag will be followed by a
204: * newline.
205: * @param tag the tag name
206: * @param name the name of the attribute
207: * @param value the value of the attribute
208: */
209: public void tag(String tag, String name, String value) {
210: tag(tag, name, value, true);
211: }
212:
213: /**
214: * Print a start tag with one attribute. The tag will be followed by a
215: * newline, and the indentation level will be increased.
216: * @param tag the tag name
217: * @param name the name of the attribute
218: * @param value the value of the attribute
219: */
220: public void start(String tag, String name, String value) {
221: tag(tag, name, value, false);
222: }
223:
224: /**
225: * Internal method for printing a tag with attributes.
226: * @param tag the tag name
227: * @param names the names of the attributes
228: * @param values the values of the attributes
229: * @param nattr the number of attributes
230: * @param close true to close the tag, false to leave it
231: * open and adjust the spacing
232: */
233: protected void tag(String tag, ArrayList names, ArrayList values,
234: int nattr, boolean close) {
235: spacing();
236: m_out.print('<');
237: m_out.print(tag);
238: for (int i = 0; i < nattr; ++i) {
239: m_out.print(' ');
240: m_out.print((String) names.get(i));
241: m_out.print('=');
242: m_out.print('\"');
243: escapeString((String) values.get(i));
244: m_out.print('\"');
245: }
246: if (close)
247: m_out.print('/');
248: m_out.print('>');
249: println();
250:
251: if (!close) {
252: m_tagStack.add(tag);
253: }
254: }
255:
256: /**
257: * Print a closed tag with attributes. The tag will be followed by a
258: * newline.
259: * @param tag the tag name
260: * @param names the names of the attributes
261: * @param values the values of the attributes
262: * @param nattr the number of attributes
263: */
264: public void tag(String tag, ArrayList names, ArrayList values,
265: int nattr) {
266: tag(tag, names, values, nattr, true);
267: }
268:
269: /**
270: * Print a start tag with attributes. The tag will be followed by a
271: * newline, and the indentation level will be increased.
272: * @param tag the tag name
273: * @param names the names of the attributes
274: * @param values the values of the attributes
275: * @param nattr the number of attributes
276: */
277: public void start(String tag, ArrayList names, ArrayList values,
278: int nattr) {
279: tag(tag, names, values, nattr, false);
280: }
281:
282: /**
283: * Print a start tag without attributes. The tag will be followed by a
284: * newline, and the indentation level will be increased.
285: * @param tag the tag name
286: */
287: public void start(String tag) {
288: tag(tag, (String[]) null, null, 0, false);
289: }
290:
291: /**
292: * Close the most recently opened tag. The tag will be followed by a
293: * newline, and the indentation level will be decreased.
294: */
295: public void end() {
296: String tag = (String) m_tagStack.remove(m_tagStack.size() - 1);
297: spacing();
298: m_out.print('<');
299: m_out.print('/');
300: m_out.print(tag);
301: m_out.print('>');
302: println();
303: }
304:
305: /**
306: * Print a new content tag with a single attribute, consisting of an
307: * open tag, content text, and a closing tag, all on one line.
308: * @param tag the tag name
309: * @param name the name of the attribute
310: * @param value the value of the attribute, this text will be escaped
311: * @param content the text content, this text will be escaped
312: */
313: public void contentTag(String tag, String name, String value,
314: String content) {
315: spacing();
316: m_out.print('<');
317: m_out.print(tag);
318: m_out.print(' ');
319: m_out.print(name);
320: m_out.print('=');
321: m_out.print('\"');
322: escapeString(value);
323: m_out.print('\"');
324: m_out.print('>');
325: escapeString(content);
326: m_out.print('<');
327: m_out.print('/');
328: m_out.print(tag);
329: m_out.print('>');
330: println();
331: }
332:
333: /**
334: * Print a new content tag with no attributes, consisting of an
335: * open tag, content text, and a closing tag, all on one line.
336: * @param tag the tag name
337: * @param content the text content, this text will be escaped
338: */
339: public void contentTag(String tag, String content) {
340: spacing();
341: m_out.print('<');
342: m_out.print(tag);
343: m_out.print('>');
344: escapeString(content);
345: m_out.print('<');
346: m_out.print('/');
347: m_out.print(tag);
348: m_out.print('>');
349: println();
350: }
351:
352: /**
353: * Print content text.
354: * @param content the content text, this text will be escaped
355: */
356: public void content(String content) {
357: escapeString(content);
358: }
359:
360: /**
361: * Finish the XML document.
362: */
363: public void finish() {
364: m_bias = 0;
365: m_out.flush();
366: }
367:
368: /**
369: * Finish the XML document, printing the given footer text at the
370: * end of the document.
371: * @param footer the footer text, this will not be escaped
372: */
373: public void finish(String footer) {
374: m_bias = 0;
375: m_out.print(footer);
376: m_out.flush();
377: }
378:
379: /**
380: * Print the current spacing (determined by the indentation level)
381: * into the document. This method is used by many of the other
382: * formatting methods, and so should only need to be called in
383: * the case of custom text printing outside the mechanisms
384: * provided by this class.
385: */
386: public void spacing() {
387: int len = m_bias + m_tagStack.size() * m_tab;
388: for (int i = 0; i < len; ++i)
389: m_out.print(' ');
390: }
391:
392: // ------------------------------------------------------------------------
393: // Escape Text
394:
395: // unicode ranges and valid/invalid characters
396: private static final char LOWER_RANGE = 0x20;
397: private static final char UPPER_RANGE = 0x7f;
398: private static final char[] VALID_CHARS = { 0x9, 0xA, 0xD };
399:
400: private static final char[] INVALID = { '<', '>', '"', '\'', '&' };
401: private static final String[] VALID = { "<", ">", """,
402: "'", "&" };
403:
404: /**
405: * Escape a string such that it is safe to use in an XML document.
406: * @param str the string to escape
407: */
408: protected void escapeString(String str) {
409: if (str == null) {
410: m_out.print("null");
411: return;
412: }
413:
414: int len = str.length();
415: for (int i = 0; i < len; ++i) {
416: char c = str.charAt(i);
417:
418: if ((c < LOWER_RANGE && c != VALID_CHARS[0]
419: && c != VALID_CHARS[1] && c != VALID_CHARS[2])
420: || (c > UPPER_RANGE)) {
421: // character out of range, escape with character value
422: m_out.print("&#");
423: m_out.print(Integer.toString(c));
424: m_out.print(';');
425: } else {
426: boolean valid = true;
427: // check for invalid characters (e.g., "<", "&", etc)
428: for (int j = INVALID.length - 1; j >= 0; --j) {
429: if (INVALID[j] == c) {
430: valid = false;
431: m_out.print(VALID[j]);
432: break;
433: }
434: }
435: // if character is valid, don't escape
436: if (valid) {
437: m_out.print(c);
438: }
439: }
440: }
441: }
442:
443: } // end of class XMLWriter
|