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 XmlWriter {
051:
052: private static final boolean shouldPrettyprint = false;
053:
054: private BufferedWriter out;
055:
056: private XmlWriter(OutputStreamWriter w, boolean declare)
057: 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 XmlWriter(OutputStream out, String enc, boolean declare)
097: throws UnsupportedEncodingException, IOException {
098: this (new OutputStreamWriter(out, enc), declare);
099: }
100:
101: /**
102: * Creates a new writer that will write to the given byte-output stream
103: * using the given encoding. An initial XML declaration will be written to
104: * the stream. </p>
105: *
106: * @param out
107: * The target byte-output stream
108: *
109: * @param enc
110: * The character encoding to be used
111: *
112: * @throws IOException
113: * If an I/O error occurs
114: *
115: * @throws UnsupportedEncodingException
116: * If the named encoding is not supported
117: */
118: public XmlWriter(OutputStream out, String enc)
119: throws UnsupportedEncodingException, IOException {
120: this (new OutputStreamWriter(out, enc), true);
121: }
122:
123: /**
124: * Creates a new writer that will write to the given byte-output stream
125: * using the UTF-8 encoding. An initial XML declaration will be written to
126: * the stream. </p>
127: *
128: * @param out
129: * The target byte-output stream
130: *
131: * @throws IOException
132: * If an I/O error occurs
133: */
134: public XmlWriter(OutputStream out) throws IOException {
135: this (new OutputStreamWriter(out, "UTF-8"), true);
136: }
137:
138: private char quoteChar = '"';
139:
140: /**
141: * Sets the quote character to be used by this writer when writing
142: * attribute values. </p>
143: *
144: * @param quote The new quote character, either a
145: * <small>QUOTATION MARK</small> (<tt>'\u0022'</tt>),
146: * or an <small>APOSTROPHE-QUOTE</small>
147: * (<tt>'\u0027'</tt>)
148: *
149: * @throws IllegalArgumentException
150: * If the argument is neither of the above characters
151: */
152: public void setQuote(char quote) {
153: if (quote != '"' && quote != '\'')
154: throw new IllegalArgumentException(
155: "Illegal quote character: " + quote);
156: quoteChar = quote;
157: }
158:
159: // Quote a character
160: private void quote(char c) throws IOException {
161: switch (c) {
162: case '&':
163: out.write("&");
164: break;
165: case '<':
166: out.write("<");
167: break;
168: case '>':
169: out.write(">");
170: break;
171: default:
172: out.write(c);
173: break;
174: }
175: }
176:
177: //
178: private void nonQuote(char c) throws IOException {
179: out.write(c);
180: }
181:
182: // Quote a character in an attribute value
183: private void aquote(char c) throws IOException {
184: switch (c) {
185: case '\'':
186: if (quoteChar == c)
187: out.write("'");
188: else
189: out.write(c);
190: break;
191: case '"':
192: if (quoteChar == c)
193: out.write(""");
194: else
195: out.write(c);
196: break;
197: default:
198: quote(c);
199: break;
200: }
201: }
202:
203: // Quote a string containing character data
204: private void quote(String s) throws IOException {
205: for (int i = 0; i < s.length(); i++)
206: quote(s.charAt(i));
207: }
208:
209: /* Allowing support for CDATA */
210: private void nonQuote(String s) throws IOException {
211: for (int i = 0; i < s.length(); i++)
212: nonQuote(s.charAt(i));
213: }
214:
215: // Quote a string containing an attribute value
216: private void aquote(String s) throws IOException {
217: for (int i = 0; i < s.length(); i++)
218: aquote(s.charAt(i));
219: }
220:
221: private void indent(int depth) throws IOException {
222: for (int i = 0; i < depth; i++)
223: out.write(" ");
224: }
225:
226: // Formatting state
227: private int depth = 0;
228: private boolean inStart = false;
229: private boolean needNewline = false;
230: private boolean writtenChars = false;
231: private boolean inAttribute = false;
232: private boolean inAttributeValue = false;
233:
234: /**
235: * Writes a DOCTYPE declaration. </p>
236: *
237: * @param root The name of the root element
238: *
239: * @param dtd The URI of the document-type definition
240: *
241: * @throws IOException
242: * If an I/O error occurs
243: */
244: public void doctype(String root, String dtd) throws IOException {
245: if (shouldPrettyprint && needNewline)
246: out.newLine();
247: needNewline = true;
248: out.write("<!DOCTYPE " + root + " SYSTEM " + quoteChar);
249: quote(dtd);
250: out.write(quoteChar + ">");
251: if (shouldPrettyprint)
252: out.newLine();
253: }
254:
255: private void start0(String name) throws IOException {
256: finishStart();
257: if (shouldPrettyprint && !writtenChars) {
258: needNewline = true;
259: indent(depth);
260: }
261: out.write('<');
262: out.write(name);
263: inStart = true;
264: writtenChars = false;
265: depth++;
266: }
267:
268: private void start1(String name) throws IOException {
269: finishStart();
270: if (shouldPrettyprint && !writtenChars) {
271: if (needNewline)
272: out.newLine();
273: needNewline = true;
274: indent(depth);
275: }
276: out.write('<');
277: out.write(name);
278: inStart = true;
279: writtenChars = false;
280: depth++;
281: }
282:
283: private void finishStart() throws IOException {
284: if (inStart) {
285: if (inAttribute)
286: out.write(quoteChar);
287: out.write('>');
288: inStart = false;
289: inAttribute = false;
290: inAttributeValue = false;
291: }
292: }
293:
294: /**
295: * Writes a start tag for the named element. </p>
296: *
297: * @param name The name to be used in the start tag
298: *
299: * @throws IOException
300: * If an I/O error occurs
301: */
302: public void start(String name) throws IOException {
303: start1(name);
304: }
305:
306: /**
307: * Writes an attribute for the current element. </p>
308: *
309: * @param name The attribute's name
310: *
311: * @param value The attribute's value
312: *
313: * @throws IllegalStateException
314: * If the previous method invoked upon this object was neither
315: * {@link #start start} nor {@link #attribute attribute}
316: *
317: * @throws IOException
318: * If an I/O error occurs
319: */
320: public void attribute(String name, String value) throws IOException {
321: attributeName(name);
322: attributeValue(value);
323: }
324:
325: /**
326: * Writes an attribute (unquoted) for the current element. </p>
327: *
328: * @param name The attribute's name
329: *
330: * @param value The attribute's value
331: *
332: * @throws IllegalStateException
333: * If the previous method invoked upon this object was neither
334: * {@link #start start} nor {@link #attribute attribute}
335: *
336: * @throws IOException
337: * If an I/O error occurs
338: */
339: public void attributeUnquoted(String name, String value)
340: throws IOException {
341: attributeName(name);
342: attributeValueUnquoted(value);
343: }
344:
345: /**
346: * Writes an attribute for the current element. </p>
347: *
348: * @param prefix The attribute's prefix
349: *
350: * @param name The attribute's name
351: *
352: * @param value The attribute's value
353: *
354: * @throws IllegalStateException
355: * If the previous method invoked upon this object was neither
356: * {@link #start start} nor {@link #attribute attribute}
357: *
358: * @throws IOException
359: * If an I/O error occurs
360: */
361: public void attribute(String prefix, String name, String value)
362: throws IOException {
363: attributeName(prefix, name);
364: attributeValue(value);
365: }
366:
367: /**
368: * Writes an attribute (unquoted) for the current element. </p>
369: *
370: * @param prefix The attribute's prefix
371: *
372: * @param name The attribute's name
373: *
374: * @param value The attribute's value
375: *
376: * @throws IllegalStateException
377: * If the previous method invoked upon this object was neither
378: * {@link #start start} nor {@link #attribute attribute}
379: *
380: * @throws IOException
381: * If an I/O error occurs
382: */
383: public void attributeUnquoted(String prefix, String name,
384: String value) throws IOException {
385: attributeName(prefix, name);
386: attributeValueUnquoted(value);
387: }
388:
389: /**
390: * Writes an attribute name for the current element. After invoking this
391: * method, invoke the {@link #attributeValue attributeValue} method to
392: * write the attribute value, or invoke the {@link #attributeValueToken
393: * attributeValueToken} method to write one or more space-separated value
394: * tokens. </p>
395: *
396: * @param name The attribute's name
397: *
398: * @throws IllegalStateException
399: * If the previous method invoked upon this object was neither
400: * {@link #start start} nor {@link #attribute attribute}
401: */
402: public void attributeName(String name) throws IOException {
403: if (!inStart)
404: throw new IllegalStateException();
405: if (inAttribute) {
406: out.write(quoteChar);
407: inAttribute = false;
408: inAttributeValue = false;
409: }
410: out.write(' ');
411: out.write(name);
412: out.write('=');
413: out.write(quoteChar);
414: inAttribute = true;
415: }
416:
417: /**
418: * Writes an attribute name for the current element. After invoking this
419: * method, invoke the {@link #attributeValue attributeValue} method to
420: * write the attribute value, or invoke the {@link #attributeValueToken
421: * attributeValueToken} method to write one or more space-separated value
422: * tokens. </p>
423: *
424: * @param prefix The attribute's prefix
425: * @param name The attribute's name
426: *
427: * @throws IllegalStateException
428: * If the previous method invoked upon this object was neither
429: * {@link #start start} nor {@link #attribute attribute}
430: */
431: public void attributeName(String prefix, String name)
432: throws IOException {
433: if (!inStart)
434: throw new IllegalStateException();
435: if (inAttribute) {
436: out.write(quoteChar);
437: inAttribute = false;
438: inAttributeValue = false;
439: }
440: out.write(' ');
441: out.write(prefix);
442: out.write(':');
443: out.write(name);
444: out.write('=');
445: out.write(quoteChar);
446: inAttribute = true;
447: }
448:
449: /**
450: * Writes a value for the current attribute. </p>
451: *
452: * @param value The attribute's value
453: *
454: * @throws IllegalStateException
455: * If the previous method invoked upon this object was not
456: * {@link #attributeName attributeName}
457: */
458: public void attributeValue(String value) throws IOException {
459: if (!inAttribute || inAttributeValue)
460: throw new IllegalStateException();
461: aquote(value);
462: out.write(quoteChar);
463: inAttribute = false;
464: }
465:
466: /**
467: * Writes a value (unquoted) for the current attribute. </p>
468: *
469: * @param value The attribute's value
470: *
471: * @throws IllegalStateException
472: * If the previous method invoked upon this object was not
473: * {@link #attributeName attributeName}
474: */
475: public void attributeValueUnquoted(String value) throws IOException {
476: if (!inAttribute || inAttributeValue)
477: throw new IllegalStateException();
478: out.write(value, 0, value.length());
479: out.write(quoteChar);
480: inAttribute = false;
481: }
482:
483: /**
484: * Writes one token of the current attribute's value. Adjacent tokens will
485: * be separated by single space characters. </p>
486: *
487: * @param token The token to be written
488: *
489: * @throws IllegalStateException
490: * If the previous method invoked upon this object was neither
491: * {@link #attributeName attributeName} nor
492: * {@link #attributeValueToken attributeValueToken}
493: */
494: public void attributeValueToken(String token) throws IOException {
495: if (!inAttribute)
496: throw new IllegalStateException();
497: if (inAttributeValue)
498: out.write(' ');
499: aquote(token);
500: inAttributeValue = true;
501: }
502:
503: /**
504: * Writes an end tag for the named element. </p>
505: *
506: * @param name The name to be used in the end tag
507: *
508: * @throws IOException
509: * If an I/O error occurs
510: */
511: public void end(String name) throws IOException {
512: if (inStart) {
513: if (inAttribute)
514: out.write(quoteChar);
515: out.write("/>");
516: inStart = false;
517: inAttribute = false;
518: inAttributeValue = false;
519: } else {
520: out.write("</");
521: out.write(name);
522: out.write('>');
523: }
524: depth--;
525: writtenChars = false;
526: }
527:
528: /**
529: * Writes some character data. </p>
530: *
531: * @param chars The character data to be written
532: *
533: * @throws IOException
534: * If an I/O error occurs
535: */
536: public void chars(String chars) throws IOException {
537: finishStart();
538: quote(chars);
539: writtenChars = true;
540: }
541:
542: public void chars(CDATA chars) throws IOException {
543: finishStart();
544: nonQuote(chars.getText());
545: writtenChars = true;
546: }
547:
548: /**
549: * Writes some character data, skipping quoting. </p>
550: *
551: * @param chars The character data to be written
552: *
553: * @throws IOException
554: * If an I/O error occurs
555: */
556: public void charsUnquoted(String chars) throws IOException {
557: finishStart();
558: out.write(chars, 0, chars.length());
559: writtenChars = true;
560: }
561:
562: /**
563: * Writes some character data, skipping quoting. </p>
564: *
565: * @param buf Buffer containing the character data to be written
566: * @param off The offset of the data to be written
567: * @param len The length of the data to be written
568: *
569: * @throws IOException
570: * If an I/O error occurs
571: */
572: public void charsUnquoted(char[] buf, int off, int len)
573: throws IOException {
574: finishStart();
575: out.write(buf, off, len);
576: writtenChars = true;
577: }
578:
579: /**
580: * Writes a leaf element with the given character content. </p>
581: *
582: * @param name The name to be used in the start and end tags
583: *
584: * @param chars The character data to be written
585: *
586: * <p> This method writes a start tag with the given name, followed by the
587: * given character data, followed by an end tag. If the <tt>chars</tt>
588: * parameter is <tt>null</tt> or the empty string then an empty tag is
589: * written. </p>
590: *
591: * @throws IOException
592: * If an I/O error occurs
593: */
594: public void leaf(String name, String chars) throws IOException {
595: start1(name);
596: if ((chars != null) && (chars.length() != 0))
597: chars(chars);
598: end(name);
599: }
600:
601: public void inlineLeaf(String name, String chars)
602: throws IOException {
603: start0(name);
604: if ((chars != null) && (chars.length() != 0))
605: chars(chars);
606: end(name);
607: }
608:
609: /**
610: * Writes an empty leaf element. </p>
611: *
612: * @param name name to be used in the empty-element tag
613: */
614: public void leaf(String name) throws IOException {
615: leaf(name, null);
616: }
617:
618: public void inlineLeaf(String name) throws IOException {
619: inlineLeaf(name, null);
620: }
621:
622: /**
623: * Flushes the writer. </p>
624: *
625: * @throws IOException
626: * If an I/O error occurs
627: */
628: public void flush() throws IOException {
629: if (depth != 0)
630: // throw new IllegalStateException("Nonzero depth");
631: // if (shouldPrettyprint)
632: out.newLine();
633: out.flush();
634: }
635:
636: /**
637: * Flushes the writer and closes the underlying byte-output stream. </p>
638: *
639: * @throws IOException
640: * If an I/O error occurs
641: */
642: public void close() throws IOException {
643: flush();
644: out.close();
645: }
646: }
|