/* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.riotfamily.common.util; import java.io.PrintWriter; /** * Utility class to generate markup code. This example ... * <pre> *TagWriter tag = new TagWriter(writer); *tag.start("div").attribute("id", "foo") * .body("Hello ") * .start("strong").body("World") * .closeAll(); * </pre> * ... will produce the following code: * <pre> * <div id="foo">Hello <strong>World</strong></div> * </pre> * <p> * Note that calling <code>start()</code> on an already opened tag will return * a new TagWriter. To create complex nested structures you should better use * a {@link org.riotfamily.common.util.DocumentWriter DocumentWriter} instead. * </p> */ public class TagWriter { private static final int STATE_CLOSED = 0; private static final int STATE_START = 1; private static final int STATE_BODY = 2; private static final int STATE_CDATA = 3; private boolean empty = false; private String tagName; private PrintWriter writer; private boolean xhtml = true; private int state = STATE_CLOSED; private TagWriter parent; public TagWriter(PrintWriter writer) { this.writer = writer; } private TagWriter(TagWriter parent) { this.parent = parent; this.writer = parent.writer; this.xhtml = parent.xhtml; } public void setXhtml(boolean xhtml) { this.xhtml = xhtml; } public TagWriter start(String tagName) { return start(tagName, false); } public TagWriter startEmpty(String tagName) { return start(tagName, true); } public TagWriter start(String tagName, boolean empty) { if (state != STATE_CLOSED) { if (state == STATE_START) { body(); } return new TagWriter(this).start(tagName, empty); } else { this.tagName = tagName; this.empty = empty; writer.write('<'); writer.write(tagName); state = STATE_START; return this; } } public TagWriter attribute(String name) { String value = null; if (xhtml) { value = name; } return attribute(name, value, true); } public TagWriter attribute(String name, int value) { if (state != STATE_START) { throw new IllegalStateException("start() must be called first"); } writer.write(' '); writer.write(name); writer.write('='); writer.write('"'); writer.write(String.valueOf(value)); writer.write('"'); return this; } public TagWriter attribute(String name, boolean present) { String value = present ? name : null; return attribute(name, value, false); } public TagWriter attribute(String name, String value) { return attribute(name, value, false); } public TagWriter attribute(String name, String value, boolean renderEmpty) { if (state != STATE_START) { throw new IllegalStateException("start() must be called first"); } if (value == null && !renderEmpty) { return this; } writer.write(' '); writer.write(name); if (value != null) { writer.write('='); writer.write('"'); writer.write(FormatUtils.xmlEscape(value)); writer.write('"'); } return this; } public TagWriter body() { if (state != STATE_START) { throw new IllegalStateException("start() must be called first"); } if (empty) { throw new IllegalStateException("Body not allowed for empty tags"); } writer.write('>'); state = STATE_BODY; return this; } public TagWriter body(String body) { return body(body, true); } public TagWriter body(String body, boolean escapeHtml) { body(); if (body != null) { if (escapeHtml) { writer.write(FormatUtils.xmlEscape(body)); } else { writer.write(body); } } return this; } public TagWriter cData() { if (state != STATE_BODY) { throw new IllegalStateException("body() must be called first"); } if (state == STATE_CDATA) { throw new IllegalStateException( "cData() must not be called within a CDATA section"); } writer.write("<![CDATA[\n"); state = STATE_CDATA; return this; } public TagWriter closeCData() { if (state != STATE_CDATA) { throw new IllegalStateException("cData() must be called first"); } writer.write("]]>"); state = STATE_BODY; return this; } public TagWriter print(String s) { if (state < STATE_BODY) { throw new IllegalStateException("body() must be called first"); } writer.print(state == STATE_CDATA ? s : FormatUtils.xmlEscape(s)); return this; } public TagWriter println(String s) { print(s); writer.println(); return this; } public TagWriter println() { writer.println(); return this; } public TagWriter end() { if (state == STATE_CLOSED) { throw new IllegalStateException("Tag already closed"); } if (empty) { if (xhtml) { writer.write('/'); } writer.write('>'); } else { if (state == STATE_CDATA) { closeCData(); } if (state < STATE_BODY) { body(); } writer.write('<'); writer.write('/'); writer.write(tagName); writer.write('>'); } state = STATE_CLOSED; return parent != null ? parent : this; } public void closeAll() { TagWriter writer = this; while (writer != null) { writer.end(); writer = writer.parent; } } }