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