Package be.roam.hue.doj

Source Code of be.roam.hue.doj.Doj$EmptyDoj

/*
* Copyright 2009 Roam - roam.be
*
* Licensed 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 be.roam.hue.doj;

import com.gargoylesoftware.htmlunit.ElementNotFoundException;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.ClickableElement;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.HtmlButton;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlOption;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlSelect;
import com.gargoylesoftware.htmlunit.html.HtmlTextArea;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Node;
import static be.roam.hue.doj.MatchType.*;

/**
* Doj is a jQuery-style DOM traversal tool, to be used in conjunction with
* HtmlUnit's {@link HtmlElement}.
* <p>
* The best examples of usage can be found in the unit tests for this class,
* but here are some pointers.
* </p>
* <p>
* Say you want to get value of the text input inside a form with class "search",
* located inside a div with id sidebar. Here's how you can do it with Doj:
* </p>
* <pre><code>
* // Possibility #1 - plain and simple
* String value1 = Doj.on(page).getById("sidebar").get("form").withClass("search").get("input").value();
* // Possibility #2 - concise with simple CSS selectors
* String value2 = Doj.on(page).get("#sidebar form.search input").value();
* </code></pre>
* <p>
* A Doj instance is <strong>immutable</strong>. In the above code, each
* possibility creates 5 new Doj instances:
* </p>
* <ol>
* <li>Doj.on(): the initial object</li>
* <li>One for getting an element by id "sidebar"</li>
* <li>One for getting descendants by tag name "form"</li>
* <li>One for filtering the context elements by class "search"</li>
* <li>One for getting descendants by tag name "input"</li>
* </ol>
* <p>
* Because Doj relies on two implementations to keep the DOM traversal code
* clean (one for an empty Doj, one for a non-empty Doj), construction of a new
* Doj instance is handled by the <code>on(...)</code> factory methods.
* </p>
* <p>
* Note that Doj implements {@link Iterable}, allowing you to loop over the
* context elements with a for-each loop. Following code will print the value of
* the class attribute of each div on the page:
* </p>
* <pre><code>
* Doj allDivsOnThePage = Doj.on(page).get("div");
* for (Doj div : allDivsOnThePage) {
*      System.out.println(div.attribute("class"));
* }
* </code></pre>
* <p>
* When traversing the DOM, missing elements will not throw exceptions. Assume
* you've got a page without any tables and without any elements with the
* class "nono":
* </p>
* <pre><code>
* String thisWillBeNull = Doj.on(page).get("table").get("a").get("span").attribute("title");
* String asWillThis = Doj.on(page).get(".nono").get("a").get("span").attribute("title");
* </code></pre>
* <p>
* Checking whether there is actually anything there, goes like this:
* </p>
* <pre><code>
* boolean thisIsTrue = Doj.on(page).get("table").get("a").get("span").isEmpty();
* boolean thisToo = Doj.on(page).get(".nono").get("a").get("span").size() == 0;
* boolean andThis = Doj.on(page).get("table").isEmpty();
* boolean thisIsProbablyFalse = Doj.on(page).get("body").isEmpty();
* </code></pre>
* @author Kevin Wetzels
*/
public abstract class Doj implements Iterable<Doj> {

    public static final Doj EMPTY = new EmptyDoj();

    /**
     * Gets the wrapped element at the given index.
     * <p>
     * When no such element exists, an empty Doj instance is returned.
     * </p>
     * @param index index of the element to retrieve - pass a negative value to start from the back
     * @return new Doj instance
     */
    public abstract Doj get(int index);

    /**
     * Gets the element at the given index.
     * @param index index of the element to retrieve - pass a negative value to start from the back
     * @return the element at the given index, or null if no such element exists
     */
    public abstract HtmlElement getElement(int index);

    /**
     * Creates a new Doj instance by removing the element at the given index
     * from the context.
     * <p>
     * If no such element exists, the current instance is returned.
     * </p>
     * @param index index of the element to remove - pass a negative value to start from the back
     * @return new Doj instance
     */
    public abstract Doj remove(int index);

    /**
     * Creates a new Doj instance consisting of the children of the context
     * that have the given id.
     * <p>
     * <strong>Note:</strong> due to HtmlUnit's implementation of
     * {@link HtmlElement#getElementById(java.lang.String)}, this will not look
     * for the element with the given id as a descendant of the context
     * elements, but for the element with the given id. For instance:
     * <code>Doj.on(page).getById("header").getById("header").size()</code>
     * will not return 0 as you would expect, but 1.
     * </p>
     * @param id id to match
     * @return new Doj instance
     */
    public abstract Doj getById(String id);

