001 /* 002 * Copyright 2005 John G. Wilson 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 */ 017 018 package groovy.util.slurpersupport; 019 020 import groovy.lang.Buildable; 021 import groovy.lang.Closure; 022 import groovy.lang.GroovyObject; 023 import groovy.lang.Writable; 024 025 import java.io.IOException; 026 import java.io.Writer; 027 import java.util.HashMap; 028 import java.util.Iterator; 029 import java.util.LinkedList; 030 import java.util.List; 031 import java.util.Map; 032 import java.util.Stack; 033 034 035 /** 036 * @author John Wilson 037 * 038 */ 039 040 public class Node implements Writable { 041 private final String name; 042 private final Map attributes; 043 private final Map attributeNamespaces; 044 private final String namespaceURI; 045 private final List children = new LinkedList(); 046 private final Stack replacementNodeStack = new Stack(); 047 048 public Node(final Node parent, final String name, final Map attributes, final Map attributeNamespaces, final String namespaceURI) { 049 this.name = name; 050 this.attributes = attributes; 051 this.attributeNamespaces = attributeNamespaces; 052 this.namespaceURI = namespaceURI; 053 } 054 055 public String name() { 056 return this.name; 057 } 058 059 public String namespaceURI() { 060 return this.namespaceURI; 061 } 062 063 public Map attributes() { 064 return this.attributes; 065 } 066 067 public List children() { 068 return this.children; 069 } 070 071 public void addChild(final Object child) { 072 this.children.add(child); 073 } 074 075 public void replaceNode(final Closure replacementClosure, final GPathResult result) { 076 this.replacementNodeStack.push(new ReplacementNode() { 077 public void build(final GroovyObject builder, final Map namespaceMap, final Map namespaceTagHints) { 078 final Closure c = (Closure)replacementClosure.clone(); 079 080 Node.this.replacementNodeStack.pop(); // disable the replacement whilst the closure is being executed 081 c.setDelegate(builder); 082 c.call(new Object[]{result}); 083 Node.this.replacementNodeStack.push(this); 084 } 085 }); 086 } 087 088 089 protected void replaceBody(final Object newValue) { 090 this.children.clear(); 091 this.children.add(newValue); 092 } 093 094 protected void appendNode(final Object newValue, final GPathResult result) { 095 if (newValue instanceof Closure) { 096 this.children.add(new ReplacementNode() { 097 public void build(final GroovyObject builder, final Map namespaceMap, final Map namespaceTagHints) { 098 final Closure c = (Closure)((Closure)newValue).clone(); 099 100 c.setDelegate(builder); 101 c.call(new Object[]{result}); 102 } 103 }); 104 } else { 105 this.children.add(newValue); 106 } 107 } 108 109 /* (non-Javadoc) 110 * @see org.codehaus.groovy.sandbox.util.slurpersupport.Node#text() 111 */ 112 public String text() { 113 final StringBuffer buff = new StringBuffer(); 114 final Iterator iter = this.children.iterator(); 115 116 while (iter.hasNext()) { 117 final Object child = iter.next(); 118 119 if (child instanceof Node) { 120 buff.append(((Node)child).text()); 121 } else { 122 buff.append(child); 123 } 124 } 125 126 return buff.toString(); 127 } 128 129 /* (non-Javadoc) 130 * @see org.codehaus.groovy.sandbox.util.slurpersupport.Node#childNodes() 131 */ 132 133 public Iterator childNodes() { 134 return new Iterator() { 135 private final Iterator iter = Node.this.children.iterator(); 136 private Object nextElementNodes = getNextElementNodes(); 137 138 public boolean hasNext() { 139 return this.nextElementNodes != null; 140 } 141 142 public Object next() { 143 try { 144 return this.nextElementNodes; 145 } finally { 146 this.nextElementNodes = getNextElementNodes(); 147 } 148 } 149 150 public void remove() { 151 throw new UnsupportedOperationException(); 152 } 153 154 private Object getNextElementNodes() { 155 while (iter.hasNext()) { 156 final Object node = iter.next(); 157 158 if (node instanceof Node) { 159 return node; 160 } 161 } 162 163 return null; 164 } 165 }; 166 } 167 168 /* (non-Javadoc) 169 * @see org.codehaus.groovy.sandbox.util.slurpersupport.Node#writeTo(java.io.Writer) 170 */ 171 public Writer writeTo(final Writer out) throws IOException { 172 if (this.replacementNodeStack.empty()) { 173 final Iterator iter = this.children.iterator(); 174 175 while (iter.hasNext()) { 176 final Object child = iter.next(); 177 178 if (child instanceof Writable) { 179 ((Writable)child).writeTo(out); 180 } else { 181 out.write(child.toString()); 182 } 183 } 184 185 return out; 186 187 } else { 188 return ((Writable)this.replacementNodeStack.peek()).writeTo(out); 189 } 190 } 191 192 public void build(final GroovyObject builder, final Map namespaceMap, final Map namespaceTagHints) { 193 if (this.replacementNodeStack.empty()) { 194 final Closure rest = new Closure(null) { 195 public Object doCall(final Object o) { 196 buildChildren(builder, namespaceMap, namespaceTagHints); 197 198 return null; 199 } 200 }; 201 202 if (this.namespaceURI.length() == 0 && this.attributeNamespaces.isEmpty()) { 203 builder.invokeMethod(this.name, new Object[]{this.attributes, rest}); 204 } else { 205 final List newTags = new LinkedList(); 206 builder.getProperty("mkp"); 207 final List namespaces = (List)builder.invokeMethod("getNamespaces", new Object[]{}); 208 209 final Map current = (Map)namespaces.get(0); 210 final Map pending = (Map)namespaces.get(1); 211 212 if (this.attributeNamespaces.isEmpty()) { 213 builder.getProperty(getTagFor(this.namespaceURI, current, pending, namespaceMap, namespaceTagHints, newTags, builder)); 214 builder.invokeMethod(this.name, new Object[]{this.attributes, rest}); 215 } else { 216 final Map attributesWithNamespaces = new HashMap(this.attributes); 217 final Iterator attrs = this.attributes.keySet().iterator(); 218 219 while (attrs.hasNext()) { 220 final Object key = attrs.next(); 221 final Object attributeNamespaceURI = this.attributeNamespaces.get(key); 222 223 if (attributeNamespaceURI != null) { 224 attributesWithNamespaces.put(getTagFor(attributeNamespaceURI, current, pending, namespaceMap, namespaceTagHints, newTags, builder) + 225 "$" + key, attributesWithNamespaces.remove(key)); 226 } 227 } 228 229 builder.getProperty(getTagFor(this.namespaceURI, current, pending, namespaceMap,namespaceTagHints, newTags, builder)); 230 builder.invokeMethod(this.name, new Object[]{attributesWithNamespaces, rest}); 231 } 232 233 // remove the new tags we had to define for this element 234 if (!newTags.isEmpty()) { 235 final Iterator iter = newTags.iterator(); 236 237 do { 238 pending.remove(iter.next()); 239 } while (iter.hasNext()); 240 } 241 } 242 } else { 243 ((ReplacementNode)this.replacementNodeStack.peek()).build(builder, namespaceMap, namespaceTagHints); 244 } 245 } 246 247 private static String getTagFor(final Object namespaceURI, final Map current, 248 final Map pending, final Map local, final Map tagHints, 249 final List newTags, final GroovyObject builder) { 250 String tag = findNamespaceTag(pending, namespaceURI); // look in the namespaces whose declaration has already been emitted 251 252 if (tag == null) { 253 tag = findNamespaceTag(current, namespaceURI); // look in the namespaces who will be declared at the next element 254 255 if (tag == null) { 256 // we have to declare the namespace - choose a tag 257 tag = findNamespaceTag(local, namespaceURI); // If the namespace has been decared in the GPath expression use that tag 258 259 if (tag == null || tag.length() == 0) { 260 tag = findNamespaceTag(tagHints, namespaceURI); // If the namespace has been used in the parse documant use that tag 261 } 262 263 if (tag == null || tag.length() == 0) { // otherwise make up a new tag and check it has not been used before 264 int suffix = 0; 265 266 do { 267 final String posibleTag = "tag" + suffix++; 268 269 if (!pending.containsKey(posibleTag) && !current.containsKey(posibleTag) && !local.containsKey(posibleTag)) { 270 tag = posibleTag; 271 } 272 } while (tag == null); 273 } 274 275 final Map newNamespace = new HashMap(); 276 newNamespace.put(tag, namespaceURI); 277 builder.getProperty("mkp"); 278 builder.invokeMethod("declareNamespace", new Object[]{newNamespace}); 279 newTags.add(tag); 280 } 281 } 282 283 return tag; 284 } 285 286 private static String findNamespaceTag(final Map tagMap, final Object namespaceURI) { 287 if (tagMap.containsValue(namespaceURI)) { 288 final Iterator entries = tagMap.entrySet().iterator(); 289 290 while (entries.hasNext()) { 291 final Map.Entry entry = (Map.Entry)entries.next(); 292 293 if (namespaceURI.equals(entry.getValue())) { 294 return (String)entry.getKey(); 295 } 296 } 297 } 298 299 return null; 300 } 301 302 private void buildChildren(final GroovyObject builder, final Map namespaceMap, final Map namespaceTagHints) { 303 final Iterator iter = this.children.iterator(); 304 305 while (iter.hasNext()) { 306 final Object child = iter.next(); 307 308 if (child instanceof Node) { 309 ((Node)child).build(builder, namespaceMap, namespaceTagHints); 310 } else if (child instanceof Buildable) { 311 ((Buildable)child).build(builder); 312 } else { 313 builder.getProperty("mkp"); 314 builder.invokeMethod("yield", new Object[]{child}); 315 } 316 } 317 } 318 }