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.DelegatingMetaClass; 023 import groovy.lang.GString; 024 import groovy.lang.GroovyObject; 025 import groovy.lang.GroovyObjectSupport; 026 import groovy.lang.GroovyRuntimeException; 027 import groovy.lang.IntRange; 028 import groovy.lang.MetaClass; 029 import groovy.lang.Writable; 030 031 import java.math.BigDecimal; 032 import java.math.BigInteger; 033 import java.net.MalformedURLException; 034 import java.net.URI; 035 import java.net.URISyntaxException; 036 import java.net.URL; 037 import java.util.ArrayList; 038 import java.util.HashMap; 039 import java.util.Iterator; 040 import java.util.LinkedList; 041 import java.util.List; 042 import java.util.Map; 043 import java.util.Stack; 044 045 import org.codehaus.groovy.runtime.DefaultGroovyMethods; 046 047 048 /** 049 * @author John Wilson 050 */ 051 052 public abstract class GPathResult extends GroovyObjectSupport implements Writable, Buildable { 053 protected final GPathResult parent; 054 protected final String name; 055 protected final String namespacePrefix; 056 protected final Map namespaceMap = new HashMap(); 057 protected final Map namespaceTagHints; 058 059 /** 060 * @param parent 061 * @param name 062 * @param namespacePrefix 063 * @param namespaceTagHints 064 */ 065 public GPathResult(final GPathResult parent, final String name, final String namespacePrefix, final Map namespaceTagHints) { 066 if (parent == null) { 067 // we are the top of the tree 068 this.parent = this; 069 this.namespaceMap.put("xml", "http://www.w3.org/XML/1998/namespace"); // The XML namespace is always defined 070 } else { 071 this.parent = parent; 072 this.namespaceMap.putAll(parent.namespaceMap); 073 } 074 this.name = name; 075 this.namespacePrefix = namespacePrefix; 076 this.namespaceTagHints = namespaceTagHints; 077 078 setMetaClass(getMetaClass()); // wrap the standard MetaClass with the delegate 079 } 080 081 /* (non-Javadoc) 082 * @see groovy.lang.GroovyObjectSupport#setMetaClass(groovy.lang.MetaClass) 083 */ 084 public void setMetaClass(final MetaClass metaClass) { 085 final MetaClass newMetaClass = new DelegatingMetaClass(metaClass) { 086 /* (non-Javadoc) 087 * @see groovy.lang.DelegatingMetaClass#getAttribute(java.lang.Object, java.lang.String) 088 */ 089 public Object getAttribute(final Object object, final String attribute) { 090 return GPathResult.this.getProperty("@" + attribute); 091 } 092 093 public void setAttribute(final Object object, final String attribute, final Object newValue) { 094 GPathResult.this.setProperty("@" + attribute, newValue); 095 } 096 }; 097 super.setMetaClass(newMetaClass); 098 } 099 100 public Object getProperty(final String property) { 101 if ("..".equals(property)) { 102 return parent(); 103 } else if ("*".equals(property)) { 104 return children(); 105 } else if ("**".equals(property)) { 106 return depthFirst(); 107 } else if (property.startsWith("@")) { 108 if (property.indexOf(":") != -1) { 109 final int i = property.indexOf(":"); 110 return new Attributes(this, "@" + property.substring(i + 1), property.substring(1, i), this.namespaceTagHints); 111 } else { 112 return new Attributes(this, property, this.namespaceTagHints); 113 } 114 } else { 115 if (property.indexOf(":") != -1) { 116 final int i = property.indexOf(":"); 117 return new NodeChildren(this, property.substring(i + 1), property.substring(0, i), this.namespaceTagHints); 118 } else { 119 return new NodeChildren(this, property, this.namespaceTagHints); 120 } 121 } 122 } 123 124 public void setProperty(final String property, final Object newValue) { 125 if (property.startsWith("@")) { 126 if (newValue instanceof String || newValue instanceof GString) { 127 final Iterator iter = iterator(); 128 129 while (iter.hasNext()) { 130 final NodeChild child = (NodeChild)iter.next(); 131 132 child.attributes().put(property.substring(1), newValue); 133 } 134 } 135 } else { 136 final GPathResult result = new NodeChildren(this, property, this.namespaceTagHints); 137 138 if (newValue instanceof Map) { 139 final Iterator iter = ((Map)newValue).entrySet().iterator(); 140 141 while (iter.hasNext()) { 142 final Map.Entry entry = (Map.Entry)iter.next(); 143 144 result.setProperty("@" + entry.getKey(), entry.getValue()); 145 } 146 } else { 147 if (newValue instanceof Closure) { 148 result.replaceNode((Closure)newValue); 149 } else { 150 result.replaceBody(newValue); 151 } 152 } 153 } 154 } 155 156 public Object leftShift(final Object newValue) { 157 appendNode(newValue); 158 return this; 159 } 160 161 public Object plus(final Object newValue) { 162 this.replaceNode(new Closure(this) { 163 public void doCall(Object[] args) { 164 final GroovyObject delegate = (GroovyObject)getDelegate(); 165 166 delegate.getProperty("mkp"); 167 delegate.invokeMethod("yield", args); 168 169 delegate.getProperty("mkp"); 170 delegate.invokeMethod("yield", new Object[]{newValue}); 171 } 172 }); 173 174 return this; 175 } 176 177 protected abstract void replaceNode(Closure newValue); 178 179 protected abstract void replaceBody(Object newValue); 180 181 protected abstract void appendNode(Object newValue); 182 183 public String name() { 184 return this.name; 185 } 186 187 public GPathResult parent() { 188 return this.parent; 189 } 190 191 public GPathResult children() { 192 return new NodeChildren(this, this.namespaceTagHints); 193 } 194 195 public String lookupNamespace(final String prefix) { 196 return (String)this.namespaceTagHints.get(prefix); 197 } 198 199 public String toString() { 200 return text(); 201 } 202 203 public Integer toInteger() { 204 return DefaultGroovyMethods.toInteger(text()); 205 } 206 207 public Long toLong() { 208 return DefaultGroovyMethods.toLong(text()); 209 } 210 211 public Float toFloat() { 212 return DefaultGroovyMethods.toFloat(text()); 213 } 214 215 public Double toDouble() { 216 return DefaultGroovyMethods.toDouble(text()); 217 } 218 219 public BigDecimal toBigDecimal() { 220 return DefaultGroovyMethods.toBigDecimal(text()); 221 } 222 223 public BigInteger toBigInteger() { 224 return DefaultGroovyMethods.toBigInteger(text()); 225 } 226 227 public URL toURL() throws MalformedURLException { 228 return DefaultGroovyMethods.toURL(text()); 229 } 230 231 public URI toURI() throws URISyntaxException { 232 return DefaultGroovyMethods.toURI(text()); 233 } 234 235 public Boolean toBoolean() { 236 return DefaultGroovyMethods.toBoolean(text()); 237 } 238 239 public GPathResult declareNamespace(final Map newNamespaceMapping) { 240 this.namespaceMap.putAll(newNamespaceMapping); 241 return this; 242 } 243 244 /* (non-Javadoc) 245 * @see java.lang.Object#equals(java.lang.Object) 246 */ 247 public boolean equals(Object obj) { 248 return text().equals(obj.toString()); 249 } 250 251 public Object getAt(final int index) { 252 if (index < 0) throw new ArrayIndexOutOfBoundsException(index); 253 254 final Iterator iter = iterator(); 255 int count = 0; 256 257 while (iter.hasNext()) { 258 if (count++ == index) { 259 return iter.next(); 260 } else { 261 iter.next(); 262 } 263 } 264 265 return new NoChildren(this, this.name, this.namespaceTagHints); 266 } 267 268 public Object getAt(final IntRange range) { 269 final int from = range.getFromInt(); 270 final int to = range.getToInt(); 271 272 if (range.isReverse()) { 273 throw new GroovyRuntimeException("Reverse ranges not supported, range supplied is ["+ to + ".." + from + "]"); 274 } else if (from < 0 || to < 0) { 275 throw new GroovyRuntimeException("Negative range indexes not supported, range supplied is ["+ from + ".." + to + "]"); 276 } else { 277 return new Iterator() { 278 final Iterator iter = iterator(); 279 Object next; 280 int count = 0; 281 282 public boolean hasNext() { 283 if (count <= to) { 284 while (iter.hasNext()) { 285 if (count++ >= from) { 286 this.next = iter.next(); 287 return true; 288 } else { 289 iter.next(); 290 } 291 } 292 } 293 294 return false; 295 } 296 297 public Object next() { 298 return next; 299 } 300 301 public void remove() { 302 throw new UnsupportedOperationException(); 303 } 304 305 }; 306 } 307 } 308 309 public void putAt(final int index, final Object newValue) { 310 final GPathResult result = (GPathResult)getAt(index); 311 312 if (newValue instanceof Closure) { 313 result.replaceNode((Closure)newValue); 314 } else { 315 result.replaceBody(newValue); 316 } 317 } 318 319 public Iterator depthFirst() { 320 return new Iterator() { 321 private final List list = new LinkedList(); 322 private final Stack stack = new Stack(); 323 private Iterator iter = iterator(); 324 private GPathResult next = getNextByDepth(); 325 326 public boolean hasNext() { 327 return this.next != null; 328 } 329 330 public Object next() { 331 try { 332 return this.next; 333 } finally { 334 this.next = getNextByDepth(); 335 } 336 } 337 338 public void remove() { 339 throw new UnsupportedOperationException(); 340 } 341 342 private GPathResult getNextByDepth() { 343 while (this.iter.hasNext()) { 344 final GPathResult node = (GPathResult) this.iter.next(); 345 this.list.add(node); 346 this.stack.push(this.iter); 347 this.iter = node.children().iterator(); 348 } 349 350 if (this.list.isEmpty()) { 351 return null; 352 } else { 353 GPathResult result = (GPathResult) this.list.get(0); 354 this.list.remove(0); 355 this.iter = (Iterator) this.stack.pop(); 356 return result; 357 } 358 } 359 }; 360 } 361 362 /** 363 * An iterator useful for traversing XML documents/fragments in breadth-first order. 364 * 365 * @return Iterator the iterator of GPathResult objects 366 */ 367 public Iterator breadthFirst() { 368 return new Iterator() { 369 private final List list = new LinkedList(); 370 private Iterator iter = iterator(); 371 private GPathResult next = getNextByBreadth(); 372 373 public boolean hasNext() { 374 return this.next != null; 375 } 376 377 public Object next() { 378 try { 379 return this.next; 380 } finally { 381 this.next = getNextByBreadth(); 382 } 383 } 384 385 public void remove() { 386 throw new UnsupportedOperationException(); 387 } 388 389 private GPathResult getNextByBreadth() { 390 List children = new ArrayList(); 391 while (this.iter.hasNext() || !children.isEmpty()) { 392 if (this.iter.hasNext()) { 393 final GPathResult node = (GPathResult) this.iter.next(); 394 this.list.add(node); 395 this.list.add(this.iter); 396 children.add(node.children()); 397 } else { 398 List nextLevel = new ArrayList(); 399 for (int i = 0; i < children.size(); i++) { 400 GPathResult next = (GPathResult) children.get(i); 401 Iterator iterator = next.iterator(); 402 while (iterator.hasNext()) { 403 nextLevel.add(iterator.next()); 404 } 405 } 406 this.iter = nextLevel.iterator(); 407 children = new ArrayList(); 408 } 409 } 410 if (this.list.isEmpty()) { 411 return null; 412 } else { 413 GPathResult result = (GPathResult) this.list.get(0); 414 this.list.remove(0); 415 this.iter = (Iterator) this.list.get(0); 416 this.list.remove(0); 417 return result; 418 } 419 } 420 }; 421 } 422 423 public List list() { 424 final Iterator iter = nodeIterator(); 425 final List result = new LinkedList(); 426 while (iter.hasNext()) { 427 result.add(new NodeChild((Node) iter.next(), this.parent, this.namespacePrefix, this.namespaceTagHints)); 428 } 429 return result; 430 } 431 432 public boolean isEmpty() { 433 return size() == 0; 434 } 435 436 public abstract int size(); 437 438 public abstract String text(); 439 440 public abstract GPathResult parents(); 441 442 public abstract Iterator childNodes(); 443 444 public abstract Iterator iterator(); 445 446 public abstract GPathResult find(Closure closure); 447 448 public abstract GPathResult findAll(Closure closure); 449 450 public abstract Iterator nodeIterator(); 451 }