    /**
     * Creates a new Doj instance containing the next sibling elements of the
     * current context elements.
     * @return new Doj instance
     */
    public abstract Doj next();

    /**
     * Creates a new Doj instance containing the next sibling elements of the
     * current context elements, matching the given tag.
     * <p>
     * Unlike {@link #next()}, this method will keep looking for the first
     * matching sibling until it finds a match or is out of siblings.
     * </p>
     * @param tag tag to match
     * @return new Doj instance
     */
    public abstract Doj next(String tag);

    /**
     * Creates a new Doj instance containing the previous sibling elements of the
     * current context elements.
     * @return new Doj instance
     */
    public abstract Doj previous();

    /**
     * Creates a new Doj instance containing the previous sibling elements of the
     * current context elements, matching the given tag.
     * <p>
     * Unlike {@link #previous()}, this method will keep looking for the first
     * matching sibling until it finds a match or is out of siblings.
     * </p>
     * @param tag tag to match
     * @return new Doj instance
     */
    public abstract Doj previous(String tag);

    /**
     * Creates a new Doj instance containing the direct parent elements of the
     * current context elements.
     * @return new Doj instance
     */
    public abstract Doj parent();

    /**
     * Creates a new Doj instance containing the parent elements of the current
     * context elements that match the given tag.
     * <p>
     * Unlike {@link #parent() }, this method will keep traversing up the DOM
     * until a match is found or the top of the DOM has been found
     * </p>
     * @param tag tag to match
     * @return new Doj instance
     */
    public abstract Doj parent(String tag);

    /**
     * Creates a new Doj instance without the duplicate elements from the original.
     * <p>
     * Calling this method should not be necessary since Doj tries to use it
     * where possible.
     * </p>
     * <p>
     * Note that in order to function correctly, this method will need to set
     * some data on the element to serve as a hash code - which is missing in
     * HtmlUnit's {@link HtmlElement} - but only if there's no id to use.
     * </p>
     * @return new Doj instance
     */
    public abstract Doj unique();

    /**
     * Creates a new Doj instance containing the elements matching the given
     * (simple) CSS selector.
     * <p>
     * Following selectors are allowed:
     * </p>
     * <dl>
     * <dt>type selectors</dt>
     * <dd>HTML element tag names, e.g. "h1" will only match h1 elements</dd>
     * <dt>class selectors</dt>
     * <dd>passing ".something" will only match the elements with the class "something"</dd>
     * <dt>id selectors</dt>
     * <dd>pass "#theid" to math the element with id "theid"</dd>
     * <dt>a combination of the above selectors</dt>
     * <dd>passing "div.article" wil only match the div elements with class "article"</dd>
     * <dt>selectors with descendant combinators</dt>
     * <dd>passing "div.article a.more" will only match the anchors with class "more"
     * that are descendants of a div with class "article". But when using a
     * selector such as "div.article p#someid", remember that HtmlUnit will
     * look for the element with the given id anywhere on the page, not
     * just within divs with class "article".</dd>
     * </dl>
     * @param selector selector to use to match elements
     * @return new Doj instance
     */
    public abstract Doj get(String selector);

    /**
     * Shorthand for <code>get(selector).get(indexOfElement)</code>.
     * @param selector selector to use
     * @param index index of the element matching the selector to return
     * @return new Doj instance
     */
    public Doj get(String selector, int index) {
        return get(selector).get(index);
    }

    /**
     * Creates a new Doj instance containing all child elements of the current
     * context elements with the given tag.
     * @param tag tag to match
     * @return new Doj instance
     */
    public abstract Doj getByTag(String tag);

    /**
     * Returns the id of the first context element.
     * @return the id of the first context element
     */
    public abstract String id();

    /**
     * Returns the ids of all context elements.
     * @return the ids of all context elements
     */
    public abstract String[] ids();

    /**
     * Shorthand for <code>getByAttribute("class",
     * MatchType.CONTAINED_WITH_WHITESPACE, classToLookFor)</code>.
     * @param classToLookFor the class to match
     * @return new Doj instance
     */
    public Doj getByClass(String classToLookFor) {
        return getByAttribute("class", MatchType.CONTAINED_WITH_WHITESPACE, classToLookFor);
    }

    /**
     * Shorthand for <code>getByAttribute(attribute, MatchType.MATCHING, value)</code>.
     * @param attribute attribute to consider
     * @param value value to match exactly
     * @return new Doj instance
     */
    public Doj getByAttribute(String attribute, String value) {
        return getByAttribute(attribute, MATCHING, value);
    }

    /**
     * Creates a new Doj instance containing all child elements of the current
     * context elements with the given attribute matching the given value as
     * determined by the match type.
     *
     * @param attribute attribute to consider
     * @param matchType type of match to make
     * @param value the value to match
     * @return new Doj instance
     */
    public abstract Doj getByAttribute(String attribute, MatchType matchType, String value);

    /**
     * Returns true if at least one of the context elements has the given class.
     * @param valueToContain class to check for
     * @return true if at least one of the context elements has the given class
     */
    public abstract boolean hasClass(String valueToContain);

    /**
     * Returns true if at least one of the context elements matches the tag.
     * @param tag tag to match
     * @return true if at least one of the context elements matches the tag
     */
    public abstract boolean is(String tag);

    /**
     * Creates a new Doj instance by only retaining the elements that match
     * the given tag.
     * @param tag tag to match
     * @return new Doj instance
     */
    public abstract Doj withTag(String tag);

    /**
     * Creates a new Doj instance by only retaining the element that contain
     * the given text, i.e.: <code>node.text().contains(textToContain)</code>
     * will return <code>true</code> for each node.
     * @param textToContain text the retained nodes should contain
     * @return new Doj instance
     */
    public abstract Doj withTextContaining(String textToContain);

    /**
     * Shorthand for <code>withAttribute(key, MatchType.EXISTING, someValueOrEvenNull)</code>
     * @param key attribute to look for
     * @return a filtered context
     */
    public Doj with(String key) {
        return withAttribute(key, MatchType.EXISTING, null);
    }

    /**
     * Shorthand for {@link #withAttribute(java.lang.String, java.lang.String)}.
     * @param key key to consider
     * @param value value to match exactly
     * @return new Doj instance
     */
    public Doj with(String key, String value) {
        return withAttribute(key, value);
    }

    /**
     * Shorthand for <code>withAttribute(key, MatchType.MATCHING, someValueOrEvenNull)</code>
     * @param key key to consider
     * @param value value value to match exactly
     * @return new Doj instance
     */
    public Doj withAttribute(String key, String value) {
        return withAttribute(key, MATCHING, value);
    }

    /**
     * Creates a new Doj instance containing all context elements with the
     * attribute matching the given value, as determined by the match type.
     * @param key key to consider
     * @param matchType type of match
     * @param value value to match
     * @return new Doj instance
     */
    public abstract Doj withAttribute(String key, MatchType matchType, String value);

    /**
     * Shorthand for <code>withAttribute(key, MatchType.CONTAINING, someValueOrEvenNull)</code>
     * @param key key to consider
     * @param value value that should be contained by the attribute
     * @return new Doj instance
     */
    public Doj withAttributeContaining(String key, String value) {
        return withAttribute(key, MatchType.CONTAINING, value);
    }

    /**
     * Shorthand for <code>withAttribute("class", MatchType.CONTAINED_WITH_WHITESPACE, valueToContain)</code>
     * @param valueToContain value
     * @return new Doj instance
     */
    public Doj withClass(String valueToContain) {
        return withAttribute("class", MatchType.CONTAINED_WITH_WHITESPACE, valueToContain);
    }

    /**
     * Shorthand for <code>withAttribute("id", MatchType.MATCHING, valueToContain)</code>
     * @param valueToContain value
     * @return new Doj instance
     */
    public Doj withId(String valueToContain) {
        return withAttribute("id", MatchType.MATCHING, valueToContain);
    }

    /**
     * Shorthand for <code>withAttribute("type", MatchType.MATCHING, valueToContain)</code>
     * @param type type to match
     * @return new Doj instance
     */
    public Doj withType(String type) {
        return withAttribute("type", type);
    }

    /**
     * Shorthand for <code>withAttribute("name", MatchType.MATCHING, valueToContain)</code>
     * @param name name to match
     * @return new Doj instance
     */
    public Doj withName(String name) {
        return withAttribute("name", name);
    }

    /**
     * Shorthand for <code>hasAttribute(key, MatchType.EXISTING, anyValue)</code>
     * @param key of the attribute
     * @return new Doj instance
     */
    public boolean hasAttribute(String key) {
        return hasAttribute(key, MatchType.EXISTING, null);
    }

    /**
     * Shorthand for <code>hasAttribute(key, MatchType.MATCHING, value)</code>
     * @param key of the attribute
     * @param value value to match
     * @return new Doj instance
     */
    public boolean hasAttribute(String key, String value) {
        return hasAttribute(key, MatchType.MATCHING, value);
    }

    /**
     * Returns true if one of the context elements has a value for the attribute
     * matching the given value as defined by the match type.
     * @param key key of the attribute to match
     * @param matchType type of match to make
     * @param value the value to match
     * @return true if one of the context elements matches
     */
    public abstract boolean hasAttribute(String key, MatchType matchType, String value);

    /**
     * Returns the text content of the first context element.
     * @return the text content of the first context element
     */
    public abstract String text();

    /**
     * Returns the text contents of all context elements.
     * @return the text contents of all context elements
     */
    public abstract String[] texts();

    /**
     * Returns the trimmed text content of the first context element.
     * <p>
     * All whitespace characters, including newlines are condensed into a
     * single space - leading and trailing whitespace is removed.
     * </p>
     * @return the trimmed text content of the first context element
     */
    public abstract String trimmedText();

    /**
     * Returns the trimmed text contents of all context elements.
     * <p>
     * All whitespace characters, including newlines are condensed into a
     * single space - leading and trailing whitespace is removed.
     * </p>
     * @return the trimmed text contents of all context elements
     */
    public abstract String[] trimmedTexts();

    /**
     * Returns the value of the given attribute of the first context element.
     * @param key key of the attribute
     * @return the value of the given attribute of the first context element
     */
    public abstract String attribute(String key);

    /**
     * Returns the values of the given attribute of all context elements.
     * @param key key of the attribute
     * @return the values of the given attribute of all context elements
     */
    public abstract String[] attributes(String key);

    /**
     * Sets the attribute of each context element to the given value.
     * @param key key of the attribute
     * @param value value to set the attribute to
     * @return current instance
     */
    public abstract Doj attribute(String key, String value);

    /**
     * Returns the value of the first context element for input elements
     * (including textarea, select and button).
     * <p>
     * In the case of a select, the value of the first selected option is returned.
     * </p>
     * <p>
     * <strong>Note:</strong> use {@link #values()} if you want all selected
     * options of a multiple select o if you want the values of all context
     * elements.
     * </p>
     * @return value of the first context element
     */
    public abstract String value();

    /**
     * Sets the value of the form input elements to the given value. In the
     * case of a multiple select, this will select an extra option.
     * @param value value to use
     * @return current Doj instance
     */
    public abstract Doj value(String value);

    /**
     * Returns the values of all form input context elements.
     * <p>
     * In the case of a select, the values of all selected options are returned.
     * </p>
     * @return the values of all form input context elements
     */
    public abstract String[] values();

    /**
     * Clicks on the first context element.
     * @return the result of clicking on the first context element
     * @throws java.io.IOException
     * @throws java.lang.ClassCastException
     */
    public abstract Page click() throws IOException, ClassCastException;

    /**
     * Returns the number of context elements.
     * @return the number of context elements
     */
    public abstract int size();

    /**
     * Returns true when there are no context elements.
     * @return true when there are no context elements
     */
    public abstract boolean isEmpty();

    /**
     * Creates a new Doj instance containing only the first context element (wrapped).
     * @return new Doj instance
     */
    public abstract Doj first();

    /**
     * Returns the first context element (not wrapped).
     * @return the first context element (not wrapped)
     */
    public HtmlElement firstElement() {
        return getElement(0);
    }

    /**
     * Creates a new Doj instance containing only the last context element (wrapped).
     * @return new Doj instance
     */
    public abstract Doj last();

    /**
     * Returns the last context element (not wrapped).
     * @return the last context element (not wrapped)
     */
    public HtmlElement lastElement() {
        return getElement(-1);
    }

    /**
     * Creates a new Doj instance containing the context elements resulting
     * from applying {@link #sliceElements(int, int)}.
     * @param startIndex index to start at - pass a negative number to work from the back to the front
     * @param nrItems number of items to slice
     * @return new Doj instance
     */
    public abstract Doj slice(int startIndex, int nrItems);

    /**
     * Returns the context elements from the start index.
     * <p>
     * When the start index falls out of bounds, it's limited to the closest
     * valid index (e.g. given 5 elements, an index of -6 is limited to 0 and
     * an index of 7 is limited to 4). The number of items is limited to the
     * number of items that can be retrieved (e.g. given 5 elements, with start
     * index 4 and number of items 3, only one is returned).
     * </p>
     * @param startIndex index to start - pass a negative number to work from the back to the front
     * @param nrItems the number of items to slice
     * @return the context elements from the start index
     */
    public abstract HtmlElement[] sliceElements(int startIndex, int nrItems);

