| /* |
| * 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 javax.xml.parsers.DocumentBuilderFactory |
| import org.w3c.dom.Node |
| |
| import groovy.xml.streamingmarkupsupport.AbstractStreamingBuilder |
| import groovy.xml.streamingmarkupsupport.StreamingMarkupWriter |
| import groovy.xml.streamingmarkupsupport.BaseMarkupBuilder |
| |
| class StreamingDOMBuilder extends AbstractStreamingBuilder { |
| def pendingStack = [] |
| def commentClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, dom -> |
| def comment = dom.document.createComment(body) |
| if (comment != null) { |
| dom.element.appendChild(comment) |
| } |
| } |
| def piClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, dom -> |
| attrs.each {target, instruction -> |
| def pi = null |
| if (instruction instanceof Map) { |
| def buf = new StringBuffer() |
| instruction.each { name, value -> |
| if (value.toString().contains('"')) { |
| buf.append(" $name='$value'") |
| } else { |
| buf.append(" $name=\"$value\"" ) |
| } |
| } |
| pi = dom.document.createProcessingInstruction(target, buf.toString()) |
| } else { |
| pi = dom.document.createProcessingInstruction(target, instruction) |
| } |
| if (pi != null) { |
| dom.element.appendChild(pi) |
| } |
| } |
| } |
| def noopClosure = {doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, dom -> |
| if (body instanceof Closure) { |
| def body1 = body.clone() |
| body1.delegate = doc |
| body1(doc) |
| } else if (body instanceof Buildable) { |
| body.build(doc) |
| } else if (body != null) { |
| body.each { |
| if (it instanceof Closure) { |
| def body1 = it.clone() |
| body1.delegate = doc |
| body1(doc) |
| } else if (it instanceof Buildable) { |
| it.build(doc) |
| } else { |
| dom.element.appendChild(dom.document.createTextNode(it)) |
| } |
| } |
| } |
| } |
| def tagClosure = {tag, doc, pendingNamespaces, namespaces, namespaceSpecificTags, prefix, attrs, body, dom -> |
| def attributes = [] |
| def nsAttributes = [] |
| |
| attrs.each {key, value -> |
| if (key.contains('$')) { |
| def parts = key.tokenize('$') |
| def namespaceUri = null |
| |
| if (namespaces.containsKey(parts[0])) { |
| namespaceUri = namespaces[parts[0]] |
| |
| nsAttributes.add([namespaceUri, "${parts[0]}:${parts[1]}", value]) |
| |
| } else { |
| throw new GroovyRuntimeException("bad attribute namespace tag in ${key}") |
| } |
| } else { |
| attributes.add([key, value]) |
| } |
| } |
| |
| def hiddenNamespaces = [:] |
| |
| pendingNamespaces.each {key, value -> |
| hiddenNamespaces[key] = namespaces[key] |
| namespaces[key] = value |
| nsAttributes.add(["http://www.w3.org/2000/xmlns/", "xmlns:${key}", value]) |
| |
| } |
| |
| // setup the tag info |
| |
| def uri = "" |
| def qualifiedName = tag |
| |
| if (prefix != "") { |
| if (namespaces.containsKey(prefix)) { |
| uri = namespaces[prefix] |
| } else if (pendingNamespaces.containsKey(prefix)) { |
| uri = pendingNamespaces[prefix] |
| } else { |
| throw new GroovyRuntimeException("Namespace prefix: ${prefix} is not bound to a URI") |
| } |
| if (prefix != ":") { |
| qualifiedName = prefix + ":" + tag |
| } |
| } |
| |
| def element = dom.document.createElementNS(uri, qualifiedName) |
| |
| nsAttributes.each { |
| element.setAttributeNS(it[0], it[1], it[2]) |
| } |
| attributes.each { |
| element.setAttribute(it[0], it[1]) |
| } |
| |
| dom.element.appendChild(element) |
| dom.element = element |
| |
| if (body != null) { |
| pendingStack.add pendingNamespaces.clone() |
| pendingNamespaces.clear() |
| |
| if (body instanceof Closure) { |
| def body1 = body.clone() |
| |
| body1.delegate = doc |
| body1(doc) |
| } else if (body instanceof Buildable) { |
| body.build(doc) |
| } else { |
| body.each { |
| if (it instanceof Closure) { |
| def body1 = it.clone() |
| |
| body1.delegate = doc |
| body1(doc) |
| } else if (it instanceof Buildable) { |
| it.build(doc) |
| } else { |
| dom.element.appendChild(dom.document.createTextNode(it)) |
| } |
| } |
| } |
| |
| pendingNamespaces.clear() |
| pendingNamespaces.putAll pendingStack.pop() |
| } |
| |
| dom.element = dom.element.getParentNode() |
| |
| hiddenNamespaces.each { key, value -> |
| if (value == null) namespaces.remove key |
| else namespaces[key] = value |
| } |
| } |
| |
| def builder = null |
| |
| StreamingDOMBuilder() { |
| specialTags.putAll(['yield':noopClosure, |
| 'yieldUnescaped':noopClosure, |
| '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 bind(closure) { |
| def boundClosure = this.builder.bind(closure) |
| return { |
| if (it instanceof Node) { |
| def document = it.getOwnerDocument() |
| boundClosure.trigger = ['document' : document, 'element' : it] |
| return document |
| } else { |
| def newDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() |
| boundClosure.trigger = ['document' : newDocument, 'element' : newDocument] |
| return newDocument |
| } |
| } |
| } |
| } |