blob: eaa31514cdc4eefa34b5eae9c7cece0b0883f3e5 [file] [log] [blame]
/*
* Copyright 2003-2007 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
class StreamingMarkupBuilder extends AbstractStreamingBuilder {
def pendingStack = []
def commentClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, out ->
out.unescaped() << "<!--"
out.escaped() << body
out.unescaped() << "-->"
}
def piClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, out ->
attrs.each {target, instruction ->
out.unescaped() << "<?"
if (instruction instanceof Map) {
out.unescaped() << target
instruction.each {name, value ->
if (value.toString().contains('"')) {
out.unescaped() << " $name='$value'"
} else {
out.unescaped() << " $name=\"$value\""
}
}
} else {
out.unescaped() << "$target $instruction"
}
out.unescaped() << "?>"
}
}
def declarationClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, out ->
out.unescaped() << '<?xml version="1.0" encoding="'
out.escaped() << "${out.encoding}"
out.unescaped() << '"?>\n'
}
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
}
}
}
def unescapedClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, out ->
out.unescaped() << body
}
def tagClosure = {tag, doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, out ->
if (prefix != "") {
if (!(namespaces.containsKey(prefix) || pendingNamespaces.containsKey(prefix))) {
throw new GroovyRuntimeException("Namespace prefix: ${prefix} is not bound to a URI")
}
if (prefix != ":") tag = prefix + ":" + tag
}
out = out.unescaped() << "<${tag}"
attrs.each {key, value ->
if (key.contains('$')) {
def parts = key.tokenize('$')
if (namespaces.containsKey(parts[0]) || pendingNamespaces.containsKey(parts[0])) {
key = parts[0] + ":" + parts[1]
} else {
throw new GroovyRuntimeException("bad attribute namespace tag: ${parts[0]} in ${key}")
}
}
out << " ${key}='"
out.writingAttribute = true
"${value}".build(doc)
out.writingAttribute = false
out << "'"
}
def hiddenNamespaces = [:]
pendingNamespaces.each {key, value ->
hiddenNamespaces[key] = namespaces[key]
namespaces[key] = value
out << ((key == ":") ? " xmlns='" : " xmlns:${key}='")
out.writingAttribute = true
"${value}".build(doc)
out.writingAttribute = false
out << "'"
}
if (body == null) {
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 << "</${tag}>"
}
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
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)
boundClosure.trigger = out
out.flush()
}.asWritable()
}
}