/*
* Copyright (c) 2005-2011 Grameen Foundation USA
* All rights reserved.
*
* 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.
*
* See also http://www.apache.org/licenses/LICENSE-2.0.html for an
* explanation of the license and how it is applied.
*/
package org.mifos.framework.struts.tags;
import java.util.Stack;
/**
* Do we really need our own XML/HTML generation class?
*/
public class XmlBuilder {
private final StringBuilder out = new StringBuilder();
private final Stack openElements = new Stack();
public void startTag(String tag, String... attributes) {
out.append("<");
tagName(tag);
attributes(attributes);
out.append(">");
openElements.push(tag);
}
private void tagName(String tag) {
rejectCharacter(tag, '<');
rejectCharacter(tag, '&');
rejectCharacter(tag, '>');
rejectCharacter(tag, '\'');
rejectCharacter(tag, '"');
out.append(tag);
}
private void rejectCharacter(String tag, char character) {
if (tag.indexOf(character) != -1) {
throw new XmlBuilderException("Bad character " + character + " in start tag " + tag);
}
}
private void attribute(String attributeName, String attributeValue) {
if (attributeName != null) { // useful for dealing with optional
// attributes
out.append(" ");
out.append(attributeName);
out.append("=\"");
out.append(attributeValue.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")
.replaceAll("\"", """));
out.append("\"");
}
}
private void attributes(String[] attributes) {
for (int i = 0; i < attributes.length; i += 2) {
attribute(attributes[i], attributes[i + 1]);
}
}
public void endTag(String tag) {
Object startTag = openElements.pop();
if (!tag.equals(startTag)) {
throw new XmlBuilderException("end tag " + tag + " does not match start tag " + startTag);
}
out.append("</");
out.append(tag);
out.append(">");
}
public void singleTag(String tag, String... attributes) {
out.append("<");
tagName(tag);
attributes(attributes);
out.append(" />");
}
public String getOutput() {
if (!openElements.isEmpty()) {
throw new XmlBuilderException("unclosed element " + openElements.peek());
}
return out.toString();
}
/**
* Whether it is better style to have toString give you the output, or make
* people call {@link #getOutput()} is an interesting debate, but we provide
* toString simply for ease of converting code which had been using
* StringBuilder.
*/
@Override
public String toString() {
return getOutput();
}
public void text(String text) {
if (text != null) {
out.append(text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">"));
}
}
public void newline() {
out.append('\n');
}
public void nonBreakingSpace() {
// note: using the usual " " won't pass unit tests as valid XHTML
// so the equivalent "\\u00a0" is being used instead
out.append("\u00a0");
}
public void indent(int spaces) {
for (int i = 0; i < spaces; ++i) {
out.append(' ');
}
}
/**
* Insert the XML corresponding to the argument. The argument is an
* XmlBuilder rather than a string to encourage programmers to treat XML and
* one thing, and strings (for example, data from a database, or anything
* else which needs to be quoted) as another.
*/
public void append(XmlBuilder builder) {
out.append(builder.getOutput());
}
public void comment(String string) {
out.append("<!--");
String text = string.replaceAll("--", "__");
if (text.charAt(0) == '-') {
text = "_" + text.substring(1);
}
if (text.endsWith("-")) {
text = text.substring(0, text.length() - 1) + "_";
}
out.append(text);
out.append("-->");
}
}