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: 5.0.0-alpha-11
Show newest version
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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.BaseMarkupBuilder
import groovy.xml.streamingmarkupsupport.StreamingMarkupWriter

/**
 * 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
  • *
*/ @SuppressWarnings('Instanceof') 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() << ' def valueStr = value.toString() valueStr.contains('\'') || (useDoubleQuotes && !valueStr.contains('"')) } } 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.push 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 */ def 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 def writable = { out -> out = new StreamingMarkupWriter(out, enc, useDoubleQuotes) boundClosure.trigger = out out.flush() }.asWritable() writable } /** * 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 */ def bindNode(node) { bind { out << node } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy