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

com.github.loyada.jdollarx.ElementProperties Maven / Gradle / Ivy

There is a newer version: 1.5.5
Show newest version
package com.github.loyada.jdollarx;



import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.github.loyada.jdollarx.PathUtils.transformXpathToCorrectAxis;

/**
 * Various constrains  on {@link Path} instances, that are used with the methods {@link Path#that}
 * and {@link Path#and}.
 */
public final class ElementProperties {

    private ElementProperties() {
    }

    /**
     * The element is the last sibling (ie: last child) of its parent.
     */
    public static final ElementProperty isLastSibling = new ElementProperty() {
        @Override
        public String toXpath() {
            return "last()";
        }

        public String toString() {
            return "is last sibling";
        }
    };

    /**
     * The element is diabled
     */
    public static final ElementProperty isDisabled = new ElementProperty() {
        @Override
        public String toXpath() {
            return "@disabled";
        }

        public String toString() {
            return "is disabled";
        }
    };

    /**
     * The element is enabled
     */
    public static final ElementProperty isEnabled = new ElementProperty() {
        @Override
        public String toXpath() {
            return "not(@disabled)";
        }

        public String toString() {
            return "is enabled";
        }
    };

    /**
     * The element is checked
     */
    public static final ElementProperty isChecked = new ElementProperty() {
        @Override
        public String toXpath() {
            return "@checked";
        }

        public String toString() {
            return "is checked";
        }
    };

    /**
     * The element is selected
     */
    public static final ElementProperty isSelected = new ElementProperty() {
        @Override
        public String toXpath() {
            return "@selected";
        }

        public String toString() {
            return "is selected";
        }
    };

    /**
     * The element has n direct children
     * @param n the number of children
     * @return a element property that can be applied with Path::that
     */
    public static ElementPropertyWithNumericalBoundaries hasNChildren(Integer n) {
        return new ElementPropertyWithNumericalBoundaries() {
            @Override
            public String toXpath() {
                return "count(./*)=" + n;
            }

            @Override
            public String toString() {
                return String.format("has %d children", n);
            }

            /**
             * The element has at least n direct children
             * @return a element property that can be applied with Path::that
             */
            @Override
            public ElementProperty orMore() {
                return new ElementProperty() {
                    @Override
                    public String toXpath() {
                        return "count(./*)>=" + n;
                    }

                    public String toString() {
                        return String.format("has at least %d children", n);
                    }
                };
            }

            /**
             * The element has at most n direct children
             * @return a element property that can be applied with Path::that
             */
            @Override
            public ElementProperty orLess() {
                return new ElementProperty() {
                    @Override
                    public String toXpath() {
                        return "count(./*)<=" + n;
                    }

                    public String toString() {
                        return String.format("has at most %d children", n);
                    }
                };
            }
        };
    }

    /**
     * The element has no children. Examples where it might be useful: an empty list, empty table, etc.
     */
    public static final ElementProperty hasNoChildren = new ElementProperty() {
        @Override
        public String toXpath() {
            return "count(./*)=0";
        }

        public String toString() {
            return "has no children";
        }
    };

    /**
     * The element has 1 or more children (the opposite from hasNoChildren). Examples where it might be useful: an non-empty list, non-empty table, etc.
     */
    public static final ElementProperty hasChildren = new ElementProperty() {
        @Override
        public String toXpath() {
            return "count(./*)>0";
        }

        public String toString() {
            return "has some children";
        }
    };

    /**
     * The element is the nth-from-last sibling. Example usage: find the element before the last one in a list.
     * @param reverseIndex - the place from last, starting at 0 for the last sibling.
     * @return a element property that can be applied with Path::that
     */
    public static ElementProperty isNthFromLastSibling(Integer reverseIndex) {
        return new ElementProperty() {
            @Override
            public String toXpath() {
                return String.format("count(following-sibling::*)=%d", reverseIndex);
            }

            public String toString() {
                return String.format("is in place %d from the last sibling", reverseIndex);
            }
        };
    }

