~brian-thomason/+junk/groovy-1.7.2

« back to all changes in this revision

Viewing changes to src/main/groovy/xml/StreamingDOMBuilder.groovy

  • Committer: Brian Thomason
  • Date: 2011-12-20 17:31:12 UTC
  • Revision ID: brian.thomason@canonical.com-20111220173112-u53pbzhiy5y1hau0
Initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright 2003-2007 the original author or authors.
 
3
 *
 
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
 
7
 *
 
8
 *     http://www.apache.org/licenses/LICENSE-2.0
 
9
 *
 
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.
 
15
 */
 
16
 
 
17
package groovy.xml
 
18
 
 
19
import javax.xml.parsers.DocumentBuilderFactory
 
20
import org.w3c.dom.Node
 
21
 
 
22
import groovy.xml.streamingmarkupsupport.AbstractStreamingBuilder
 
23
import groovy.xml.streamingmarkupsupport.BaseMarkupBuilder
 
24
 
 
25
class StreamingDOMBuilder extends AbstractStreamingBuilder {
 
26
    def pendingStack = []
 
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)
 
32
        }
 
33
    }
 
34
    def piClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, dom ->
 
35
        attrs.each {target, instruction ->
 
36
            def pi = null
 
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'")
 
42
                    } else {
 
43
                        buf.append(" $name=\"$value\"" )
 
44
                    }
 
45
                }
 
46
                pi = dom.document.createProcessingInstruction(target, buf.toString())
 
47
            } else {
 
48
                pi = dom.document.createProcessingInstruction(target, instruction)
 
49
            }
 
50
            if (pi != null) {
 
51
                dom.element.appendChild(pi)
 
52
            }
 
53
        }
 
54
    }
 
55
    def noopClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, dom ->
 
56
        if (body instanceof Closure) {
 
57
            def body1 = body.clone()
 
58
            body1.delegate = doc
 
59
            body1(doc)
 
60
        } else if (body instanceof Buildable) {
 
61
            body.build(doc)
 
62
        } else if (body != null) {
 
63
            body.each {
 
64
                if (it instanceof Closure) {
 
65
                    def body1 = it.clone()
 
66
                    body1.delegate = doc
 
67
                    body1(doc)
 
68
                } else if (it instanceof Buildable) {
 
69
                    it.build(doc)
 
70
                } else {
 
71
                    dom.element.appendChild(dom.document.createTextNode(it))
 
72
                }
 
73
            }
 
74
        }
 
75
    }
 
76
    def tagClosure = {tag, doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, dom ->
 
77
        def attributes = []
 
78
        def nsAttributes = []
 
79
        def defaultNamespace = defaultNamespaceStack.last()
 
80
 
 
81
        attrs.each {key, value ->
 
82
            if (key.contains('$')) {
 
83
                def parts = key.tokenize('$')
 
84
                def namespaceUri = null
 
85
 
 
86
                if (namespaces.containsKey(parts[0])) {
 
87
                    namespaceUri = namespaces[parts[0]]
 
88
 
 
89
                    nsAttributes.add([namespaceUri, "${parts[0]}:${parts[1]}", "$value"])
 
90
 
 
91
                } else {
 
92
                    throw new GroovyRuntimeException("bad attribute namespace tag in ${key}")
 
93
                }
 
94
            } else {
 
95
                attributes.add([key, value])
 
96
            }
 
97
        }
 
98
 
 
99
        def hiddenNamespaces = [:]
 
100
 
 
101
        pendingNamespaces.each {key, value ->
 
102
                if (key == ':') {
 
103
                defaultNamespace = "$value"
 
104
                    nsAttributes.add(["http://www.w3.org/2000/xmlns/", "xmlns", defaultNamespace])
 
105
            } else {
 
106
                    hiddenNamespaces[key] = namespaces[key]
 
107
                    namespaces[key] = value
 
108
                    nsAttributes.add(["http://www.w3.org/2000/xmlns/", "xmlns:${key}", "$value"])
 
109
            }
 
110
        }
 
111
 
 
112
        // setup the tag info
 
113
 
 
114
        def uri = defaultNamespace
 
115
        def qualifiedName = tag
 
116
 
 
117
        if (prefix != "") {
 
118
            if (namespaces.containsKey(prefix)) {
 
119
                uri = namespaces[prefix]
 
120
            } else if (pendingNamespaces.containsKey(prefix)) {
 
121
                uri = pendingNamespaces[prefix]
 
122
            } else {
 
123
                throw new GroovyRuntimeException("Namespace prefix: ${prefix} is not bound to a URI")
 
124
            }
 
125
            if (prefix != ":") {
 
126
                qualifiedName = prefix + ":" + tag
 
127
            }
 
128
        }
 
129
 
 
130
        def element = dom.document.createElementNS(uri, qualifiedName)
 
131
 
 
132
        nsAttributes.each {
 
133
            element.setAttributeNS(it[0], it[1], it[2])
 
134
        }
 
135
        attributes.each {
 
136
            element.setAttribute(it[0], it[1])
 
137
        }
 
138
 
 
139
        dom.element.appendChild(element)
 
140
        dom.element = element
 
141
 
 
142
        if (body != null) {
 
143
            defaultNamespaceStack.push defaultNamespace
 
144
            pendingStack.add pendingNamespaces.clone()
 
145
            pendingNamespaces.clear()
 
146
 
 
147
            if (body instanceof Closure) {
 
148
                def body1 = body.clone()
 
149
 
 
150
                body1.delegate = doc
 
151
                body1(doc)
 
152
            } else if (body instanceof Buildable) {
 
153
                body.build(doc)
 
154
            } else {
 
155
                body.each {
 
156
                    if (it instanceof Closure) {
 
157
                        def body1 = it.clone()
 
158
 
 
159
                        body1.delegate = doc
 
160
                        body1(doc)
 
161
                    } else if (it instanceof Buildable) {
 
162
                        it.build(doc)
 
163
                    } else {
 
164
                        dom.element.appendChild(dom.document.createTextNode(it))
 
165
                    }
 
166
                }
 
167
            }
 
168
 
 
169
            pendingNamespaces.clear()
 
170
            pendingNamespaces.putAll pendingStack.pop()
 
171
            defaultNamespaceStack.pop()
 
172
        }
 
173
 
 
174
        dom.element = dom.element.getParentNode()
 
175
 
 
176
        hiddenNamespaces.each { key, value ->
 
177
            if (value == null) namespaces.remove key
 
178
            else namespaces[key] = value
 
179
        }
 
180
    }
 
181
 
 
182
    def builder = null
 
183
 
 
184
    StreamingDOMBuilder() {
 
185
        specialTags.putAll(['yield':noopClosure,
 
186
                            'yieldUnescaped':noopClosure,
 
187
                            'comment':commentClosure,
 
188
                            'pi':piClosure])
 
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)
 
193
    }
 
194
 
 
195
    def bind(closure) {
 
196
        def boundClosure = this.builder.bind(closure)
 
197
        return {
 
198
            if (it instanceof Node) {
 
199
                def document = it.getOwnerDocument()
 
200
                boundClosure.trigger = ['document' : document, 'element' : it]
 
201
                return document
 
202
            } else {
 
203
                def dBuilder = DocumentBuilderFactory.newInstance()
 
204
                dBuilder.namespaceAware = true
 
205
                def newDocument = dBuilder.newDocumentBuilder().newDocument()
 
206
                boundClosure.trigger = ['document' : newDocument, 'element' : newDocument]
 
207
                return newDocument
 
208
            }
 
209
        }
 
210
    }
 
211
}