All Downloads are FREE. Search and download functionalities are using the official Maven repository.

groovy.xml.StreamingMarkupBuilder.groovy Maven / Gradle / Ivy

There is a newer version: 3.9
Show newest version
/*
 * Copyright 2003-2013 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.xml.streamingmarkupsupport.AbstractStreamingBuilder
import groovy.xml.streamingmarkupsupport.StreamingMarkupWriter
import groovy.xml.streamingmarkupsupport.BaseMarkupBuilder

/**
 * A builder class for creating XML markup.  This implementation uses a
 * {@link StreamingMarkupWriter} to handle output.
 * 

* Example: *

System.out << new StreamingMarkupBuilder().bind {
 *   root {
 *     a( a1:'one' ) {
 *       b { mkp.yield( '3 < 5' ) }
 *       c( a2:'two', 'blah' )
 *     }
 *   }
 * }
* Will output the following String, without newlines or indentation: *
<root>
 *   <a a1='one'>
 *     <b>3 &lt; 5</b>
 *     <c a2='two'>blah</c>
 *   </a>
 * </root>
* Notes: *
    *
  • 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 'yield', 'pi', 'comment', 'out', * 'namespaces', 'xmlDeclaration' and 'yieldUnescaped'. *
  • *
  • Note that tab, newline and carriage return characters are escaped within attributes, i.e. will become , and respectively
  • *
*/ class StreamingMarkupBuilder extends AbstractStreamingBuilder { boolean useDoubleQuotes = false boolean expandEmptyElements = false def getQt() { useDoubleQuotes ? '"' : "'" } def pendingStack = [] /** * Invoked by calling mkp.comment */ def commentClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, out -> out.unescaped() << "" } /** * Invoked by calling mkp.pi */ def piClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, out -> attrs.each {target, instruction -> out.unescaped() << " if (value.toString().contains('\'') || (useDoubleQuotes && !value.toString().contains('"'))) { out.unescaped() << " $name=\"$value\"" } else { out.unescaped() << " $name='$value'" } } } else { out.unescaped() << "$target $instruction" } out.unescaped() << "?>" } } /** * Invoked by calling mkp.xmlDeclaration */ def declarationClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, out -> out.unescaped() << '\n' } /** * Invoked by calling mkp.yield. Used to render text to the * output stream. Any XML reserved characters will be escaped to ensure * well-formedness. */ def noopClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, out -> body.each { if (it instanceof Closure) { def body1 = it.clone() body1.delegate = doc body1(doc) } else if (it instanceof Buildable) { it.build(doc) } else { out.escaped() << it } } } /** * Invoked by calling mkp.yieldUnescaped. Used to render * literal text or markup to the output stream. No escaping is done on the * output. */ def unescapedClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, out -> out.unescaped() << body } def tagClosure = {tag, doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, out -> boolean pendingIsDefaultNamespace = pendingNamespaces.containsKey(prefix) && !pendingNamespaces[prefix] if (prefix != "") { if (!(namespaces.containsKey(prefix) || pendingNamespaces.containsKey(prefix))) { throw new GroovyRuntimeException("Namespace prefix: ${prefix} is not bound to a URI") } if (prefix != ":" && !pendingIsDefaultNamespace) tag = prefix + ":" + tag } out = out.unescaped() << "<${tag}" attrs.each {key, value -> if (key.contains('$')) { def parts = key.tokenize('$') String localpart = parts[1].contains("}") ? parts[1].tokenize("}")[1] : parts[1] if (namespaces.containsKey(parts[0]) || pendingNamespaces.containsKey(parts[0])) { key = parts[0] + ":" + localpart } else { throw new GroovyRuntimeException("bad attribute namespace tag: ${parts[0]} in ${key}") } } out << " ${key}=" + qt out.writingAttribute = true "${value}".build(doc) out.writingAttribute = false out << qt } def hiddenNamespaces = [:] pendingNamespaces.each { key, value -> if (value) { hiddenNamespaces[key] = namespaces[key] namespaces[key] = value out << ((key == ":") ? " xmlns=" + qt : " xmlns:${key}=" + qt) out.writingAttribute = true "${value}".build(doc) out.writingAttribute = false out << qt } } if (body == null && !expandEmptyElements) { out << "/>" } else { out << ">" pendingStack.add pendingNamespaces.clone() pendingNamespaces.clear() body.each { if (it instanceof Closure) { def body1 = it.clone() body1.delegate = doc body1(doc) } else if (it instanceof Buildable) { it.build(doc) } else { out.escaped() << it } } pendingNamespaces.clear() pendingNamespaces.putAll pendingStack.pop() out << "" } hiddenNamespaces.each {key, value -> if (value == null) { namespaces.remove key } else { namespaces[key] = value } } } def builder = null StreamingMarkupBuilder() { specialTags.putAll( ['yield': noopClosure, 'yieldUnescaped': unescapedClosure, 'xmlDeclaration': declarationClosure, 'comment': commentClosure, 'pi': piClosure]) def nsSpecificTags = [':': [tagClosure, tagClosure, [:]], // the default namespace 'http://www.w3.org/XML/1998/namespace': [tagClosure, tagClosure, [:]], 'http://www.codehaus.org/Groovy/markup/keywords': [badTagClosure, tagClosure, specialTags]] this.builder = new BaseMarkupBuilder(nsSpecificTags) } def encoding = null /** * Returns a {@link Writable} object, which may be used to render * the markup directly to a String, or send the output to a stream. *

* Examples: *

     * // get the markup as a string:
     * new StreamingMarkupBuilder().bind { div { out << "hello world" } }.toString()
     * 
     * // send the output directly to a file:
     * new StreamingMarkupBuilder().bind { div { out << "hello world" } } \
     *      .writeTo( new File('myFile.xml').newWriter() )
     * 
* * @return a {@link Writable} to render the markup */ public bind(closure) { def boundClosure = this.builder.bind(closure); def enc = encoding; // take a snapshot of the encoding when the closure is bound to the builder {out -> out = new StreamingMarkupWriter(out, enc, useDoubleQuotes) boundClosure.trigger = out out.flush() }.asWritable() } /** * Convenience method for binding a single node. * The call bindNode(node) is equivalent to bind{ out << node }. * Returns a {@link Writable} object, which may be used to render * the markup directly to a String, or send the output to a stream. * * @see #bind(Closure) * @return a {@link Writable} to render the markup */ public bindNode(node) { bind { out << node } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy