All Downloads are FREE. Search and download functionalities are using the official Maven repository.

groovy.xml.slurpersupport.GPathResult Maven / Gradle / Ivy

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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.slurpersupport;

import groovy.lang.Buildable;
import groovy.lang.Closure;
import groovy.lang.DelegatingMetaClass;
import groovy.lang.GString;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.IntRange;
import groovy.lang.MetaClass;
import groovy.lang.Writable;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.ResourceGroovyMethods;
import org.codehaus.groovy.runtime.StringGroovyMethods;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;

/**
 * Base class for representing lazy evaluated GPath expressions.
 */
public abstract class GPathResult extends GroovyObjectSupport implements Writable, Buildable, Iterable {
    protected final GPathResult parent;
    protected final String name;
    protected final String namespacePrefix;
    protected final Map namespaceMap = new HashMap();
    protected final Map namespaceTagHints;

    /**
     * Creates a new GPathResult named name with the parent parent,
     * the namespacePrefix namespacePrefix and the namespaceTagHints specified in
     * the namespaceTagHints Map.
     *
     * @param parent the GPathResult prior to the application of the expression creating this GPathResult
     * @param name if the GPathResult corresponds to something with a name, e.g. a node
     * @param namespacePrefix the namespace prefix if any
     * @param namespaceTagHints the known tag to namespace mappings
     */
    public GPathResult(final GPathResult parent, final String name, final String namespacePrefix, final Map namespaceTagHints) {
        if (parent == null) {
            // we are the top of the tree
            this.parent = this;
            this.namespaceMap.put("xml", "http://www.w3.org/XML/1998/namespace");  // The XML namespace is always defined
        } else {
            this.parent = parent;
            this.namespaceMap.putAll(parent.namespaceMap);
        }
        this.name = name;
        this.namespacePrefix = namespacePrefix;
        this.namespaceTagHints = namespaceTagHints;

        setMetaClass(getMetaClass()); // wrap the standard MetaClass with the delegate
    }

    /**
     * Replaces the MetaClass of this GPathResult.
     *
     * @param metaClass the new MetaClass
     */
    @Override
    public void setMetaClass(final MetaClass metaClass) {
        final MetaClass newMetaClass = new DelegatingMetaClass(metaClass) {
            @Override
            public Object getAttribute(final Object object, final String attribute) {
                return GPathResult.this.getProperty("@" + attribute);
            }

            @Override
            public void setAttribute(final Object object, final String attribute, final Object newValue) {
                GPathResult.this.setProperty("@" + attribute, newValue);
            }
        };
        super.setMetaClass(newMetaClass);
    }

    /**
     * Returns the specified Property of this GPathResult.
     * 

* Realizes the follow shortcuts: *

    *
  • '..' for parent() *
  • '*' for children() *
  • '**' for depthFirst() *
  • '@' for attribute access *
* @param property the Property to fetch */ @Override public Object getProperty(final String property) { if ("..".equals(property)) { return parent(); } else if ("*".equals(property)) { return children(); } else if ("**".equals(property)) { return depthFirst(); } else if (property.startsWith("@")) { if (property.contains(":") && !this.namespaceTagHints.isEmpty()) { final int i = property.indexOf(':'); return new Attributes(this, "@" + property.substring(i + 1), property.substring(1, i), this.namespaceTagHints); } else { return new Attributes(this, property, this.namespaceTagHints); } } else { if (property.contains(":") && !this.namespaceTagHints.isEmpty()) { final int i = property.indexOf(':'); return new NodeChildren(this, property.substring(i + 1), property.substring(0, i), this.namespaceTagHints); } else { return new NodeChildren(this, property, this.namespaceTagHints); } } } /** * Replaces the specified property of this GPathResult with a new value. * * @param property the property of this GPathResult to replace * @param newValue the new value of the property */ @Override public void setProperty(final String property, final Object newValue) { if (property.startsWith("@")) { if (newValue instanceof String || newValue instanceof GString) { for (Object o : this) { final NodeChild child = (NodeChild) o; child.attributes().put(property.substring(1), newValue); } } } else { final GPathResult result = new NodeChildren(this, property, this.namespaceTagHints); if (newValue instanceof Map) { for (Object o : ((Map) newValue).entrySet()) { final Map.Entry entry = (Map.Entry) o; result.setProperty("@" + entry.getKey(), entry.getValue()); } } else { if (newValue instanceof Closure) { result.replaceNode((Closure) newValue); } else { result.replaceBody(newValue); } } } } /** * Overloads the left shift operator to provide an easy way to * lazily append Objects to this GPathResult. * * @param newValue the Object to append * @return this */ public Object leftShift(final Object newValue) { appendNode(newValue); return this; } /** * Lazily adds the specified Object to this GPathResult. * * @param newValue the Object to add * @return this */ public Object plus(final Object newValue) { this.replaceNode(new Closure(this) { public void doCall(Object[] args) { final GroovyObject delegate = (GroovyObject) getDelegate(); delegate.getProperty("mkp"); delegate.invokeMethod("yield", args); delegate.getProperty("mkp"); delegate.invokeMethod("yield", new Object[]{newValue}); } }); return this; } // GROOVY-9852: so that XmlSlurper plus (shorthand for replaceNode) // is preferred ahead of the DGM plus(Iterable, Iterable) method // since GPathResult is Iterable public Object plus(final Closure newValue) { return plus((Object) newValue); } protected abstract void replaceNode(Closure newValue); protected abstract void replaceBody(Object newValue); protected abstract void appendNode(Object newValue); /** * Returns the name of this GPathResult. * * @return the name of this GPathResult */ public String name() { return this.name; } /** * Returns the parent of this GPathResult. If this GPathResult has no parent the GPathResult itself is returned. * This is no navigation in the XML tree. It is backtracking on the GPath expression chain. * It is the behavior of parent() prior to 2.2.0. * Backtracking on '..' actually goes down one level in the tree again. * find() and findAll() are popped along with the level they have been applied to. * * @return the parent or this */ public GPathResult pop() { return this.parent; } /** * Returns as GPathResult with the parent nodes of the current GPathResult * * @return the parents GPathResult or this for the root */ public GPathResult parent() { return new NodeParents(this, this.namespaceTagHints); } /** * Returns the children of this GPathResult as a GPathResult object. * * @return the children of this GPathResult */ public GPathResult children() { return new NodeChildren(this, this.namespaceTagHints); } /** * Returns the namespace mapped to the specified prefix. * * @param prefix the prefix lookup * @return the namespace of the prefix */ public String lookupNamespace(final String prefix) { Object namespace = namespaceMap.get(prefix); if (namespace != null) { return namespace.toString(); } return this.namespaceTagHints.isEmpty() ? prefix : this.namespaceTagHints.get(prefix); } /** * Returns the text of this GPathResult. * * @return the GPathResult, converted to a String */ @Override public String toString() { return text(); } /** * Converts the text of this GPathResult to an Integer object. * * @return the GPathResult, converted to a Integer */ public Integer toInteger() { if(textIsEmptyOrNull()){ return null; } return StringGroovyMethods.toInteger((CharSequence)text()); } /** * Converts the text of this GPathResult to a Long object. * * @return the GPathResult, converted to a Long */ public Long toLong() { if(textIsEmptyOrNull()){ return null; } return StringGroovyMethods.toLong((CharSequence)text()); } /** * Converts the text of this GPathResult to a Float object. * * @return the GPathResult, converted to a Float */ public Float toFloat() { if(textIsEmptyOrNull()){ return null; } return StringGroovyMethods.toFloat((CharSequence)text()); } /** * Converts the text of this GPathResult to a Double object. * * @return the GPathResult, converted to a Double */ public Double toDouble() { if(textIsEmptyOrNull()){ return null; } return StringGroovyMethods.toDouble((CharSequence)text()); } /** * Converts the text of this GPathResult to a BigDecimal object. * * @return the GPathResult, converted to a BigDecimal */ public BigDecimal toBigDecimal() { if(textIsEmptyOrNull()){ return null; } return StringGroovyMethods.toBigDecimal((CharSequence)text()); } /** * Converts the text of this GPathResult to a BigInteger object. * * @return the GPathResult, converted to a BigInteger */ public BigInteger toBigInteger() { if(textIsEmptyOrNull()){ return null; } return StringGroovyMethods.toBigInteger((CharSequence)text()); } private boolean textIsEmptyOrNull() { String t = text(); return null == t || 0 == t.length(); } /** * Converts the text of this GPathResult to a URL object. * * @return the GPathResult, converted to a URL */ public URL toURL() throws MalformedURLException { return ResourceGroovyMethods.toURL((CharSequence)text()); } /** * Converts the text of this GPathResult to a URI object. * * @return the GPathResult, converted to a URI */ public URI toURI() throws URISyntaxException { return ResourceGroovyMethods.toURI((CharSequence)text()); } /** * Converts the text of this GPathResult to a Boolean object. * * @return the GPathResult, converted to a Boolean */ public Boolean toBoolean() { return StringGroovyMethods.toBoolean(text()); } /** * Adds the specified map of prefix to namespace mappings to this GPathResult. * Already existing prefixes are overwritten. * * @param newNamespaceMapping the mappings to add * @return this */ public GPathResult declareNamespace(final Map newNamespaceMapping) { this.namespaceMap.putAll(newNamespaceMapping); return this; } @Override public int hashCode() { return text().hashCode(); } @Override public boolean equals(Object obj) { if (null == obj) { return false; } return text().equals(obj.toString()); } /** * Supports the subscript operator for a GPathResult. *
     * import groovy.xml.slurpersupport.*
     * import groovy.xml.XmlSlurper
     * def text = """
     * <characterList>
     *   <character/>
     *   <character>
     *     <name>Gromit</name>
     *   </character>
     * </characterList>"""
     *
     * GPathResult characterList = new XmlSlurper().parseText(text)
     *
     * assert characterList.character[1].name == 'Gromit'
     * 
* @param index an index * @return the value at the given index */ public Object getAt(final int index) { if (index < 0) { // calculate whole list in this case // recommend avoiding -ve's as this is obviously not as efficient List list = list(); int adjustedIndex = index + list.size(); if (adjustedIndex >= 0 && adjustedIndex < list.size()) return list.get(adjustedIndex); } else { final Iterator iter = iterator(); int count = 0; while (iter.hasNext()) { if (count++ == index) { return iter.next(); } else { iter.next(); } } } return new NoChildren(this, this.name, this.namespaceTagHints); } /** * Supports the range subscript operator for a GPathResult. *
     * import groovy.xml.slurpersupport.*
     * import groovy.xml.XmlSlurper
     * def text = """
     * <characterList>
     *   <character>Wallace</character>
     *   <character>Gromit</character>
     *   <character>Shaun</character>
     * </characterList>"""
     *
     * GPathResult characterList = new XmlSlurper().parseText(text)
     *
     * assert characterList.character[1..2].join(',') == 'Gromit,Shaun'
     * 
* @param range a Range indicating the items to get * @return a new list based on range borders */ public Object getAt(final IntRange range) { return DefaultGroovyMethods.getAt(list(), range); } /** * A helper method to allow GPathResults to work with subscript operators * @param index an index * @param newValue the value to put at the given index */ public void putAt(final int index, final Object newValue) { final GPathResult result = (GPathResult)getAt(index); if (newValue instanceof Closure) { result.replaceNode((Closure)newValue); } else { result.replaceBody(newValue); } } /** * Provides an Iterator over all the nodes of this GPathResult using a depth-first traversal. * * @return the Iterator of (depth-first) ordered GPathResults */ public Iterator depthFirst() { return new Iterator() { private final List list = new LinkedList(); private final Stack stack = new Stack(); private Iterator iter = iterator(); private GPathResult next = getNextByDepth(); @Override public boolean hasNext() { return this.next != null; } @Override public Object next() { try { return this.next; } finally { this.next = getNextByDepth(); } } @Override public void remove() { throw new UnsupportedOperationException(); } private GPathResult getNextByDepth() { while (this.iter.hasNext()) { final GPathResult node = (GPathResult) this.iter.next(); this.list.add(node); this.stack.push(this.iter); this.iter = node.children().iterator(); } if (this.list.isEmpty()) { return null; } else { GPathResult result = (GPathResult) this.list.get(0); this.list.remove(0); this.iter = (Iterator) this.stack.pop(); return result; } } }; } /** * Provides an Iterator over all the nodes of this GPathResult using a breadth-first traversal. * * @return the Iterator of (breadth-first) ordered GPathResults */ public Iterator breadthFirst() { return new Iterator() { private final List list = new LinkedList(); private Iterator iter = iterator(); private GPathResult next = getNextByBreadth(); @Override public boolean hasNext() { return this.next != null; } @Override public Object next() { try { return this.next; } finally { this.next = getNextByBreadth(); } } @Override public void remove() { throw new UnsupportedOperationException(); } private GPathResult getNextByBreadth() { List children = new ArrayList(); while (this.iter.hasNext() || !children.isEmpty()) { if (this.iter.hasNext()) { final GPathResult node = (GPathResult) this.iter.next(); this.list.add(node); this.list.add(this.iter); children.add(node.children()); } else { List nextLevel = new ArrayList(); for (Object child : children) { GPathResult next = (GPathResult) child; for (Object o : next) { nextLevel.add(o); } } this.iter = nextLevel.iterator(); children = new ArrayList(); } } if (this.list.isEmpty()) { return null; } else { GPathResult result = (GPathResult) this.list.get(0); this.list.remove(0); this.iter = (Iterator) this.list.get(0); this.list.remove(0); return result; } } }; } /** * Creates a list of objects representing this GPathResult. * * @return a list representing of this GPathResult */ public List list() { final Iterator iter = nodeIterator(); final List result = new LinkedList(); while (iter.hasNext()) { result.add(new NodeChild((Node) iter.next(), this.parent, this.namespacePrefix, this.namespaceTagHints)); } return result; } /** * Returns true if the GPathResult is empty, i.e. if, and only if, size() is 0. * * @return true if the GPathResult is empty */ public boolean isEmpty() { return size() == 0; } /** * Creates a Closure representing the body of this GPathResult. * * @return the body of this GPathResult, converted to a Closure */ public Closure getBody() { return new Closure(this.parent,this) { public void doCall(Object[] args) { final GroovyObject delegate = (GroovyObject)getDelegate(); final GPathResult thisObject = (GPathResult)getThisObject(); Node node = (Node)thisObject.getAt(0); List children = node.children(); for (Object child : children) { delegate.getProperty("mkp"); if (child instanceof Node) { delegate.invokeMethod("yield", new Object[]{new NodeChild((Node) child, thisObject, "*", null)}); } else { delegate.invokeMethod("yield", new Object[]{child}); } } } }; } /** * Returns the size of this GPathResult. * @return the size of this GPathResult */ public abstract int size(); /** * Returns the text of this GPathResult as a String. * @return the text of this GPathResult */ public abstract String text(); /** * Returns the parents of this GPathResult as a GPathResult. * Warning: The subclasses of this package do not implement this method yet. * @return the parents of this GPathResult */ public abstract GPathResult parents(); /** * Returns an iterator over the child nodes of this GPathResult. * @return an iterator over the child nodes of this GPathResult */ public abstract Iterator childNodes(); @Override public abstract Iterator iterator(); /** * Returns the first child of this GPathResult matching the condition(s) * specified in the passed closure. * @param closure a closure to filters the children of this GPathResult * @return the first child matching the closure */ public abstract GPathResult find(Closure closure); /** * Returns the children of this GPathResult matching the condition(s) * specified in the passed closure. * @param closure a closure to filters the children of this GPathResult * @return the children matching the closure */ public abstract GPathResult findAll(Closure closure); public abstract Iterator nodeIterator(); protected Iterator createIterator(final Object obj) { return new Iterator() { private boolean hasNext = true; @Override public boolean hasNext() { return this.hasNext; } @Override public Object next() { try { return (this.hasNext) ? obj : null; } finally { this.hasNext = false; } } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy