
groovy.xml.MarkupBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of groovy-jdk14 Show documentation
Show all versions of groovy-jdk14 Show documentation
Groovy: A powerful, dynamic language for the JVM
The newest version!
/*
* Copyright 2003-2008 the original author or authors.
*
* 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 groovy.xml;
import groovy.util.BuilderSupport;
import groovy.util.IndentPrinter;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.Iterator;
/**
* A helper class for creating XML or HTML markup.
* The builder supports various 'pretty printed' formats.
*
* Example:
* new MarkupBuilder().root {
* a( a1:'one' ) {
* b { mkp.yield( '3 < 5' ) }
* c( a2:'two', 'blah' )
* }
* }
* Will print the following to System.out:
* <root>
* <a a1='one'>
* <b>3 < 5</b>
* <c a2='two'>blah</c>
* </a>
* </root>
* Note that mkp
is a special namespace used to escape
* away from the normal building mode of the builder and get access
* to helper markup methods such as 'yield' and 'yieldUnescaped'.
* See the javadoc for {@link #getMkp()} for further details.
*
* @author James Strachan
* @author Stefan Matthias Aust
* @author Scott Stirling
* @author Paul King
* @version $Revision: 17930 $
*/
public class MarkupBuilder extends BuilderSupport {
private IndentPrinter out;
private boolean nospace;
private int state;
private boolean nodeIsEmpty = true;
private boolean useDoubleQuotes = false;
private boolean omitNullAttributes = false;
private boolean omitEmptyAttributes = false;
/**
* Prints markup to System.out
*
* @see IndentPrinter#IndentPrinter()
*/
public MarkupBuilder() {
this(new IndentPrinter());
}
/**
* Sends markup to the given PrintWriter
*
* @param pw the PrintWriter to use
* @see IndentPrinter#IndentPrinter(PrintWriter)
*/
public MarkupBuilder(PrintWriter pw) {
this(new IndentPrinter(pw));
}
/**
* Sends markup to the given Writer but first wrapping it in a PrintWriter
*
* @param writer the writer to use
* @see IndentPrinter#IndentPrinter(PrintWriter)
*/
public MarkupBuilder(Writer writer) {
this(new IndentPrinter(new PrintWriter(writer)));
}
/**
* Sends markup to the given IndentPrinter. Use this option if you want
* to customize the indent used.
*
* @param out the IndentPrinter to use
*/
public MarkupBuilder(IndentPrinter out) {
this.out = out;
}
/**
* Returns true
if attribute values are output with
* double quotes; false
if single quotes are used.
* By default, single quotes are used.
*
* @return true if double quotes are used for attributes
*/
public boolean getDoubleQuotes() {
return this.useDoubleQuotes;
}
/**
* Sets whether the builder outputs attribute values in double
* quotes or single quotes.
*
* @param useDoubleQuotes If this parameter is true
,
* double quotes are used; otherwise, single quotes are.
*/
public void setDoubleQuotes(boolean useDoubleQuotes) {
this.useDoubleQuotes = useDoubleQuotes;
}
/**
* Determine whether null attributes will appear in the produced markup.
*
* @return true
, if null attributes will be
* removed from the resulting markup.
*/
public boolean isOmitNullAttributes() {
return omitNullAttributes;
}
/**
* Allows null attributes to be removed from the generated markup.
*
* @param omitNullAttributes if true
, null
* attributes will not be included in the resulting markup.
* If false
null attributes will be included in the
* markup as empty strings regardless of the omitEmptyAttribute
* setting. Defaults to false
.
*/
public void setOmitNullAttributes(boolean omitNullAttributes) {
this.omitNullAttributes = omitNullAttributes;
}
/**
* Determine whether empty attributes will appear in the produced markup.
*
* @return true
, if empty attributes will be
* removed from the resulting markup.
*/
public boolean isOmitEmptyAttributes() {
return omitEmptyAttributes;
}
/**
* Allows empty attributes to be removed the produced markup.
*
* @param omitEmptyAttributes if true
, empty
* attributes will not be included in the resulting markup.
* Defaults to false
.
*/
public void setOmitEmptyAttributes(boolean omitEmptyAttributes) {
this.omitEmptyAttributes = omitEmptyAttributes;
}
protected IndentPrinter getPrinter() {
return this.out;
}
protected void setParent(Object parent, Object child) { }
/**
* Property that may be called from within your builder closure to access
* helper methods, namely {@link MarkupBuilderHelper#yield(String)},
* {@link MarkupBuilderHelper#yieldUnescaped(String)},
* {@link MarkupBuilderHelper#pi(Map)},
* {@link MarkupBuilderHelper#xmlDeclaration(Map)} and
* {@link MarkupBuilderHelper#comment(String)}.
*
* @return this MarkupBuilder
*/
public Object getMkp() {
return new MarkupBuilderHelper(this);
}
/**
* Prints data in the body of the current tag, escaping XML entities.
* For example: mkp.yield('5 < 7')
*
* @param value text to print
*/
public void yield(String value) {
yield(value, true);
}
/**
* Print data in the body of the current tag. Does not escape XML entities.
* For example: mkp.yieldUnescaped('I am <i>happy</i>!')
.
*
* @param value the text or markup to print.
*/
public void yieldUnescaped(String value) {
yield(value, false);
}
/**
* Produce an XML processing instruction in the output.
* For example:
*
* mkp.pi("xml-stylesheet":[href:"mystyle.css", type:"text/css"])
*
*
* @param args a map with a single entry whose key is the name of the
* processing instruction and whose value is the attributes
* for the processing instruction.
*/
void pi(Map> args) {
Iterator>> iterator = args.entrySet().iterator();
if (iterator.hasNext()) {
Map.Entry> mapEntry = iterator.next();
createNode("?" + mapEntry.getKey(), mapEntry.getValue());
state = 2;
out.println("?>");
}
}
void yield(String value, boolean escaping) {
if (state == 1) {
state = 2;
this.nodeIsEmpty = false;
out.print(">");
}
if (state == 2 || state == 3) {
out.print(escaping ? escapeElementContent(value) : value);
}
}
protected Object createNode(Object name) {
Object theName = getName(name);
toState(1, theName);
this.nodeIsEmpty = true;
return theName;
}
protected Object createNode(Object name, Object value) {
Object theName = getName(name);
if (value == null){
return createNode(theName);
} else {
toState(2, theName);
this.nodeIsEmpty = false;
out.print(">");
out.print(escapeElementContent(value.toString()));
return theName;
}
}
protected Object createNode(Object name, Map attributes, Object value) {
Object theName = getName(name);
toState(1, theName);
for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
Object attributeValue = entry.getValue();
boolean skipNull = attributeValue == null && omitNullAttributes;
boolean skipEmpty = attributeValue != null && omitEmptyAttributes &&
attributeValue.toString().length() == 0;
if (!skipNull && !skipEmpty) {
out.print(" ");
// Output the attribute name,
print(entry.getKey().toString());
// Output the attribute value within quotes. Use whichever
// type of quotes are currently configured.
out.print(this.useDoubleQuotes ? "=\"" : "='");
print(attributeValue == null ? "" : escapeAttributeValue(attributeValue.toString()));
out.print(this.useDoubleQuotes ? "\"" : "'");
}
}
if (value != null) {
yield(value.toString(), true);
} else {
nodeIsEmpty = true;
}
return theName;
}
protected Object createNode(Object name, Map attributes) {
return createNode(name, attributes, null);
}
protected void nodeCompleted(Object parent, Object node) {
toState(3, node);
out.flush();
}
protected void print(Object node) {
out.print(node == null ? "null" : node.toString());
}
protected Object getName(String methodName) {
return super.getName(methodName);
}
/**
* Returns a String with special XML characters escaped as entities so that
* output XML is valid. Escapes the following characters as corresponding
* entities:
*
* - \' as '
* - & as &
* - < as <
* - > as >
*
*
* @param value to be searched and replaced for XML special characters.
* @return value with XML characters escaped
* @deprecated
* @see #escapeXmlValue(String, boolean)
*/
protected String transformValue(String value) {
// & has to be checked and replaced before others
if (value.matches(".*&.*")) {
value = value.replaceAll("&", "&");
}
if (value.matches(".*\\'.*")) {
value = value.replaceAll("\\'", "'");
}
if (value.matches(".*<.*")) {
value = value.replaceAll("<", "<");
}
if (value.matches(".*>.*")) {
value = value.replaceAll(">", ">");
}
return value;
}
/**
* Escapes a string so that it can be used directly as an XML
* attribute value.
* @param value The string to escape.
* @return A new string in which all characters that require escaping
* have been replaced with the corresponding XML entities.
* @see #escapeXmlValue(String, boolean)
*/
private String escapeAttributeValue(String value) {
return escapeXmlValue(value, true);
}
/**
* Escapes a string so that it can be used directly in XML element
* content.
* @param value The string to escape.
* @return A new string in which all characters that require escaping
* have been replaced with the corresponding XML entities.
* @see #escapeXmlValue(String, boolean)
*/
private String escapeElementContent(String value) {
return escapeXmlValue(value, false);
}
/**
* Escapes a string so that it can be used in XML text successfully.
* It replaces the following characters with the corresponding XML
* entities:
*
* - & as &
* - < as <
* - > as >
*
* If the string is to be added as an attribute value, these
* characters are also escaped:
*
* - ' as '
*
* @param value The string to escape.
* @param isAttrValue true
if the string is to be used
* as an attribute value, otherwise false
.
* @return A new string in which all characters that require escaping
* have been replaced with the corresponding XML entities.
*/
private String escapeXmlValue(String value, boolean isAttrValue) {
StringBuffer buffer = new StringBuffer(value);
for (int i = 0, n = buffer.length(); i < n; i++) {
switch (buffer.charAt(i)) {
case '&':
buffer.replace(i, i + 1, "&");
// We're replacing a single character by a string of
// length 5, so we need to update the index variable
// and the total length.
i += 4;
n += 4;
break;
case '<':
buffer.replace(i, i + 1, "<");
// We're replacing a single character by a string of
// length 4, so we need to update the index variable
// and the total length.
i += 3;
n += 3;
break;
case '>':
buffer.replace(i, i + 1, ">");
// We're replacing a single character by a string of
// length 4, so we need to update the index variable
// and the total length.
i += 3;
n += 3;
break;
case '"':
// The double quote is only escaped if the value is for
// an attribute and the builder is configured to output
// attribute values inside double quotes.
if (isAttrValue && this.useDoubleQuotes) {
buffer.replace(i, i + 1, """);
// We're replacing a single character by a string of
// length 6, so we need to update the index variable
// and the total length.
i += 5;
n += 5;
}
break;
case '\'':
// The apostrophe is only escaped if the value is for an
// attribute, as opposed to element content, and if the
// builder is configured to surround attribute values with
// single quotes.
if (isAttrValue && !this.useDoubleQuotes){
buffer.replace(i, i + 1, "'");
// We're replacing a single character by a string of
// length 6, so we need to update the index variable
// and the total length.
i += 5;
n += 5;
}
break;
default:
break;
}
}
return buffer.toString();
}
private void toState(int next, Object name) {
switch (state) {
case 0:
switch (next) {
case 1:
case 2:
out.print("<");
print(name);
break;
case 3:
throw new Error();
}
break;
case 1:
switch (next) {
case 1:
case 2:
out.print(">");
if (nospace) {
nospace = false;
} else {
out.println();
out.incrementIndent();
out.printIndent();
}
out.print("<");
print(name);
break;
case 3:
if (nodeIsEmpty) {
out.print(" />");
}
break;
}
break;
case 2:
switch (next) {
case 1:
case 2:
if (!nodeIsEmpty) {
out.println();
out.incrementIndent();
out.printIndent();
}
out.print("<");
print(name);
break;
case 3:
out.print("");
print(name);
out.print(">");
break;
}
break;
case 3:
switch (next) {
case 1:
case 2:
if (nospace) {
nospace = false;
} else {
out.println();
out.printIndent();
}
out.print("<");
print(name);
break;
case 3:
if (nospace) {
nospace = false;
} else {
out.println();
out.decrementIndent();
out.printIndent();
}
out.print("");
print(name);
out.print(">");
break;
}
break;
}
state = next;
}
private Object getName(Object name) {
if (name instanceof QName) {
return ((QName) name).getQualifiedName();
}
return name;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy