001: /*
002: * Copyright (c) 1998-2005 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Sam
027: */
028:
029: package com.caucho.vfs;
030:
031: import java.io.PrintWriter;
032: import java.io.Writer;
033: import java.util.HashMap;
034:
035: // TODO: get rid of set/getContentType, use getStrategyForContentType(...) and setStrategy(...)
036: // TODO: trap all print(...) for xml escaping, indenting
037: // TODO: capture all \n and \r\n and make them do println()
038: // TODO: clean up flags now that patterns are known
039: // TODO: CDATA
040: // TODO: startDocument, endDocument (<?xml with charset and DOCTYPE)
041: // TODO: li review (including test case), because of bug in some browser (dt,dd?)
042: public class XmlWriter extends PrintWriter {
043: public final static Strategy XML = new Xml();
044: public final static Strategy XHTML = new Xhtml();
045: public final static Strategy HTML = new Html();
046:
047: private boolean _isIndenting = false;
048: private int _indent = 0;
049:
050: private boolean _isElementOpen;
051: private boolean _isElementOpenNeedsNewline;
052: private String _openElementName;
053:
054: private Strategy _strategy = XML;
055: private String _contentType = "text/xml";
056: private String _characterEncoding;
057: private boolean _isNewLine = true;
058:
059: public XmlWriter(Writer out) {
060: super (out);
061: }
062:
063: public String getContentType() {
064: return _contentType;
065: }
066:
067: /**
068: * Default is "text/xml".
069: */
070: public void setContentType(String contentType) {
071: _contentType = contentType;
072:
073: if (_contentType.equals("text/xml"))
074: _strategy = XML;
075: if (_contentType.equals("application/xml"))
076: _strategy = XML;
077: else if (_contentType.equals("text/xhtml"))
078: _strategy = XHTML;
079: else if (_contentType.equals("application/xhtml+xml"))
080: _strategy = XHTML;
081: else if (_contentType.equals("text/html"))
082: _strategy = HTML;
083: else
084: _strategy = XML;
085: }
086:
087: public void setStrategy(Strategy strategy) {
088: _strategy = strategy;
089: }
090:
091: public void setIndenting(boolean isIndenting) {
092: _isIndenting = isIndenting;
093: }
094:
095: public boolean isIndenting() {
096: return _isIndenting;
097: }
098:
099: /**
100: * Default is "UTF-8".
101: */
102: public void setCharacterEncoding(String characterEncoding) {
103: _characterEncoding = characterEncoding;
104: }
105:
106: public String getCharacterEncoding() {
107: return _characterEncoding;
108: }
109:
110: private boolean closeElementIfNeeded(boolean isEnd) {
111: if (_isElementOpen) {
112: _isElementOpen = false;
113:
114: _strategy.closeElement(this , _openElementName, isEnd);
115:
116: if (_isElementOpenNeedsNewline) {
117: _isElementOpenNeedsNewline = false;
118: softPrintln();
119: }
120:
121: return true;
122: }
123:
124: return false;
125: }
126:
127: private void startElement(String name, boolean isLineBefore,
128: boolean isLineAfter) {
129: closeElementIfNeeded(false);
130:
131: if (isLineBefore)
132: softPrintln();
133:
134: _openElementName = name;
135:
136: _strategy.openElement(this , name);
137: _isElementOpen = true;
138: _isElementOpenNeedsNewline = isLineAfter;
139:
140: if (_isIndenting)
141: _indent++;
142: }
143:
144: private void endElement(String name, boolean isLineBefore,
145: boolean isLineAfter) {
146: if (_isIndenting)
147: _indent--;
148:
149: if (!closeElementIfNeeded(true)) {
150: if (isLineBefore)
151: softPrintln();
152:
153: _strategy.endElement(this , name);
154: }
155:
156: if (isLineAfter)
157: softPrintln();
158: }
159:
160: /**
161: * Start an element.
162: */
163: public void startElement(String name) {
164: startElement(name, false, false);
165: }
166:
167: /**
168: * End an element.
169: */
170: public void endElement(String name) {
171: endElement(name, false, false);
172: }
173:
174: /**
175: * Start an element where the opening tag is on it's own line, the content
176: * is on it's own line, and the closing tag is on it's own line.
177: */
178: public void startBlockElement(String name) {
179: startElement(name, true, true);
180: }
181:
182: /**
183: * End an element where the opening tag is on it's own line, the content
184: * is on it's own line, and the closing tag is on it's own line.
185: */
186: public void endBlockElement(String name) {
187: endElement(name, true, true);
188: }
189:
190: /**
191: * Start an element where the opening tag, content, and closing tags are on
192: * a single line of their own.
193: */
194: public void startLineElement(String name) {
195: startElement(name, true, false);
196: }
197:
198: /**
199: * End an element where the opening tag, content, and closing tags are on
200: * a single line of their own.
201: */
202: public void endLineElement(String name) {
203: endElement(name, false, true);
204: }
205:
206: /**
207: * Convenience method, same as doing a startElement() and then immediately
208: * doing an endElement().
209: */
210: public void writeElement(String name) {
211: startElement(name);
212: endElement(name);
213: }
214:
215: /**
216: * Convenience method, same as doing a startLineElement() and then immediately
217: * doing an endLineElement().
218: */
219: public void writeLineElement(String name) {
220: startLineElement(name);
221: endLineElement(name);
222: }
223:
224: /**
225: * Convenience method, same as doing a startBlockElement() and then immediately
226: * doing an endBlockElement().
227: */
228: public void writeBlockElement(String name) {
229: startBlockElement(name);
230: endBlockElement(name);
231: }
232:
233: /**
234: * Convenience method, same as doing a startElement(), writeText(text),
235: * endElement().
236: */
237: public void writeElement(String name, Object text) {
238: startElement(name);
239: writeText(text);
240: endElement(name);
241: }
242:
243: /**
244: * Convenience method, same as doing a startLineElement(), writeText(text),
245: * endLineElement().
246: */
247: public void writeLineElement(String name, Object text) {
248: startLineElement(name);
249: writeText(text);
250: endLineElement(name);
251: }
252:
253: /**
254: * Convenience method, same as doing a startBlockElement(), writeText(text),
255: * endBlockElement().
256: */
257: public void writeBlockElement(String name, Object text) {
258: startBlockElement(name);
259: writeText(text);
260: endBlockElement(name);
261: }
262:
263: /**
264: * Write an attribute with a value, if value is null nothing is written.
265: *
266: * @throws IllegalStateException if the is no element is open
267: */
268: public void writeAttribute(String name, Object value) {
269: if (!_isElementOpen)
270: throw new IllegalStateException("no open element");
271:
272: if (value == null)
273: return;
274:
275: _isElementOpen = false;
276: try {
277: _strategy.writeAttribute(this , name, value);
278: } finally {
279: _isElementOpen = true;
280: }
281:
282: }
283:
284: /**
285: * Write an attribute with multiple values, separated by space, if a value
286: * is null nothing is written.
287: *
288: * @throws IllegalStateException if the is no element is open
289: */
290: public void writeAttribute(String name, Object... values) {
291: if (!_isElementOpen)
292: throw new IllegalStateException("no open element");
293:
294: _isElementOpen = false;
295:
296: try {
297: _strategy.writeAttribute(this , name, values);
298: } finally {
299: _isElementOpen = true;
300: }
301:
302: }
303:
304: /**
305: * Close an open element (if any), then write with escaping as needed.
306: */
307: public void writeText(char ch) {
308: closeElementIfNeeded(false);
309: writeIndentIfNewLine();
310: _strategy.writeText(this , ch);
311: }
312:
313: /**
314: * Close an open element (if any), then write with escaping as needed.
315: */
316: public void writeText(char[] buf) {
317: closeElementIfNeeded(false);
318: writeIndentIfNewLine();
319:
320: _strategy.writeText(this , buf);
321: }
322:
323: /**
324: * Close an open element (if any), then write with escaping as needed.
325: */
326: public void writeText(char[] buf, int offset, int length) {
327: closeElementIfNeeded(false);
328: writeIndentIfNewLine();
329: _strategy.writeText(this , buf, offset, length);
330: }
331:
332: /**
333: * Close an open element (if any), then write object.toString(), with escaping
334: * as needed.
335: */
336: public void writeText(Object obj) {
337: closeElementIfNeeded(false);
338: writeIndentIfNewLine();
339: _strategy.writeTextObject(this , obj);
340: }
341:
342: /**
343: * Close an open element (if any), then write with escaping as needed.
344: */
345: public void writeComment(String comment) {
346: closeElementIfNeeded(false);
347: writeIndentIfNewLine();
348:
349: _strategy.writeComment(this , comment);
350: }
351:
352: /**
353: * Close an open element (if any), then flush the underlying
354: * writer.
355: */
356: public void flush() {
357: closeElementIfNeeded(true);
358: super .flush();
359: }
360:
361: public void println() {
362: closeElementIfNeeded(false);
363: super .println();
364: _isNewLine = true;
365: }
366:
367: public boolean isNewLine() {
368: return _isNewLine;
369: }
370:
371: public boolean softPrintln() {
372: if (!isNewLine()) {
373: println();
374: return true;
375: } else
376: return false;
377: }
378:
379: public void write(int ch) {
380: closeElementIfNeeded(false);
381: _isNewLine = false;
382: super .write(ch);
383: }
384:
385: public void write(char buf[], int off, int len) {
386: closeElementIfNeeded(false);
387: _isNewLine = false;
388: super .write(buf, off, len);
389: }
390:
391: public void write(char buf[]) {
392: closeElementIfNeeded(false);
393: _isNewLine = false;
394: super .write(buf);
395: }
396:
397: public void write(String s, int off, int len) {
398: closeElementIfNeeded(false);
399: _isNewLine = false;
400: super .write(s, off, len);
401: }
402:
403: public void write(String s) {
404: closeElementIfNeeded(false);
405: _isNewLine = false;
406: super .write(s);
407: }
408:
409: static public abstract class Strategy {
410: abstract void openElement(XmlWriter writer, String name);
411:
412: abstract void closeElement(XmlWriter writer, String name,
413: boolean isEnd);
414:
415: abstract void endElement(XmlWriter writer, String name);
416:
417: abstract void writeAttribute(XmlWriter writer, String name,
418: Object value);
419:
420: abstract void writeAttribute(XmlWriter writer, String name,
421: Object... values);
422:
423: abstract void writeText(XmlWriter writer, char ch);
424:
425: abstract void writeText(XmlWriter writer, char[] buf);
426:
427: abstract void writeText(XmlWriter writer, char[] buf,
428: int offset, int length);
429:
430: abstract void writeTextObject(XmlWriter writer, Object obj);
431:
432: abstract void writeComment(XmlWriter writer, String comment);
433: }
434:
435: static public class Xml extends Strategy {
436: void openElement(XmlWriter writer, String name) {
437: writer.writeIndentIfNewLine();
438: writer.write('<');
439: writer.write(name);
440: }
441:
442: void closeElement(XmlWriter writer, String name, boolean isEnd) {
443: if (isEnd)
444: writer.write('/');
445:
446: writer.write('>');
447: }
448:
449: void endElement(XmlWriter writer, String name) {
450: writer.writeIndentIfNewLine();
451:
452: writer.write("</");
453: writer.write(name);
454: writer.write('>');
455: }
456:
457: void writeAttribute(XmlWriter writer, String name, Object value) {
458: writer.write(" ");
459: writer.write(name);
460: writer.write('=');
461: writer.write("'");
462: writeAttributeValue(writer, name, value);
463: writer.write("'");
464: }
465:
466: void writeAttribute(XmlWriter writer, String name,
467: Object... values) {
468: writer.write(" ");
469: writer.write(name);
470: writer.write('=');
471: writer.write("'");
472:
473: int len = values.length;
474:
475: for (int i = 0; i < len; i++) {
476: Object value = values[i];
477:
478: if (value == null)
479: continue;
480:
481: if (i > 0)
482: writer.write(' ');
483:
484: writeAttributeValue(writer, name, value);
485: }
486:
487: writer.write("'");
488: }
489:
490: protected void writeAttributeValue(XmlWriter writer,
491: String name, Object value) {
492: writeXmlEscaped(writer, value);
493: }
494:
495: public void writeText(XmlWriter writer, char ch) {
496: writeXmlEscapedChar(writer, ch);
497: }
498:
499: public void writeText(XmlWriter writer, char[] buf) {
500: int endIndex = buf.length;
501:
502: for (int i = 0; i < endIndex; i++) {
503: writeXmlEscapedChar(writer, buf[i]);
504: }
505: }
506:
507: public void writeText(XmlWriter writer, char[] buf, int offset,
508: int length) {
509: int endIndex = offset + length;
510:
511: for (int i = offset; i < endIndex; i++) {
512: writeXmlEscapedChar(writer, buf[i]);
513: }
514: }
515:
516: public void writeTextObject(XmlWriter writer, Object obj) {
517: String string = String.valueOf(obj);
518: int len = string.length();
519:
520: for (int i = 0; i < len; i++) {
521: writeXmlEscapedChar(writer, string.charAt(i));
522: }
523: }
524:
525: public void writeComment(XmlWriter writer, String comment) {
526: writer.write("<!-- ");
527: writeXmlEscaped(writer, comment);
528: writer.write(" -->");
529: }
530:
531: private void writeXmlEscapedChar(XmlWriter writer, char ch) {
532: switch (ch) {
533: case '<':
534: writer.write("<");
535: break;
536: case '>':
537: writer.write(">");
538: break;
539: case '&':
540: writer.write("&");
541: break;
542: case '\"':
543: writer.write(""");
544: break;
545: case '\'':
546: writer.write("'");
547: break;
548: default:
549: writer.write(ch);
550: }
551: }
552:
553: private void writeXmlEscaped(XmlWriter writer, Object object) {
554: String string = object.toString();
555:
556: int len = string.length();
557:
558: for (int i = 0; i < len; i++) {
559: writeXmlEscapedChar(writer, string.charAt(i));
560: }
561: }
562:
563: }
564:
565: private void writeIndentIfNewLine() {
566: if (isNewLine()) {
567: for (int i = _indent * 2; i > 0; i--) {
568: write(' ');
569: }
570: }
571: }
572:
573: /**
574: * If content model is empty, <br />
575: */
576: static public class Xhtml extends Xml {
577: private int EMPTY = 1;
578: private int BREAK_BEFORE = 2;
579: private int BREAK_AFTER = 4;
580: private int BREAK_AFTER_CONTENT = 8;
581: private int EAT_BREAK_BEFORE = 16; // ignore a BREAK_AFTER in the next element
582: private int BOOLEAN_ATTRIBUTE = 1024;
583:
584: private HashMap<String, Integer> _flags = new HashMap<String, Integer>();
585:
586: public Xhtml() {
587: addFlags("html", BREAK_BEFORE | BREAK_AFTER);
588: addFlags("head", BREAK_BEFORE | BREAK_AFTER);
589: addFlags("body", BREAK_BEFORE | BREAK_AFTER);
590:
591: addFlags("style", BREAK_BEFORE | BREAK_AFTER);
592: addFlags("meta", BREAK_BEFORE | BREAK_AFTER | EMPTY);
593: addFlags("link", BREAK_BEFORE | BREAK_AFTER | EMPTY);
594: addFlags("title", BREAK_BEFORE | BREAK_AFTER_CONTENT);
595: addFlags("base", BREAK_BEFORE | BREAK_AFTER | EMPTY);
596:
597: addFlags("h1", BREAK_BEFORE | BREAK_AFTER_CONTENT);
598: addFlags("h2", BREAK_BEFORE | BREAK_AFTER_CONTENT);
599: addFlags("h3", BREAK_BEFORE | BREAK_AFTER_CONTENT);
600: addFlags("h4", BREAK_BEFORE | BREAK_AFTER_CONTENT);
601: addFlags("h5", BREAK_BEFORE | BREAK_AFTER_CONTENT);
602: addFlags("h6", BREAK_BEFORE | BREAK_AFTER_CONTENT);
603:
604: addFlags("p", BREAK_BEFORE | BREAK_AFTER);
605: addFlags("div", BREAK_BEFORE | BREAK_AFTER);
606:
607: addFlags("ul", BREAK_BEFORE | BREAK_AFTER);
608: addFlags("ol", BREAK_BEFORE | BREAK_AFTER);
609:
610: addFlags("li", BREAK_BEFORE | BREAK_AFTER_CONTENT);
611:
612: addFlags("dl", BREAK_BEFORE | BREAK_AFTER);
613: addFlags("dt", BREAK_BEFORE | BREAK_AFTER_CONTENT);
614: addFlags("dd", BREAK_BEFORE | BREAK_AFTER_CONTENT);
615:
616: addFlags("hr", BREAK_BEFORE | BREAK_AFTER | EMPTY);
617: addFlags("br", BREAK_AFTER | EMPTY);
618: addFlags("option", EMPTY);
619:
620: addFlags("img", EMPTY);
621:
622: addFlags("area", EMPTY);
623:
624: addFlags("pre", BREAK_BEFORE | BREAK_AFTER);
625:
626: addFlags("blockquote", BREAK_BEFORE | BREAK_AFTER);
627: addFlags("address", BREAK_BEFORE | BREAK_AFTER);
628:
629: addFlags("fieldset", BREAK_BEFORE | BREAK_AFTER);
630: addFlags("form", BREAK_BEFORE | BREAK_AFTER);
631: addFlags("ins", BREAK_BEFORE | BREAK_AFTER);
632: addFlags("del", BREAK_BEFORE | BREAK_AFTER);
633: addFlags("script", BREAK_BEFORE | BREAK_AFTER);
634: addFlags("noscript", BREAK_BEFORE | BREAK_AFTER);
635:
636: addFlags("input", EMPTY);
637:
638: // addFlag("select", BREAK_BEFORE | BREAK_AFTER);
639: // addFlag("optgroup", BREAK_BEFORE | BREAK_AFTER);
640: // addFlag("option", BREAK_BEFORE | BREAK_AFTER);
641: // addFlag("textarea", BREAK_BEFORE | BREAK_AFTER);
642: // addFlag("fieldset", BREAK_BEFORE | BREAK_AFTER);
643: // addFlag("legend", BREAK_BEFORE | BREAK_AFTER);
644:
645: addFlags("table", BREAK_BEFORE | BREAK_AFTER);
646: addFlags("thead", BREAK_BEFORE | BREAK_AFTER);
647: addFlags("tfoot", BREAK_BEFORE | BREAK_AFTER);
648: addFlags("tr", BREAK_BEFORE | BREAK_AFTER_CONTENT);
649: addFlags("col", EMPTY);
650:
651: addFlags("object", BREAK_BEFORE | BREAK_AFTER);
652: addFlags("param", BREAK_BEFORE | BREAK_AFTER | EMPTY);
653:
654: addFlags("compact", BOOLEAN_ATTRIBUTE);
655: addFlags("nowrap", BOOLEAN_ATTRIBUTE);
656: addFlags("ismap", BOOLEAN_ATTRIBUTE);
657: addFlags("declare", BOOLEAN_ATTRIBUTE);
658: addFlags("noshade", BOOLEAN_ATTRIBUTE);
659: addFlags("checked", BOOLEAN_ATTRIBUTE);
660: addFlags("disabled", BOOLEAN_ATTRIBUTE);
661: addFlags("readonly", BOOLEAN_ATTRIBUTE);
662: addFlags("multiple", BOOLEAN_ATTRIBUTE);
663: addFlags("selected", BOOLEAN_ATTRIBUTE);
664: addFlags("noresize", BOOLEAN_ATTRIBUTE);
665: addFlags("defer", BOOLEAN_ATTRIBUTE);
666: }
667:
668: protected void addFlags(String name, int flag) {
669: int intValue = getFlags(name);
670:
671: intValue |= flag;
672:
673: _flags.put(name, intValue);
674: }
675:
676: protected int getFlags(String name) {
677: int intValue;
678:
679: Integer integer = _flags.get(name);
680:
681: if (integer == null)
682: intValue = 0;
683: else
684: intValue = integer;
685:
686: return intValue;
687: }
688:
689: void openElement(XmlWriter writer, String name) {
690: int flags = getFlags(name);
691:
692: if ((flags & BREAK_BEFORE) > 0)
693: writer.softPrintln();
694:
695: writer.writeIndentIfNewLine();
696:
697: writer.write('<');
698: writer.write(name);
699: }
700:
701: protected void writeAttributeValue(XmlWriter writer,
702: String name, Object value) {
703: int flags = getFlags(name);
704:
705: if ((flags & BOOLEAN_ATTRIBUTE) > 0)
706: value = name.toUpperCase();
707:
708: super .writeAttributeValue(writer, name, value);
709: }
710:
711: void closeElement(XmlWriter writer, String name, boolean isEnd) {
712: int flags = getFlags(name);
713:
714: boolean isEmpty = (flags & EMPTY) > 0;
715:
716: if (isEnd && isEmpty)
717: writer.write(" />");
718: else
719: writer.write('>');
720:
721: if ((flags & BREAK_AFTER) > 0)
722: writer.softPrintln();
723:
724: if (isEnd && !isEmpty)
725: endElement(writer, name);
726: }
727:
728: void endElement(XmlWriter writer, String name) {
729: int flags = getFlags(name);
730:
731: boolean isFullBreak = (flags & (BREAK_BEFORE | BREAK_AFTER)) == (BREAK_BEFORE | BREAK_AFTER);
732:
733: if (isFullBreak)
734: writer.softPrintln();
735:
736: writer.writeIndentIfNewLine();
737:
738: if ((flags & EMPTY) == 0) {
739: writer.write("</");
740: writer.write(name);
741: writer.write('>');
742: }
743:
744: if (isFullBreak || ((flags & BREAK_AFTER_CONTENT) > 0))
745: writer.softPrintln();
746: }
747:
748: protected void writeDoctype(XmlWriter writer) {
749: // TODO: review this, should perhaps use strict here, transitional in something else
750:
751: writer
752: .println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
753:
754: /**
755: <!DOCTYPE html
756: PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
757: "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
758:
759: <!DOCTYPE html
760: PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
761: "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
762:
763: <!DOCTYPE html
764: PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
765: "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
766: **/
767: }
768:
769: protected void writeXmlDeclaration(XmlWriter writer) {
770: String encoding = writer.getCharacterEncoding();
771:
772: writer.println("<?xml version=\"1.0\" encoding=\""
773: + encoding + "\"?>");
774: }
775:
776: }
777:
778: static public class Html extends Xhtml {
779: public Html() {
780: }
781: }
782: }
|