001: /*
002: * Portions Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.tools.internal.ws.util.xml;
027:
028: import java.io.BufferedWriter;
029: import java.io.IOException;
030: import java.io.OutputStream;
031: import java.io.OutputStreamWriter;
032: import java.io.UnsupportedEncodingException;
033:
034: import com.sun.xml.internal.ws.util.xml.CDATA;
035:
036: // ## Delay IOExceptions until flush or close
037: // ## Need DOM, SAX output
038:
039: /**
040: * A writer of XML output streams.
041: *
042: * <p> An XML writer knows hardly anything about XML document well-formedness,
043: * to say nothing of validity. It relies upon the invoker to ensure that the
044: * generated document is well-formed and, if required, valid.
045: *
046: *
047: * @author WS Development Team
048: */
049:
050: public class PrettyPrintingXmlWriter {
051:
052: private static final boolean shouldPrettyprint = true;
053:
054: private BufferedWriter out;
055:
056: private PrettyPrintingXmlWriter(OutputStreamWriter w,
057: boolean declare) throws IOException {
058: // XXX-NOTE - set the buffer size to 1024 here
059: this .out = new BufferedWriter(w, 1024);
060: String enc = w.getEncoding();
061:
062: /* Work around bogus canonical encoding names */
063: if (enc.equals("UTF8"))
064: enc = "UTF-8";
065: else if (enc.equals("ASCII"))
066: enc = "US-ASCII";
067:
068: if (declare) {
069: out.write("<?xml version=\"1.0\" encoding=\"" + enc
070: + "\"?>");
071: out.newLine();
072: needNewline = true;
073: }
074: }
075:
076: /**
077: * Creates a new writer that will write to the given byte-output stream
078: * using the given encoding. An initial XML declaration will optionally be
079: * written to the stream. </p>
080: *
081: * @param out
082: * The target byte-output stream
083: *
084: * @param enc
085: * The character encoding to be used
086: *
087: * @param declare
088: * If <tt>true</tt>, write the XML declaration to the output stream
089: *
090: * @throws IOException
091: * If an I/O error occurs
092: *
093: * @throws UnsupportedEncodingException
094: * If the named encoding is not supported
095: */
096: public PrettyPrintingXmlWriter(OutputStream out, String enc,
097: boolean declare) throws UnsupportedEncodingException,
098: IOException {
099: this (new OutputStreamWriter(out, enc), declare);
100: }
101:
102: /**
103: * Creates a new writer that will write to the given byte-output stream
104: * using the given encoding. An initial XML declaration will be written to
105: * the stream. </p>
106: *
107: * @param out
108: * The target byte-output stream
109: *
110: * @param enc
111: * The character encoding to be used
112: *
113: * @throws IOException
114: * If an I/O error occurs
115: *
116: * @throws UnsupportedEncodingException
117: * If the named encoding is not supported
118: */
119: public PrettyPrintingXmlWriter(OutputStream out, String enc)
120: throws UnsupportedEncodingException, IOException {
121: this (new OutputStreamWriter(out, enc), true);
122: }
123:
124: /**
125: * Creates a new writer that will write to the given byte-output stream
126: * using the UTF-8 encoding. An initial XML declaration will be written to
127: * the stream. </p>
128: *
129: * @param out
130: * The target byte-output stream
131: *
132: * @throws IOException
133: * If an I/O error occurs
134: */
135: public PrettyPrintingXmlWriter(OutputStream out) throws IOException {
136: this (new OutputStreamWriter(out, "UTF-8"), true);
137: }
138:
139: private char quoteChar = '"';
140:
141: /**
142: * Sets the quote character to be used by this writer when writing
143: * attribute values. </p>
144: *
145: * @param quote The new quote character, either a
146: * <small>QUOTATION MARK</small> (<tt>'\u0022'</tt>),
147: * or an <small>APOSTROPHE-QUOTE</small>
148: * (<tt>'\u0027'</tt>)
149: *
150: * @throws IllegalArgumentException
151: * If the argument is neither of the above characters
152: */
153: public void setQuote(char quote) {
154: if (quote != '"' && quote != '\'')
155: throw new IllegalArgumentException(
156: "Illegal quote character: " + quote);
157: quoteChar = quote;
158: }
159:
160: // Quote a character
161: private void quote(char c) throws IOException {
162: switch (c) {
163: case '&':
164: out.write("&");
165: break;
166: case '<':
167: out.write("<");
168: break;
169: case '>':
170: out.write(">");
171: break;
172: default:
173: out.write(c);
174: break;
175: }
176: }
177:
178: // Quote a character in an attribute value
179: private void aquote(char c) throws IOException {
180: switch (c) {
181: case '\'':
182: if (quoteChar == c)
183: out.write("'");
184: else
185: out.write(c);
186: break;
187: case '"':
188: if (quoteChar == c)
189: out.write(""");
190: else
191: out.write(c);
192: break;
193: default:
194: quote(c);
195: break;
196: }
197: }
198:
199: //
200: private void nonQuote(char c) throws IOException {
201: out.write(c);
202: }
203:
204: // Quote a string containing character data
205: private void quote(String s) throws IOException {
206: for (int i = 0; i < s.length(); i++)
207: quote(s.charAt(i));
208: }
209:
210: /* Allowing support for CDATA */
211: private void nonQuote(String s) throws IOException {
212: for (int i = 0; i < s.length(); i++)
213: nonQuote(s.charAt(i));
214: }
215:
216: // Quote a string containing an attribute value
217: private void aquote(String s) throws IOException {
218: for (int i = 0; i < s.length(); i++)
219: aquote(s.charAt(i));
220: }
221:
222: private void indent(int depth) throws IOException {
223: for (int i = 0; i < depth; i++)
224: out.write(" ");
225: }
226:
227: // Formatting state
228: private int depth = 0;
229: private boolean inStart = false;
230: private boolean needNewline = false;
231: private boolean writtenChars = false;
232: private boolean inAttribute = false;
233: private boolean inAttributeValue = false;
234:
235: /**
236: * Writes a DOCTYPE declaration. </p>
237: *
238: * @param root The name of the root element
239: *
240: * @param dtd The URI of the document-type definition
241: *
242: * @throws IOException
243: * If an I/O error occurs
244: */
245: public void doctype(String root, String dtd) throws IOException {
246: if (shouldPrettyprint && needNewline)
247: out.newLine();
248: needNewline = true;
249: out.write("<!DOCTYPE " + root + " SYSTEM " + quoteChar);
250: quote(dtd);
251: out.write(quoteChar + ">");
252: if (shouldPrettyprint)
253: out.newLine();
254: }
255:
256: private void start0(String name) throws IOException {
257: finishStart();
258: if (shouldPrettyprint && !writtenChars) {
259: needNewline = true;
260: indent(depth);
261: }
262: out.write('<');
263: out.write(name);
264: inStart = true;
265: writtenChars = false;
266: depth++;
267: }
268:
269: private void start1(String name) throws IOException {
270: finishStart();
271: if (shouldPrettyprint && !writtenChars) {
272: if (needNewline)
273: out.newLine();
274: needNewline = true;
275: indent(depth);
276: }
277: out.write('<');
278: out.write(name);
279: inStart = true;
280: writtenChars = false;
281: depth++;
282: }
283:
284: private void finishStart() throws IOException {
285: if (inStart) {
286: if (inAttribute)
287: out.write(quoteChar);
288: out.write('>');
289: inStart = false;
290: inAttribute = false;
291: inAttributeValue = false;
292: }
293: }
294:
295: /**
296: * Writes a start tag for the named element. </p>
297: *
298: * @param name The name to be used in the start tag
299: *
300: * @throws IOException
301: * If an I/O error occurs
302: */
303: public void start(String name) throws IOException {
304: start1(name);
305: }
306:
307: /**
308: * Writes an attribute for the current element. </p>
309: *
310: * @param name The attribute's name
311: *
312: * @param value The attribute's value
313: *
314: * @throws IllegalStateException
315: * If the previous method invoked upon this object was neither
316: * {@link #start start} nor {@link #attribute attribute}
317: *
318: * @throws IOException
319: * If an I/O error occurs
320: */
321: public void attribute(String name, String value) throws IOException {
322: attributeName(name);
323: attributeValue(value);
324: }
325:
326: /**
327: * Writes an attribute (unquoted) for the current element. </p>
328: *
329: * @param name The attribute's name
330: *
331: * @param value The attribute's value
332: *
333: * @throws IllegalStateException
334: * If the previous method invoked upon this object was neither
335: * {@link #start start} nor {@link #attribute attribute}
336: *
337: * @throws IOException
338: * If an I/O error occurs
339: */
340: public void attributeUnquoted(String name, String value)
341: throws IOException {
342: attributeName(name);
343: attributeValueUnquoted(value);
344: }
345:
346: /**
347: * Writes an attribute for the current element. </p>
348: *
349: * @param prefix The attribute's prefix
350: *
351: * @param name The attribute's name
352: *
353: * @param value The attribute's value
354: *
355: * @throws IllegalStateException
356: * If the previous method invoked upon this object was neither
357: * {@link #start start} nor {@link #attribute attribute}
358: *
359: * @throws IOException
360: * If an I/O error occurs
361: */
362: public void attribute(String prefix, String name, String value)
363: throws IOException {
364: attributeName(prefix, name);
365: attributeValue(value);
366: }
367:
368: /**
369: * Writes an attribute (unquoted) for the current element. </p>
370: *
371: * @param prefix The attribute's prefix
372: *
373: * @param name The attribute's name
374: *
375: * @param value The attribute's value
376: *
377: * @throws IllegalStateException
378: * If the previous method invoked upon this object was neither
379: * {@link #start start} nor {@link #attribute attribute}
380: *
381: * @throws IOException
382: * If an I/O error occurs
383: */
384: public void attributeUnquoted(String prefix, String name,
385: String value) throws IOException {
386: attributeName(prefix, name);
387: attributeValueUnquoted(value);
388: }
389:
390: /**
391: * Writes an attribute name for the current element. After invoking this
392: * method, invoke the {@link #attributeValue attributeValue} method to
393: * write the attribute value, or invoke the {@link #attributeValueToken
394: * attributeValueToken} method to write one or more space-separated value
395: * tokens. </p>
396: *
397: * @param name The attribute's name
398: *
399: * @throws IllegalStateException
400: * If the previous method invoked upon this object was neither
401: * {@link #start start} nor {@link #attribute attribute}
402: */
403: public void attributeName(String name) throws IOException {
404: if (!inStart)
405: throw new IllegalStateException();
406: if (inAttribute) {
407: out.write(quoteChar);
408: inAttribute = false;
409: inAttributeValue = false;
410: }
411: out.write(' ');
412: out.write(name);
413: out.write('=');
414: out.write(quoteChar);
415: inAttribute = true;
416: }
417:
418: /**
419: * Writes an attribute name for the current element. After invoking this
420: * method, invoke the {@link #attributeValue attributeValue} method to
421: * write the attribute value, or invoke the {@link #attributeValueToken
422: * attributeValueToken} method to write one or more space-separated value
423: * tokens. </p>
424: *
425: * @param prefix The attribute's prefix
426: * @param name The attribute's name
427: *
428: * @throws IllegalStateException
429: * If the previous method invoked upon this object was neither
430: * {@link #start start} nor {@link #attribute attribute}
431: */
432: public void attributeName(String prefix, String name)
433: throws IOException {
434: if (!inStart)
435: throw new IllegalStateException();
436: if (inAttribute) {
437: out.write(quoteChar);
438: inAttribute = false;
439: inAttributeValue = false;
440: }
441: out.write(' ');
442: out.write(prefix);
443: out.write(':');
444: out.write(name);
445: out.write('=');
446: out.write(quoteChar);
447: inAttribute = true;
448: }
449:
450: /**
451: * Writes a value for the current attribute. </p>
452: *
453: * @param value The attribute's value
454: *
455: * @throws IllegalStateException
456: * If the previous method invoked upon this object was not
457: * {@link #attributeName attributeName}
458: */
459: public void attributeValue(String value) throws IOException {
460: if (!inAttribute || inAttributeValue)
461: throw new IllegalStateException();
462: aquote(value);
463: out.write(quoteChar);
464: inAttribute = false;
465: }
466:
467: /**
468: * Writes a value (unquoted) for the current attribute. </p>
469: *
470: * @param value The attribute's value
471: *
472: * @throws IllegalStateException
473: * If the previous method invoked upon this object was not
474: * {@link #attributeName attributeName}
475: */
476: public void attributeValueUnquoted(String value) throws IOException {
477: if (!inAttribute || inAttributeValue)
478: throw new IllegalStateException();
479: out.write(value, 0, value.length());
480: out.write(quoteChar);
481: inAttribute = false;
482: }
483:
484: /**
485: * Writes one token of the current attribute's value. Adjacent tokens will
486: * be separated by single space characters. </p>
487: *
488: * @param token The token to be written
489: *
490: * @throws IllegalStateException
491: * If the previous method invoked upon this object was neither
492: * {@link #attributeName attributeName} nor
493: * {@link #attributeValueToken attributeValueToken}
494: */
495: public void attributeValueToken(String token) throws IOException {
496: if (!inAttribute)
497: throw new IllegalStateException();
498: if (inAttributeValue)
499: out.write(' ');
500: aquote(token);
501: inAttributeValue = true;
502: }
503:
504: /**
505: * Writes an end tag for the named element. </p>
506: *
507: * @param name The name to be used in the end tag
508: *
509: * @throws IOException
510: * If an I/O error occurs
511: */
512: public void end(String name) throws IOException {
513: if (inStart) {
514: if (inAttribute)
515: out.write(quoteChar);
516: out.write("/>");
517: inStart = false;
518: inAttribute = false;
519: inAttributeValue = false;
520: } else {
521: out.write("</");
522: out.write(name);
523: out.write('>');
524: }
525: depth--;
526: writtenChars = false;
527: }
528:
529: /**
530: * Writes some character data. </p>
531: *
532: * @param chars The character data to be written
533: *
534: * @throws IOException
535: * If an I/O error occurs
536: */
537: public void chars(String chars) throws IOException {
538: finishStart();
539: quote(chars);
540: writtenChars = true;
541: }
542:
543: public void chars(CDATA chars) throws IOException {
544: finishStart();
545: nonQuote(chars.getText());
546: writtenChars = true;
547: }
548:
549: /**
550: * Writes some character data, skipping quoting. </p>
551: *
552: * @param chars The character data to be written
553: *
554: * @throws IOException
555: * If an I/O error occurs
556: */
557: public void charsUnquoted(String chars) throws IOException {
558: finishStart();
559: out.write(chars, 0, chars.length());
560: writtenChars = true;
561: }
562:
563: /**
564: * Writes some character data, skipping quoting. </p>
565: *
566: * @param buf Buffer containing the character data to be written
567: * @param off The offset of the data to be written
568: * @param len The length of the data to be written
569: *
570: * @throws IOException
571: * If an I/O error occurs
572: */
573: public void charsUnquoted(char[] buf, int off, int len)
574: throws IOException {
575: finishStart();
576: out.write(buf, off, len);
577: writtenChars = true;
578: }
579:
580: /**
581: * Writes a leaf element with the given character content. </p>
582: *
583: * @param name ame to be used in the start and end tags
584: *
585: * @param chars character data to be written
586: *
587: * <p> This method writes a start tag with the given name, followed by the
588: * given character data, followed by an end tag. If the <tt>chars</tt>
589: * parameter is <tt>null</tt> or the empty string then an empty tag is
590: * written. </p>
591: *
592: * @throws IOException
593: * If an I/O error occurs
594: */
595: public void leaf(String name, String chars) throws IOException {
596: start1(name);
597: if ((chars != null) && (chars.length() != 0))
598: chars(chars);
599: end(name);
600: }
601:
602: public void inlineLeaf(String name, String chars)
603: throws IOException {
604: start0(name);
605: if ((chars != null) && (chars.length() != 0))
606: chars(chars);
607: end(name);
608: }
609:
610: /**
611: * Writes an empty leaf element. </p>
612: *
613: * @param name name to be used in the empty-element tag
614: */
615: public void leaf(String name) throws IOException {
616: leaf(name, null);
617: }
618:
619: public void inlineLeaf(String name) throws IOException {
620: inlineLeaf(name, null);
621: }
622:
623: /**
624: * Flushes the writer. </p>
625: *
626: * @throws IOException
627: * If an I/O error occurs
628: */
629: public void flush() throws IOException {
630: if (depth != 0)
631: throw new IllegalStateException("Nonzero depth");
632: // if (shouldPrettyprint)
633: out.newLine();
634: out.flush();
635: }
636:
637: /**
638: * Flushes the writer and closes the underlying byte-output stream. </p>
639: *
640: * @throws IOException
641: * If an I/O error occurs
642: */
643: public void close() throws IOException {
644: flush();
645: out.close();
646: }
647: }
|