    /**
     * Returns all context elements.
     * @return all context elements
     */
    public HtmlElement[] allElements() {
        return sliceElements(0, size());
    }

    public Iterator<Doj> iterator() {
        return new DojIterator(this);
    }

    /**
     * Throws an exception when the Doj instance is empty.
     * @return the current Doj instance
     * @throws DojHasNoNodesException
     */
    public abstract Doj verifyNotEmpty() throws DojIsEmptyException;

    /**
     * Factory method to create an initial Doj instance.
     * <p>
     * Hides the fact that there are two implementations of Doj at work behind
     * the scenes: one for working with an empty context that keeps the code
     * for the other one, with most of the logic, simple.
     * </p>
     * @param contextElements the context elements to use
     * @return new Doj instance
     */
    public static Doj on(HtmlElement... contextElements) {
        return (contextElements == null || contextElements.length == 0 ? EMPTY : new NonEmptyDoj(contextElements).unique());
    }

    /**
     * Factory method to create an initial Doj instance.
     * <p>
     * Hides the fact that there are two implementations of Doj at work behind
     * the scenes: one for working with an empty context that keeps the code
     * for the other one, with most of the logic, simple.
     * </p>
     * @param contextElements the context elements to use
     * @return new Doj instance
     */
    public static Doj on(Collection<HtmlElement> contextElements) {
        return (contextElements == null || contextElements.isEmpty() ? EMPTY : new NonEmptyDoj(contextElements).unique());
    }

    /**
     * Factory method to create an initial Doj instance.
     * <p>
     * Hides the fact that there are two implementations of Doj at work behind
     * the scenes: one for working with an empty context that keeps the code
     * for the other one, with most of the logic, simple.
     * </p>
     * @param page the page supplying the document element
     * @return new Doj instance
     */
    public static Doj on(HtmlPage page) {
        return (page == null ? EMPTY : new NonEmptyDoj(page));
    }

    /**
     * Iterator for looping over the context elements of a Doj instance.
     */
    private static class DojIterator implements Iterator<Doj> {

        private int index;
        private Doj doj;

        public DojIterator(Doj doj) {
            this.doj = doj;
        }

        public boolean hasNext() {
            return index < doj.size();
        }

        public Doj next() {
            return doj.get(index++);
        }

        public void remove() {
            // Does nothing since a Doj object is immutable
        }
    }

    private static class NonEmptyDoj extends Doj {

        protected final HtmlElement[] contextElements;

        public Doj get(int index) {
            HtmlElement element = getElement(index);
            return element == null ? EMPTY : on(element);
        }

        public HtmlElement getElement(int index) {
            int size = size();
            if (index < -size || index >= size) {
                return null;
            }
            index = (index >= 0 ? index : size + index);
            return contextElements[index];
        }

        public Doj unique() {
            Set<String> retained = new HashSet<String>();
            List<HtmlElement> list = new ArrayList<HtmlElement>();
            for (HtmlElement element : contextElements) {
                String identifier = uniqueId(element);
                if (!retained.contains(identifier)) {
                    retained.add(identifier);
                    list.add(element);
                }
            }
            // This is a one-off: everything that creates a new Doj object
            // should pass via on(...) - but since unique is used all over,
            // this is the easiest way to make sure all Doj instances carry
            // unique context elements - without ending up in an infinite loop
            return list.isEmpty() ? EMPTY : new NonEmptyDoj(list);
        }

        protected String uniqueId(HtmlElement element) {
            String identifier = element.getId();
            if (!StringUtils.isBlank(identifier)) {
                return identifier;
            }
            final String attribute = "data-doj-id";
            identifier = element.getAttribute(attribute);
            if (!StringUtils.isBlank(identifier)) {
                return identifier;
            }
            identifier = UUID.randomUUID().toString();
            element.setAttribute(attribute, identifier);
            return identifier;
        }

        public Doj remove(int index) {
            int size = size();
            if (index < -size || index >= size) {
                return this;
            }
            if (size == 1 && (index == 0 || index == -1)) {
                return EMPTY;
            }
            int indexElementToRemove = (index >= 0 ? index : size + index);
            HtmlElement[] newContextElements = new HtmlElement[size - 1];
            for (int loop = 0; loop < size; ++loop) {
                if (loop == indexElementToRemove) {
                    continue;
                }
                newContextElements[loop < indexElementToRemove ? loop : loop - 1] = contextElements[loop];
            }
            return on(newContextElements);
        }

