2
* Copyright 2003-2007 the original author or authors.
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
* you may not use this file except in compliance with the License.
6
* You may obtain a copy of the License at
8
* http://www.apache.org/licenses/LICENSE-2.0
10
* Unless required by applicable law or agreed to in writing, software
11
* distributed under the License is distributed on an "AS IS" BASIS,
12
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
* See the License for the specific language governing permissions and
14
* limitations under the License.
19
import javax.xml.parsers.DocumentBuilderFactory
20
import org.w3c.dom.Node
22
import groovy.xml.streamingmarkupsupport.AbstractStreamingBuilder
23
import groovy.xml.streamingmarkupsupport.BaseMarkupBuilder
25
class StreamingDOMBuilder extends AbstractStreamingBuilder {
27
def defaultNamespaceStack = [""]
28
def commentClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, dom ->
29
def comment = dom.document.createComment(body)
30
if (comment != null) {
31
dom.element.appendChild(comment)
34
def piClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, dom ->
35
attrs.each {target, instruction ->
37
if (instruction instanceof Map) {
38
def buf = new StringBuffer()
39
instruction.each { name, value ->
40
if (value.toString().contains('"')) {
41
buf.append(" $name='$value'")
43
buf.append(" $name=\"$value\"" )
46
pi = dom.document.createProcessingInstruction(target, buf.toString())
48
pi = dom.document.createProcessingInstruction(target, instruction)
51
dom.element.appendChild(pi)
55
def noopClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, dom ->
56
if (body instanceof Closure) {
57
def body1 = body.clone()
60
} else if (body instanceof Buildable) {
62
} else if (body != null) {
64
if (it instanceof Closure) {
65
def body1 = it.clone()
68
} else if (it instanceof Buildable) {
71
dom.element.appendChild(dom.document.createTextNode(it))
76
def tagClosure = {tag, doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, dom ->
79
def defaultNamespace = defaultNamespaceStack.last()
81
attrs.each {key, value ->
82
if (key.contains('$')) {
83
def parts = key.tokenize('$')
84
def namespaceUri = null
86
if (namespaces.containsKey(parts[0])) {
87
namespaceUri = namespaces[parts[0]]
89
nsAttributes.add([namespaceUri, "${parts[0]}:${parts[1]}", "$value"])
92
throw new GroovyRuntimeException("bad attribute namespace tag in ${key}")
95
attributes.add([key, value])
99
def hiddenNamespaces = [:]
101
pendingNamespaces.each {key, value ->
103
defaultNamespace = "$value"
104
nsAttributes.add(["http://www.w3.org/2000/xmlns/", "xmlns", defaultNamespace])
106
hiddenNamespaces[key] = namespaces[key]
107
namespaces[key] = value
108
nsAttributes.add(["http://www.w3.org/2000/xmlns/", "xmlns:${key}", "$value"])
112
// setup the tag info
114
def uri = defaultNamespace
115
def qualifiedName = tag
118
if (namespaces.containsKey(prefix)) {
119
uri = namespaces[prefix]
120
} else if (pendingNamespaces.containsKey(prefix)) {
121
uri = pendingNamespaces[prefix]
123
throw new GroovyRuntimeException("Namespace prefix: ${prefix} is not bound to a URI")
126
qualifiedName = prefix + ":" + tag
130
def element = dom.document.createElementNS(uri, qualifiedName)
133
element.setAttributeNS(it[0], it[1], it[2])
136
element.setAttribute(it[0], it[1])
139
dom.element.appendChild(element)
140
dom.element = element
143
defaultNamespaceStack.push defaultNamespace
144
pendingStack.add pendingNamespaces.clone()
145
pendingNamespaces.clear()
147
if (body instanceof Closure) {
148
def body1 = body.clone()
152
} else if (body instanceof Buildable) {
156
if (it instanceof Closure) {
157
def body1 = it.clone()
161
} else if (it instanceof Buildable) {
164
dom.element.appendChild(dom.document.createTextNode(it))
169
pendingNamespaces.clear()
170
pendingNamespaces.putAll pendingStack.pop()
171
defaultNamespaceStack.pop()
174
dom.element = dom.element.getParentNode()
176
hiddenNamespaces.each { key, value ->
177
if (value == null) namespaces.remove key
178
else namespaces[key] = value
184
StreamingDOMBuilder() {
185
specialTags.putAll(['yield':noopClosure,
186
'yieldUnescaped':noopClosure,
187
'comment':commentClosure,
189
def nsSpecificTags = [':' : [tagClosure, tagClosure, [:]], // the default namespace
190
'http://www.w3.org/2000/xmlns/' : [tagClosure, tagClosure, [:]],
191
'http://www.codehaus.org/Groovy/markup/keywords' : [badTagClosure, tagClosure, specialTags]]
192
this.builder = new BaseMarkupBuilder(nsSpecificTags)
196
def boundClosure = this.builder.bind(closure)
198
if (it instanceof Node) {
199
def document = it.getOwnerDocument()
200
boundClosure.trigger = ['document' : document, 'element' : it]
203
def dBuilder = DocumentBuilderFactory.newInstance()
204
dBuilder.namespaceAware = true
205
def newDocument = dBuilder.newDocumentBuilder().newDocument()
206
boundClosure.trigger = ['document' : newDocument, 'element' : newDocument]