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 }