001: /* ***** BEGIN LICENSE BLOCK *****
002: * Version: MPL 1.1
003: * The contents of this file are subject to the Mozilla Public License Version
004: * 1.1 (the "License"); you may not use this file except in compliance with
005: * the License. You may obtain a copy of the License at
006: * http://www.mozilla.org/MPL/
007: *
008: * Software distributed under the License is distributed on an "AS IS" basis,
009: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
010: * for the specific language governing rights and limitations under the
011: * License.
012: *
013: * The Original Code is Riot.
014: *
015: * The Initial Developer of the Original Code is
016: * Neteye GmbH.
017: * Portions created by the Initial Developer are Copyright (C) 2006
018: * the Initial Developer. All Rights Reserved.
019: *
020: * Contributor(s):
021: * Felix Gnass [fgnass at neteye dot de]
022: *
023: * ***** END LICENSE BLOCK ***** */
024: package org.riotfamily.common.markup;
025:
026: import java.io.PrintWriter;
027:
028: import org.riotfamily.common.util.FormatUtils;
029:
030: /**
031: * Utility class to generate markup code. This example ...
032: * <pre>
033: *TagWriter tag = new TagWriter(writer);
034: *tag.start("div").attribute("id", "foo")
035: * .body("Hello ")
036: * .start("strong").body("World")
037: * .closeAll();
038: * </pre>
039: * ... will produce the following code:
040: * <pre>
041: * <div id="foo">Hello <strong>World</strong></div>
042: * </pre>
043: * <p>
044: * Note that calling <code>start()</code> on an already opened tag will return
045: * a new TagWriter. To create complex nested structures you should better use
046: * a {@link org.riotfamily.common.markup.DocumentWriter DocumentWriter} instead.
047: * </p>
048: */
049: public class TagWriter {
050:
051: private static final int STATE_CLOSED = 0;
052:
053: private static final int STATE_START = 1;
054:
055: private static final int STATE_BODY = 2;
056:
057: private static final int STATE_CDATA = 3;
058:
059: private boolean empty = false;
060:
061: private String tagName;
062:
063: private PrintWriter writer;
064:
065: private boolean xhtml = true;
066:
067: private int state = STATE_CLOSED;
068:
069: private TagWriter parent;
070:
071: public TagWriter(PrintWriter writer) {
072: this .writer = writer;
073: }
074:
075: private TagWriter(TagWriter parent) {
076: this .parent = parent;
077: this .writer = parent.writer;
078: this .xhtml = parent.xhtml;
079: }
080:
081: public void setXhtml(boolean xhtml) {
082: this .xhtml = xhtml;
083: }
084:
085: public TagWriter start(String tagName) {
086: return start(tagName, false);
087: }
088:
089: public TagWriter startEmpty(String tagName) {
090: return start(tagName, true);
091: }
092:
093: public TagWriter start(String tagName, boolean empty) {
094: if (state != STATE_CLOSED) {
095: if (state == STATE_START) {
096: body();
097: }
098: return new TagWriter(this ).start(tagName, empty);
099: } else {
100: this .tagName = tagName;
101: this .empty = empty;
102: writer.write('<');
103: writer.write(tagName);
104: state = STATE_START;
105: return this ;
106: }
107: }
108:
109: public TagWriter attribute(String name) {
110: String value = null;
111: if (xhtml) {
112: value = name;
113: }
114: return attribute(name, value, true);
115: }
116:
117: public TagWriter attribute(String name, int value) {
118: if (state != STATE_START) {
119: throw new IllegalStateException(
120: "start() must be called first");
121: }
122: writer.write(' ');
123: writer.write(name);
124: writer.write('=');
125: writer.write('"');
126: writer.write(String.valueOf(value));
127: writer.write('"');
128: return this ;
129: }
130:
131: public TagWriter attribute(String name, boolean present) {
132: String value = present ? name : null;
133: return attribute(name, value, false);
134: }
135:
136: public TagWriter attribute(String name, String value) {
137: return attribute(name, value, false);
138: }
139:
140: public TagWriter attribute(String name, String value,
141: boolean renderEmpty) {
142: if (state != STATE_START) {
143: throw new IllegalStateException(
144: "start() must be called first");
145: }
146: if (value == null && !renderEmpty) {
147: return this ;
148: }
149: writer.write(' ');
150: writer.write(name);
151: if (value != null) {
152: writer.write('=');
153: writer.write('"');
154: writer.write(FormatUtils.xmlEscape(value));
155: writer.write('"');
156: }
157: return this ;
158: }
159:
160: public TagWriter body() {
161: if (state != STATE_START) {
162: throw new IllegalStateException(
163: "start() must be called first");
164: }
165: if (empty) {
166: throw new IllegalStateException(
167: "Body not allowed for empty tags");
168: }
169: writer.write('>');
170: state = STATE_BODY;
171: return this ;
172: }
173:
174: public TagWriter body(String body) {
175: return body(body, true);
176: }
177:
178: public TagWriter body(String body, boolean escapeHtml) {
179: body();
180: if (body != null) {
181: if (escapeHtml) {
182: writer.write(FormatUtils.xmlEscape(body));
183: } else {
184: writer.write(body);
185: }
186: }
187: return this ;
188: }
189:
190: public TagWriter cData() {
191: if (state != STATE_BODY) {
192: throw new IllegalStateException(
193: "body() must be called first");
194: }
195: if (state == STATE_CDATA) {
196: throw new IllegalStateException(
197: "cData() must not be called within a CDATA section");
198: }
199: writer.write("<![CDATA[\n");
200: state = STATE_CDATA;
201: return this ;
202: }
203:
204: public TagWriter closeCData() {
205: if (state != STATE_CDATA) {
206: throw new IllegalStateException(
207: "cData() must be called first");
208: }
209: writer.write("]]>");
210: state = STATE_BODY;
211: return this ;
212: }
213:
214: public TagWriter print(String s) {
215: if (state < STATE_BODY) {
216: throw new IllegalStateException(
217: "body() must be called first");
218: }
219: writer.print(state == STATE_CDATA ? s : FormatUtils
220: .xmlEscape(s));
221: return this ;
222: }
223:
224: public TagWriter println(String s) {
225: print(s);
226: writer.println();
227: return this ;
228: }
229:
230: public TagWriter println() {
231: writer.println();
232: return this ;
233: }
234:
235: public TagWriter end() {
236: if (state == STATE_CLOSED) {
237: throw new IllegalStateException("Tag already closed");
238: }
239: if (empty) {
240: if (xhtml) {
241: writer.write('/');
242: }
243: writer.write('>');
244: } else {
245: if (state == STATE_CDATA) {
246: closeCData();
247: }
248: if (state < STATE_BODY) {
249: body();
250: }
251: writer.write('<');
252: writer.write('/');
253: writer.write(tagName);
254: writer.write('>');
255: }
256: state = STATE_CLOSED;
257: return parent != null ? parent : this ;
258: }
259:
260: public void closeAll() {
261: TagWriter writer = this;
262: while (writer != null) {
263: writer.end();
264: writer = writer.parent;
265: }
266: }
267:
268: }
|