        public Doj get(String selectorString) {
            List<DojCssSelector> selectorList = new DojCssSelector().compile(selectorString);
            Doj doj = this;
            boolean descend = true;
            for (DojCssSelector selector : selectorList) {
                if (selector.getType() == DojCssSelector.Type.DESCENDANT) {
                    descend = true;
                } else {
                    doj = applySimpleSelector(selector, doj, descend);
                    descend = false;
                }
            }
            return doj;
        }

        protected Doj applySimpleSelector(DojCssSelector selector, Doj doj, boolean descend) {
            String value = selector.getValue();
            switch (selector.getType()) {
                case ELEMENT:
                    return descend ? doj.getByTag(value) : doj.withTag(value);
                case HTML_CLASS:
                    return descend ? doj.getByClass(value) : doj.withClass(value);
                case ID:
                    return descend ? doj.getById(value) : doj.withId(value);
            }
            return EMPTY;
        }

        public Doj getById(String id) {
            for (HtmlElement element : contextElements) {
                try {
                    HtmlElement elementWithId = element.getElementById(id);
                    if (elementWithId != null) {
                        return on(elementWithId);
                    }
                } catch (ElementNotFoundException e) {
                    // Ignore
                }
            }
            return EMPTY;
        }

        public Doj getByTag(String tag) {
            List<HtmlElement> list = new ArrayList<HtmlElement>();
            for (HtmlElement element : contextElements) {
                list.addAll(element.getHtmlElementsByTagName(tag));
            }
            return on(list);
        }

        public String id() {
            return firstElement().getId();
        }

        public String[] ids() {
            String[] ids = new String[contextElements.length];
            for (int index = 0; index < ids.length; ++index) {
                ids[index] = contextElements[index].getId();
            }
            return ids;
        }

        public Doj getByAttribute(String attribute, MatchType matchType, String value) {
            List<HtmlElement> list = new ArrayList<HtmlElement>();
            for (HtmlElement element : contextElements) {
                for (HtmlElement child : element.getAllHtmlChildElements()) {
                    if (matchType.isMatch(child.getAttribute(attribute), value)) {
                        list.add(child);
                    }
                }
            }
            return on(list);
        }

        public boolean hasClass(String valueToContain) {
            return hasAttribute("class", MatchType.CONTAINED_WITH_WHITESPACE, valueToContain);
        }

        public boolean is(String tag) {
            for (HtmlElement element : contextElements) {
                if (tag.equalsIgnoreCase(element.getTagName())) {
                    return true;
                }
            }
            return false;
        }

        public Doj withTag(String tag) {
            List<HtmlElement> list = new ArrayList<HtmlElement>();
            for (HtmlElement element : contextElements) {
                if (tag.equalsIgnoreCase(element.getTagName())) {
                    list.add(element);
                }
            }
            return on(list);
        }

        public Doj withAttribute(String key, MatchType matchType, String value) {
            List<HtmlElement> list = new ArrayList<HtmlElement>();
            for (HtmlElement element : contextElements) {
                if (matchType.isMatch(element.getAttribute(key), value)) {
                    list.add(element);
                }
            }
            return on(list);
        }

        public boolean hasAttribute(String key, MatchType matchType, String value) {
            for (HtmlElement element : contextElements) {
                if (matchType.isMatch(element.getAttribute(key), value)) {
                    return true;
                }
            }
            return false;
        }

        public String text() {
            return firstElement().getTextContent();
        }

        public String[] texts() {
            int size = size();
            String[] texts = new String[size];
            for (int index = 0; index < size; ++index) {
                texts[index] = contextElements[index].getTextContent();
            }
            return texts;
        }

        public String trimmedText() {
            String text = text();
            return text == null ? null : text.replaceAll("\\s+", " ").trim();
        }

        public String[] trimmedTexts() {
            int size = size();
            String[] texts = new String[size];
            for (int index = 0; index < size; ++index) {
                String text = contextElements[index].getTextContent();
                texts[index] = (text == null ? null : text.replaceAll("\\s+", " ").trim());
            }
            return texts;
        }

        public String attribute(String key) {
            return firstElement().getAttribute(key);
        }

        public String[] attributes(String key) {
            int length = contextElements.length;
            String[] values = new String[length];
            for (int index = 0; index < length; ++index) {
                values[index] = contextElements[index].getAttribute(key);
            }
            return values;
        }

        public Doj attribute(String key, String value) {
            for (HtmlElement element : contextElements) {
                element.setAttribute(key, value);
            }
            return this;
        }

        public String value() {
            HtmlElement first = firstElement();
            if ("textarea".equalsIgnoreCase(first.getTagName())) {
                return ((HtmlTextArea) first).getText();
            }
            if ("select".equalsIgnoreCase(first.getTagName())) {
                return first().get("option").with("selected", "selected").value();
            }
            if ("option".equalsIgnoreCase(first.getTagName())) {
                return ((HtmlOption) first).getValueAttribute();
            }
            if ("input".equalsIgnoreCase(first.getTagName())) {
                return ((HtmlInput) first).getValueAttribute();
            }
            if ("button".equalsIgnoreCase(first.getTagName())) {
                return ((HtmlButton) first).getValueAttribute();
            }
            return null;
        }

        public String[] values() {
            List<String> values = new ArrayList<String>();
            for (Doj element : this) {
                if (element.is("select") && element.hasAttribute("multiple", "multiple")) {
                    Doj selectedOptions = element.get("option").with("selected", "selected");
                    for (Doj option : selectedOptions) {
                        values.add(option.value());
                    }
                } else {
                    values.add(element.value());
                }
            }
            return values.toArray(new String[values.size()]);
        }

        public Page click() throws IOException, ClassCastException {
            return ((ClickableElement) firstElement()).click();
        }

        public int size() {
            return contextElements.length;
        }

        public boolean isEmpty() {
            return false;
        }

        public Doj first() {
            return on(firstElement());
        }

        public Doj last() {
            return on(lastElement());
        }

        public Doj slice(int startIndex, int nrItems) {
            return on(sliceElements(startIndex, nrItems));
        }

        public HtmlElement[] sliceElements(int startIndex, int nrItems) {
            int size = size();
            if (startIndex < 0) {
                nrItems = (nrItems > -startIndex ? -startIndex : nrItems);
                startIndex = size + startIndex;
                startIndex = (startIndex < 0 ? 0 : startIndex);
            }
            if (startIndex > size) {
                startIndex = size - 1;
            }
            nrItems = Math.min(size - startIndex, nrItems);
            HtmlElement[] result = new HtmlElement[nrItems];
            for (int index = startIndex; index < nrItems + startIndex; ++index) {
                result[index - startIndex] = contextElements[index];
            }
            return result;
        }

        public NonEmptyDoj(HtmlElement... contextElements) {
            this.contextElements = contextElements;
        }

        public NonEmptyDoj(Collection<HtmlElement> contextElements) {
            this.contextElements = new HtmlElement[contextElements.size()];
            int index = -1;
            for (HtmlElement element : contextElements) {
                this.contextElements[++index] = element;
            }
        }

        public NonEmptyDoj(HtmlPage page) {
            this.contextElements = new HtmlElement[]{page.getDocumentElement()};
        }

        public Doj next() {
            List<HtmlElement> siblings = new ArrayList<HtmlElement>();
            for (HtmlElement element : contextElements) {
                DomNode node = element.getNextSibling();
                while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
                    node = node.getNextSibling();
                }
                if (node != null) {
                    siblings.add((HtmlElement) node);
                }
            }
            return on(siblings);
        }

        public Doj next(String tag) {
            List<HtmlElement> siblings = new ArrayList<HtmlElement>();
            for (HtmlElement element : contextElements) {
                DomNode node = element.getNextSibling();
                while (node != null && (node.getNodeType() != Node.ELEMENT_NODE || !node.getNodeName().equalsIgnoreCase(tag))) {
                    node = node.getNextSibling();
                }
                if (node != null && node.getNodeName().equalsIgnoreCase(tag)) {
                    siblings.add((HtmlElement) node);
                }
            }
            return on(siblings);
        }

        public Doj previous() {
            List<HtmlElement> siblings = new ArrayList<HtmlElement>();
            for (HtmlElement element : contextElements) {
                DomNode node = element.getPreviousSibling();
                while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
                    node = node.getPreviousSibling();
                }
                if (node != null) {
                    siblings.add((HtmlElement) node);
                }
            }
            return on(siblings);
        }

        public Doj previous(String tag) {
            List<HtmlElement> siblings = new ArrayList<HtmlElement>();
            for (HtmlElement element : contextElements) {
                DomNode node = element.getPreviousSibling();
                while (node != null && (node.getNodeType() != Node.ELEMENT_NODE || !node.getNodeName().equalsIgnoreCase(tag))) {
                    node = node.getPreviousSibling();
                }
                if (node != null && node.getNodeName().equalsIgnoreCase(tag)) {
                    siblings.add((HtmlElement) node);
                }
            }
            return on(siblings);
        }

        public Doj parent() {
            List<HtmlElement> parents = new ArrayList<HtmlElement>();
            for (HtmlElement element : contextElements) {
                HtmlElement parent = (HtmlElement) element.getParentNode();
                if (parent != null) {
                    parents.add(parent);
                }
            }
            return on(parents);
        }

        public Doj parent(String tag) {
            List<HtmlElement> parents = new ArrayList<HtmlElement>();
            for (HtmlElement element : contextElements) {
                HtmlElement parent = (HtmlElement) element.getParentNode();
                while (parent != null && !parent.getTagName().equalsIgnoreCase(tag)) {
                    parent = (HtmlElement) parent.getParentNode();
                }
                if (parent != null) {
                    parents.add(parent);
                }
            }
            return on(parents);
        }

        public Doj verifyNotEmpty() throws DojIsEmptyException {
            return this;
        }

        public Doj value(String value) {
            for (HtmlElement element : contextElements) {
                if ("textarea".equalsIgnoreCase(element.getTagName())) {
                    ((HtmlTextArea) element).setText(value);
                } else if ("select".equalsIgnoreCase(element.getTagName())) {
                    ((HtmlSelect) element).setSelectedAttribute(value, true);
                } else {
                    element.setAttribute("value", value);
                }
            }
            return this;
        }

        public Doj withTextContaining(String textToContain) {
            List<HtmlElement> retained = new ArrayList<HtmlElement>();
            for (HtmlElement element : contextElements) {
                String text = element.asText();
                if (text != null && text.contains(textToContain)) {
                    retained.add(element);
                }
            }
            return on(retained);
        }
    }

    /**
     * Implementation of an empty Doj object - helps keep the other code simple.
     */
    private static class EmptyDoj extends Doj {

        private static final HtmlElement[] EMPTY_ELEMENT_ARRAY = new HtmlElement[0];
        private static final String[] EMPTY_STRING_ARRAY = new String[0];

        public Doj unique() {
            return this;
        }

        public Doj withTag(String tag) {
            return this;
        }

        public String attribute(String key) {
            return null;
        }

        public Doj attribute(String key, String value) {
            return this;
        }

        public String[] attributes(String key) {
            return EMPTY_STRING_ARRAY;
        }

        public Page click() throws IOException {
            return null;
        }

        public Doj first() {
            return this;
        }

        public Doj getByAttribute(String attribute, MatchType matchType, String value) {
            return this;
        }

        public Doj getById(String id) {
            return this;
        }

        public Doj getByTag(String tag) {
            return this;
        }

        public HtmlElement getElement(int index) {
            return null;
        }

        public boolean hasAttribute(String key, MatchType matchType, String value) {
            return false;
        }

        public boolean hasClass(String valueToContain) {
            return false;
        }

        public String id() {
            return null;
        }

        public String[] ids() {
            return EMPTY_STRING_ARRAY;
        }

        public boolean is(String tag) {
            return false;
        }

        public boolean isEmpty() {
            return true;
        }

        public Doj last() {
            return this;
        }

        public Doj next() {
            return this;
        }

        public Doj next(String tag) {
            return this;
        }

        public Doj parent() {
            return this;
        }

        public Doj parent(String tag) {
            return this;
        }

        public Doj previous() {
            return this;
        }

        public Doj previous(String tag) {
            return this;
        }

        public Doj remove(int index) {
            return this;
        }

        public int size() {
            return 0;
        }

        public Doj slice(int startIndex, int nrItems) {
            return this;
        }

        public HtmlElement[] sliceElements(int startIndex, int nrItems) {
            return EMPTY_ELEMENT_ARRAY;
        }

        public String text() {
            return null;
        }

        public String[] texts() {
            return EMPTY_STRING_ARRAY;
        }

        public String trimmedText() {
            return null;
        }

        public String[] trimmedTexts() {
            return EMPTY_STRING_ARRAY;
        }

        public String value() {
            return null;
        }

        public String[] values() {
            return EMPTY_STRING_ARRAY;
        }

        public Doj withAttribute(String key, MatchType matchType, String value) {
            return this;
        }

        public Doj get(int index) {
            return this;
        }

        public Doj get(String selector) {
            return this;
        }

        public Doj verifyNotEmpty() throws DojIsEmptyException {
            throw new DojIsEmptyException();
        }
       
        public Doj value(String value) {
            return this;
        }

        public Doj withTextContaining(String textToContain) {
            return this;
        }
    }

}
TOP

Related Classes of be.roam.hue.doj.Doj$EmptyDoj

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.