XPaths.java revision 4b31201b5a2dbf8036da5a8d089a68a39cc1dc44
1cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin/* 2cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * Copyright (C) 2013 DroidDriver committers 3cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * 4cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * Licensed under the Apache License, Version 2.0 (the "License"); 5cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * you may not use this file except in compliance with the License. 6cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * You may obtain a copy of the License at 7cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * 8cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * http://www.apache.org/licenses/LICENSE-2.0 9cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * 10cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * Unless required by applicable law or agreed to in writing, software 11cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * distributed under the License is distributed on an "AS IS" BASIS, 12cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * See the License for the specific language governing permissions and 14cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * limitations under the License. 15cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin */ 16cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin 174b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinpackage io.appium.droiddriver.finders; 18cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin 1917342a5115d7575d44a99fed9c7032e3ab316dccKevin Jinimport android.text.TextUtils; 20d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin 21cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin/** 22cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * Convenience methods and constants for XPath. 235c0ca5383d9a90b6d5e9c246f387e6261fed6211Kevin Jin * <p> 245c0ca5383d9a90b6d5e9c246f387e6261fed6211Kevin Jin * DroidDriver implementation uses default XPath library on device, so the 255c0ca5383d9a90b6d5e9c246f387e6261fed6211Kevin Jin * support may be limited to <a href="http://www.w3.org/TR/xpath/">XPath 265c0ca5383d9a90b6d5e9c246f387e6261fed6211Kevin Jin * 1.0</a>. Newer XPath features may not be supported, for example, the 275c0ca5383d9a90b6d5e9c246f387e6261fed6211Kevin Jin * fn:matches function. 28cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin */ 29cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jinpublic class XPaths { 30cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin 31cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin private XPaths() {} 32cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin 33cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin /** 34cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * @return The tag name used to build UiElement DOM. It is preferable to use 35cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * this to build XPath instead of String literals. 36cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin */ 37cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin public static String tag(String className) { 38cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin return simpleClassName(className); 39cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin } 40cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin 41cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin /** 42cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * @return The tag name used to build UiElement DOM. It is preferable to use 43cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * this to build XPath instead of String literals. 44cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin */ 45cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin public static String tag(Class<?> clazz) { 4617342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin return tag(clazz.getSimpleName()); 47cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin } 48cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin 49cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin private static String simpleClassName(String name) { 50cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin // the nth anonymous class has a class name ending in "Outer$n" 51cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin // and local inner classes have names ending in "Outer.$1Inner" 52cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin name = name.replaceAll("\\$[0-9]+", "\\$"); 53cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin 54cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin // we want the name of the inner class all by its lonesome 55cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin int start = name.lastIndexOf('$'); 56cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin 57cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin // if this isn't an inner class, just find the start of the 58cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin // top level class name. 59cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin if (start == -1) { 60cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin start = name.lastIndexOf('.'); 61cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin } 62cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin return name.substring(start + 1); 63cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin } 64cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin 65cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin /** 66cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * @return XPath predicate (with enclosing []) for boolean attribute that is 67cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * present 68cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin */ 69cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin public static String is(Attribute attribute) { 70cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin return "[@" + attribute.getName() + "]"; 71cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin } 72cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin 73cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin /** 74cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * @return XPath predicate (with enclosing []) for boolean attribute that is 75cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin * NOT present 76cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin */ 77cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin public static String not(Attribute attribute) { 78cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin return "[not(@" + attribute.getName() + ")]"; 79cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin } 80cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin 81cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin /** @return XPath predicate (with enclosing []) for attribute with value */ 82cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin public static String attr(Attribute attribute, String value) { 83d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin return String.format("[@%s=%s]", attribute.getName(), quoteXPathLiteral(value)); 84cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin } 855c0ca5383d9a90b6d5e9c246f387e6261fed6211Kevin Jin 869f554eb6d4d25a0a31be3ab88fb715fc3cee4027Tony Wickham /** @return XPath predicate (with enclosing []) for attribute containing value */ 879f554eb6d4d25a0a31be3ab88fb715fc3cee4027Tony Wickham public static String containsAttr(Attribute attribute, String containedValue) { 88d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin return String.format("[contains(@%s, %s)]", attribute.getName(), 89d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin quoteXPathLiteral(containedValue)); 909f554eb6d4d25a0a31be3ab88fb715fc3cee4027Tony Wickham } 919f554eb6d4d25a0a31be3ab88fb715fc3cee4027Tony Wickham 925c0ca5383d9a90b6d5e9c246f387e6261fed6211Kevin Jin /** Shorthand for {@link #attr}{@code (Attribute.TEXT, value)} */ 935c0ca5383d9a90b6d5e9c246f387e6261fed6211Kevin Jin public static String text(String value) { 945c0ca5383d9a90b6d5e9c246f387e6261fed6211Kevin Jin return attr(Attribute.TEXT, value); 955c0ca5383d9a90b6d5e9c246f387e6261fed6211Kevin Jin } 96d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin 9770e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin /** Shorthand for {@link #attr}{@code (Attribute.RESOURCE_ID, value)} */ 9870e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin public static String resourceId(String value) { 9970e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin return attr(Attribute.RESOURCE_ID, value); 10070e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin } 10170e34108e0fc19277e642aef3b36b65b8e254899Kevin Jin 102d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin /** 1030319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin * @return XPath predicate (with enclosing []) that filters nodes with 1040319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin * descendants satisfying {@code descendantPredicate}. 1050319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin */ 1060319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin public static String withDescendant(String descendantPredicate) { 1070319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin return "[.//*" + descendantPredicate + "]"; 1080319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin } 1090319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin 1100319e7c14a536a11851cc30cfa57241ce90fec11Kevin Jin /** 111d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin * Adapted from http://stackoverflow.com/questions/1341847/. 112d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin * <p> 113d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin * Produce an XPath literal equal to the value if possible; if not, produce an 114d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin * XPath expression that will match the value. Note that this function will 115d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin * produce very long XPath expressions if a value contains a long run of 116d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin * double quotes. 117d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin */ 118ffd0f7a9a89c3c19bc5846b23237d96ed8accbfdKevin Jin static String quoteXPathLiteral(String value) { 119d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin // if the value contains only single or double quotes, construct an XPath 120d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin // literal 121d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin if (!value.contains("\"")) { 122d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin return "\"" + value + "\""; 123d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin } 124d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin if (!value.contains("'")) { 125d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin return "'" + value + "'"; 126d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin } 127d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin 128d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin // if the value contains both single and double quotes, construct an 129d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin // expression that concatenates all non-double-quote substrings with 130d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin // the quotes, e.g.: 131d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin // concat("foo", '"', "bar") 132d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin StringBuilder sb = new StringBuilder(); 133d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin sb.append("concat(\""); 13417342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin sb.append(TextUtils.join("\",'\"',\"", value.split("\""))); 135d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin sb.append("\")"); 136d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin return sb.toString(); 137d88d4ab289d47627418aa500d43b9c11168acba7Kevin Jin } 138cd9468bc18d0e8250fc495f1ec656667eb206526Kevin Jin} 139