    /**
     * The element is the nth sibling. Example usage: find the 4th element in a list.
     * @param index - starting at 0 for the first one
     * @return a element property that can be applied with Path::that
     */
    public static ElementProperty isNthSibling(Integer index) {
        return new ElementProperty() {
            @Override
            public String toXpath() {
                return String.format("count(preceding-sibling::*)=%d", index);
            }

            public String toString() {
                return String.format("is in place %d among its siblings", index);
            }
        };
    }

    /**
     * The element is the only direct child of its parent. It has no siblings. For example: a table with a single row.
     */
    public static final ElementProperty isOnlyChild = new ElementProperty() {
        @Override
        public String toXpath() {
            return "count(preceding-sibling::*)=0 and count(following-sibling::*)=0";
        }

        public String toString() {
            return "is only child";
        }
    };

    /**
     * The index among its siblings is between first and last parameters. For example: taking a row from a table, which we know is between row number 2 and 4.
     * @param first - lower index (inclusive, starting at 0)
     * @param last - upper index (inclusive, starting at 0)
     * @return a element property that can be applied with Path::that
     */
    public static ElementProperty withIndexInRange(int first, int last) {
        return new ElementProperty() {
            @Override
            public String toXpath() {
                return String.format("position()>=%d and position()<=%d", first + 1, last + 1);
            }

            public String toString() {
                return String.format("with index from %d to %d", first, last);
            }
        };
    }

    /**
     * Custom property that allows to state the raw expath of a property, and give a string description of it.
     * Example:
     * 
     * {@code
     *
     *  Path el = span.that(hasRawXpathProperty("string(.)='x'", "is awesome"), isOnlyChild);
     *  assertThat(el.getXPath().get(), equalTo("span[string(.)='x'][count(preceding-sibling::*)=0" +
     *                                                "and count(following-sibling::*)=0]"));
     *  assertThat(el.toString(), is(equalTo("span, that is awesome, and is only child")));
     * }
     * 
* * @param rawXpathProps - the xpath property condition string. Will be wrapped with [] in the xpath * @param rawExplanation - a textual readable description of this property * @return a element property that can be applied with Path::that */ public static ElementProperty hasRawXpathProperty(String rawXpathProps, String rawExplanation) { return new ElementProperty() { @Override public String toXpath() { return rawXpathProps; } public String toString() { return rawExplanation; } }; } /** * Element has text equals to the given string parameter, ignoring case. * @param txt - the text to match to * @return an element property that can be applied with Path::that */ public static ElementProperty hasText(String txt) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.textEquals(txt); } public String toString() { return "has the text \"" + txt + "\""; } }; } /** * Element has aria-label attribute equal to the given txt * @param txt - the text to match to * @return an element property that can be applied with Path::that */ public static ElementProperty hasAriaLabel(String txt) { { return hasAttribute("aria-label", txt); } } /** * Element has text equals to the given string parameter. * The equality is case-sensitive. * @param txt - the text to match to (case sensitive) * @return an element property that can be applied with Path::that */ public static ElementProperty hasCaseSensitiveText(String txt) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.caseSensitiveTextEquals(txt); } public String toString() { return "has the text \"" + txt + "\""; } }; } /** * Element has text that starts with the given parameter * @param txt - the text to match to * @return a element property that can be applied with Path::that */ public static ElementProperty hasTextStartingWith(String txt) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.textStartsWith(txt); } public String toString() { return "has text that starts with \"" + txt + "\""; } }; } /** * Element has text that ends with the given parameter * @param txt - the text to match to * @return a element property that can be applied with Path::that */ public static ElementProperty hasTextEndingWith(String txt) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.textEndsWith(txt); } public String toString() { return "has text that ends with \"" + txt + "\""; } }; } /** * Element has ID equals to the given parameter * @param id - the ID to match to * @return a element property that can be applied with Path::that */ public static ElementProperty hasId(String id) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.hasId(id); } public String toString() { return String.format("has Id \"%s\"", id); } }; } /** * Element with a "name" attribute equal to the given parameter. Useful for input elements. * @param name the value of the name property * @return a element property that can be applied with Path::that */ public static ElementProperty hasName(String name) { return hasAttribute("name", name); } /** * Element with a "src" attribute equal to the given parameter. Useful for images. * @param src the URI of the image * @return a element property that can be applied with Path::that */ public static ElementProperty hasSource(String src) { return hasAttribute("src", src); } /** * Element with a "role" attribute equal to the given role. * @param role the value of the role attribute * @return a element property that can be applied with Path::that */ public static ElementProperty hasRole(String role) { return hasAttribute("role", role); } /** * Element with a "title" attribute equal to the given title. * @param title the value of the "title" attribute * @return a element property that can be applied with Path::that */ public static ElementProperty hasTitle(String title) { return hasAttribute("title", title); } /** * Element with a "ref" attribute equal to the given role. * @param ref the value of the role property * @return a element property that can be applied with Path::that */ public static ElementProperty hasRef(String ref) { return hasAttribute("ref", ref); } public static ElementProperty hasAttribute(String attribute, String value) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.hasAttribute(attribute, value); } public String toString() { return String.format("has %s: \"%s\"", attribute, value); } }; } /** * Element that has at least one of the classes given * @param cssClasses - the class names to match to * @return a element property that can be applied with Path::that */ public static ElementProperty hasAnyOfClasses(String... cssClasses) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.hasAnyOfClasses(cssClasses); } public String toString() { String classesAsList = String.join(", ", cssClasses); return "has at least one of the classes [" + classesAsList + "]"; } }; } /** * Element that has all of the given classes * @param cssClasses - the class names to match to * @return a element property that can be applied with Path::that */ public static ElementProperty hasClasses(String... cssClasses) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.hasClasses(cssClasses); } public String toString() { String classesAsList = String.join(", ", cssClasses); return "has classes [" + classesAsList + "]"; } }; } /** * Element that has a class with name that contain the given parameter * @param classSubString a string that should be contained in the class * @return An element property that can be applied with Path::that */ public static ElementProperty hasClassContaining(String classSubString) { return new ElementProperty() { @Override public String toXpath() { return String.format("contains(@class, '%s')", classSubString); } public String toString() { return String.format("has class containing '%s'", classSubString); } }; } /** * Element that has none of the given classes * @param cssClasses - a list of class names, none of which is present in element * @return An element property that can be applied with Path::that */ public static ElementProperty hasNonOfTheClasses(String... cssClasses) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.doesNotExist(XpathUtils.hasAnyOfClasses(cssClasses)); } public String toString() { String classesAsList = String.join(", ", cssClasses); return "has non of the classes [" + classesAsList + "]"; } }; } /** * Element that is the nth sibling of its parent * @param index - the index of the element among its sibling, starting with 0 * @return An element property that can be applied with Path::that */ public static ElementProperty isWithIndex(Integer index) { return new ElementProperty() { @Override public String toXpath() { return String.format("position() = %d", index + 1); } public String toString() { return "with index " + index; } }; } /** * The text in the element contains the given parameter, ignoring case * @param txt - the substring to match to * @return An element property that can be applied with Path::that */ public static ElementProperty hasTextContaining(String txt) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.textContains(txt); } public String toString() { return "has text containing \"" + txt + "\""; } }; } /** * The text in the element contains the given parameter. * This condition is case=sensitive. * @param txt - the substring to match to (case sensitive) * @return An element property that can be applied with Path::that */ public static ElementProperty hasCaseSensitiveTextContaining(String txt) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.caseSensitiveTextContains(txt); } public String toString() { return "has text containing \"" + txt + "\""; } }; } /** * Has the class given in the parameter * @param className the class the element has * @return An element property that can be applied with Path::that */ static public ElementProperty hasClass(String className) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.hasClass(className); } public String toString() { return "has class " + className; } }; } /** * When aggregating all the text under this element, it equals to the given parameter. * This condition is not case sensitive. * @param txt the aggregated, case insensitive, text inside the element * @return An element property that can be applied with Path::that */ public static ElementProperty hasAggregatedTextEqualTo(String txt) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.aggregatedTextEquals(txt); } public String toString() { return "with aggregated text \"" + txt + "\""; } }; } /** * When aggregating all the text under this element, it equals to the given parameter. * This condition is case sensitive. * @param txt the aggregated, case insensitive, text inside the element * @return An element property that can be applied with Path::that */ public static ElementProperty hasAggregatedCaseSensitiveTextEqualTo(String txt) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.aggregatedCaseSensitiveTextEquals(txt); } public String toString() { return "with aggregated text \"" + txt + "\""; } }; } /** * When aggregating all the text under this element, it starts with the given substring (ignoring case) * @param txt the prefix of the aggregated, case insensitive, text inside the element * @return An element property that can be applied with Path::that */ public static ElementProperty hasAggregatedTextStartingWith(String txt) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.aggregatedTextStartsWith(txt); } public String toString() { return "with aggregated text that starts with \"" + txt + "\""; } }; } /** * When aggregating all the text under this element, it ends with the given substring (ignoring case) * @param txt the suffix of the aggregated, case insensitive, text inside the element * @return An element property that can be applied with Path::that */ public static ElementProperty hasAggregatedTextEndingWith(String txt) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.aggregatedTextEndsWith(txt); } public String toString() { return "with aggregated text that ends with \"" + txt + "\""; } }; } /** * When aggregating all the text under this element, it contains the given substring (ignoring case) * @param txt a substring of the aggregated, case insensitive, text inside the element * @return An element property that can be applied with Path::that */ public static ElementProperty hasAggregatedTextContaining(String txt) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.aggregatedTextContains(txt); } public String toString() { return "with aggregated text containing \"" + txt + "\""; } }; } /** * When aggregating all the text under this element, it contains the given substring. * This condition is case sensitive. * @param txt a substring of the aggregated, case sensitive, text inside the element * @return An element property that can be applied with Path::that */ public static ElementProperty hasAggregatedCaseSensitiveTextContaining(String txt) { return new ElementProperty() { @Override public String toXpath() { return XpathUtils.aggregatedcaseSensitiveTextContains(txt); } public String toString() { return "with aggregated text containing \"" + txt + "\""; } }; } /** * Element that is hidden. This is limited to only examine embeded css style, so it not useful in some cases. */ public static ElementProperty isHidden = new ElementProperty() { @Override public String toXpath() { return XpathUtils.isHidden; } public String toString() { return "is hidden"; } }; /** * Element has non-empty text */ public static ElementProperty hasSomeText = new ElementProperty() { @Override public String toXpath() { return XpathUtils.hasSomeText; } public String toString() { return "has some text"; } }; /////////////////////////////////////////////////////////// // relationships private static String getRelationXpath(Path path, String relation) { if (path.getUnderlyingSource().isPresent() || !path.getXPath().isPresent()) throw new IllegalArgumentException("must use a pure xpath BasicPath"); return relation + "::" + transformXpathToCorrectAxis(path).get(); } private static String rValueToString(Path path) { return (path.toString().trim().contains(" ")) ? "(" + path + ")" : path.toString(); } /** * Element is direct child of the element matched by the given parameter * @param path - the parent of the current element * @return An element property that can be applied with Path::that */ public static ElementProperty isChildOf(Path path) { return new ElementProperty() { @Override public String toXpath() { return getRelationXpath(path, "parent"); } public String toString() { return "is child of: " + rValueToString(path); } }; } /** * Element is direct child of the element matched by the given parameter * @param path - the parent of the current element * @return An element property that can be applied with Path::that */ public static ElementProperty hasParent(Path path) { return isChildOf(path); } /** * Element is the parent of the given list of elements * @param paths - a list of elements that are children of the current element * @return An element property that can be applied with Path::that */ public static ElementProperty isParentOf(Path... paths) { return new RelationBetweenMultiElement("child", Arrays.asList(paths)) { @Override protected String plural(final String relation){return relation;} @Override public String toString() { String relation = String.format("has %s", paths.length==1 ? "child" : "children"); return asString(relation); } }; } /** * Element is the parent of the given list of elements * @param paths - a list of elements that are children of the current element * @return An element property that can be applied with Path::that */ public static ElementProperty hasChild(Path... paths) { return isParentOf(paths); } /** * The given elements in the parameters list are contained in the current element * @param paths - a list of elements that are descendants of the current element * @return An element property that can be applied with Path::that */ public static ElementProperty contains(Path... paths) { return new RelationBetweenMultiElement("descendant", Arrays.asList(paths)) { @Override public String toString() { return asString("has descendant"); } }; } /** * The given elements in the parameters list are contained in the current element * @param paths - a list of elements that are descendants of the current element * @return An element property that can be applied with Path::that */ public static ElementProperty isAncestorOf(Path... paths) { return contains(paths); } /** * The given elements in the parameters list are contained in the current element * @param paths - a list of elements that are descendants of the current element * @return An element property that can be applied with Path::that */ public static ElementProperty hasDescendant(Path... paths) { return contains(paths); } /** * Element is inside the given parameter * @param path the ancestor of the current element * @return An element property that can be applied with Path::that */ public static ElementProperty hasAncesctor(Path path) { return new ElementProperty() { @Override public String toXpath() { return getRelationXpath(path, "ancestor"); } public String toString() { return "has ancestor: " + rValueToString(path); } }; } /** * Element is inside the given parameter * @param path the ancestor of the current element * @return An element property that can be applied with Path::that */ public static ElementProperty isDescendantOf(Path path) { return hasAncesctor(path); } /** * Element is inside the given parameter * @param path the ancestor of the current element * @return An element property that can be applied with Path::that */ public static ElementProperty isInside(Path path) { return hasAncesctor(path); } public static ElementProperty isContainedIn(Path path) { return hasAncesctor(path); } /** * Element appears after all the given parameters in the document * @param paths - elements that precede the current element * @return An element property that can be applied with Path::that */ public static ElementProperty isAfter(Path... paths) { return new RelationBetweenMultiElement("preceding", Arrays.asList(paths)) { @Override public String toString() { return asString("is after"); } @Override protected String plural(final String relation){return relation;} }; } /** * Element is after at-least/at-most/exactly the given number of the given element. * Example use: * import static com.github.loyada.jdollarx.atLeast; * * input.that(isAfter(atLeast(2).occurrencesOf(div))); * * @param nPath - at-least/at-most/exactly the given number of the given element * @return An element property that can be applied with Path::that */ public static ElementProperty isAfter(NPath nPath){ return new RelationBetweenMultiElement("preceding", nPath ) { @Override public String toString() { return asString(String.format("is after%s%d occurrences of", RelationOperator.opAsEnglish(nPath.qualifier), nPath.n)); } @Override protected String plural(final String relation){return relation;} }; } /** * Element contains at-least/at-most/exactly the given number of the given element. * Example use: * import static com.github.loyada.jdollarx.elementProperties.contains; * * section.that(contains(atLeast(2).occurrencesOf(div))); * * @param nPath - at-least/at-most/exactly the given number of the given element * @return An element property that can be applied with Path::that */ public static ElementProperty contains(NPath nPath){ return new RelationBetweenMultiElement("descendant", nPath ) { @Override public String toString() { return asString(String.format("containing%s%d occurrences of", RelationOperator.opAsEnglish(nPath.qualifier), nPath.n)); } @Override protected String plural(final String relation){return relation;} }; } /** * Element is parent of at-least/at-most/exactly the given number of the given element. * Example use: * import static com.github.loyada.jdollarx.elementProperties.isParentOf; * * unorderList.that(isParentOf(atLeast(2).occurrencesOf(div))); * * @param nPath - at-least/at-most/exactly the given number of the given element * @return An element property that can be applied with Path::that */ public static ElementProperty isParentOf(NPath nPath){ return new RelationBetweenMultiElement("child", nPath ) { @Override public String toString() { return asString(String.format("parent of%s%d occurrences of", RelationOperator.opAsEnglish(nPath.qualifier), nPath.n)); } @Override protected String plural(final String relation){return relation;} }; } /** * Element is before all the elements given in the parameters * @param paths - all the elements that appear after the current element * @return An element property that can be applied with Path::that */ public static ElementProperty isBefore(Path... paths) { return new RelationBetweenMultiElement("following", Arrays.asList(paths)) { @Override public String toString() { return asString("is before"); } @Override protected String plural(final String relation){return relation;} }; } /** * Element is before at-least/at-most/exactly the given number of the given element. * Example use: *
     *    import static com.github.loyada.jdollarx.isBefore;
     *    input.that(isBefore(atLeast(2).occurrencesOf(div)));
     * 
* * @param nPath - at-least/at-most/exactly the given number of the given element * @return An element property that can be applied with Path::that */ public static ElementProperty isBefore(NPath nPath) { return new RelationBetweenMultiElement("following", nPath) { @Override public String toString() { return asString(String.format("is before%s%d occurrences of", RelationOperator.opAsEnglish(nPath.qualifier), nPath.n)); } @Override protected String plural(final String relation){return relation;} }; } /** * Element is a sibling of all the elements defined by the given paths * @param paths a list of paths referring to elements * @return An element property that can be applied with Path::that */ public static ElementProperty isSiblingOf(Path... paths) { return new RelationBetweenMultiElement("", Arrays.asList(paths)) { @Override protected String getXpathExpressionForSingle(Path path) { return "(" + isAfterSibling(path).toXpath() + ") or (" + isBeforeSibling(path).toXpath() + ")"; } @Override public String toString() { return asString("has sibling"); } }; } /** * Element is a sibling of all the elements defined by the given paths, AND is after all those siblings * @param paths a list of paths referring to elements * @return An element property that can be applied with Path::that */ public static ElementProperty isAfterSibling(Path... paths) { return new RelationBetweenMultiElement("preceding-sibling", Arrays.asList(paths)) { @Override public String toString() { return asString("is after sibling"); } }; } /** * Element is a sibling of the at-least/at-most/exactly n elements given, and appears after them. * Example: *
     *   Path el = element.that(isAfterSibling(exactly(2).occurrencesOf(div)));
     *   assertThat(el.toString(), is(equalTo("any element, that is after 2 siblings of type: div")));
     * 
* * @param nPath a count of elements that are siblings appearing before current elements. * @return An element property that can be applied with Path::that */ public static ElementProperty isAfterSibling(NPath nPath) { return new RelationBetweenMultiElement("preceding-sibling", nPath) { @Override public String toString() { return asString(String.format("is after%s%d siblings of type", RelationOperator.opAsEnglish(nPath.qualifier), nPath.n)); } }; } /** * Element is a sibling of all the elements defined by the given paths, AND is before all those siblings * @param paths a list of paths referring to elements * @return An element property that can be applied with Path::that */ public static ElementProperty isBeforeSibling(Path... paths) { return new RelationBetweenMultiElement("following-sibling", Arrays.asList(paths)) { @Override public String toString() { return asString("is before sibling"); } }; } /** * Element is a sibling of the at-least/at-most/exactly n elements given, and appears before them. * Example: *
     *   Path el = element.that(isBeforeSibling(exactly(2).occurrencesOf(div)));
     *   assertThat(el.toString(), is(equalTo("any element, that is before 2 siblings of type: div")));
     * 
* @param nPath a count of elements that are siblings appearing after current elements. * @return An element property that can be applied with Path::that */ public static ElementProperty isBeforeSibling(NPath nPath) { return new RelationBetweenMultiElement("following-sibling", nPath) { @Override public String toString() { return asString(String.format("is before%s%d siblings of type", RelationOperator.opAsEnglish(nPath.qualifier), nPath.n)); } }; } /** * Element does NOT have the given property. * @param prop - the property which the element must not have * @return An element property that can be applied with Path::that */ public static ElementProperty not(ElementProperty prop) { return new Not(prop); } static final class Not implements ElementProperty { private final ElementProperty p; public Not(final ElementProperty p) { this.p = p; } public String toString() { if (p.toString().startsWith("is ")) return "is not " + p.toString().substring(3); else if (p.toString().startsWith("has ")) return "has no " + p.toString().substring(4); else return "not (" + p + ")"; } @Override public String toXpath() { return XpathUtils.doesNotExist(p.toXpath()); } } static final class And implements ElementProperty { private final ElementProperty p1, p2; public And(final ElementProperty p1, final ElementProperty p2) { this.p1 = p1; this.p2 = p2; } public String toString() { return String.format("(%s and %s)", p1, p2); } @Override public String toXpath() { return "(" + p1.toXpath() + " and " + p2.toXpath() + ")"; } } static final class Or implements ElementProperty { private final ElementProperty p1, p2; public Or(final ElementProperty p1, final ElementProperty p2) { this.p1 = p1; this.p2 = p2; } public String toString() { return String.format("(%s or %s)", p1, p2); } @Override public String toXpath() { return p1.toXpath() + " or " + p2.toXpath(); } } private static class RelationBetweenMultiElement implements ElementProperty{ private final String relation; private final List paths; private final Optional nPath; public RelationBetweenMultiElement(final String relation, final List paths) { this.paths = paths; this.relation = relation; this.nPath = Optional.empty(); } public RelationBetweenMultiElement(final String relation, final NPath nPath) { this.paths = Collections.singletonList(nPath.path); this.relation = relation; this.nPath = Optional.of(nPath); } public String toXpath() { return getRelationXpath(); } protected String asString(final String prefix) { String asList = paths.stream(). map(this::rValueToString). collect(Collectors.joining(", ")); return String.format("%s: %s", plural(prefix), (paths.size() > 1) ? String.format("[%s]", asList) : asList); } protected String plural(final String relation) { return (paths.size() == 1) ? relation : (relation + "s"); } protected String getXpathExpressionForSingle(final Path path) { return String.format("%s::%s", relation, transformXpathToCorrectAxis(path).get()); } private String getRelationForSingleXpath(final Path path) { if (path.getUnderlyingSource().isPresent() || !path.getXPath().isPresent()) throw new IllegalArgumentException("must use a pure xpath Path"); if (path.getXPath().get().startsWith("(")) { throw new IllegalArgumentException("The expression not compile to a proper xpath." + "Please use Path methods instead to express the relation."); } String expressionForSingle = getXpathExpressionForSingle(path); return nPath.map(np -> addNPathQualifier(expressionForSingle)).orElse(expressionForSingle); } private String addNPathQualifier(String path) { return String.format("count(%s)%s%d", path, RelationOperator.opAsXpathString(nPath.get().qualifier), nPath.get().n); } private String getRelationXpath() { final String result = paths.stream(). map(this::getRelationForSingleXpath). collect(Collectors.joining(") and (")); return (paths.size() > 1) ? String.format("(%s)", result) : result; } private String rValueToString(Path path) { return ((path.toString().trim().contains(" "))) ? "(" + path + ")" : path.toString(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy