com.google.gwt.query.client.impl.research.SelectorEngineXPath Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2011, The gwtquery team.
*
* 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 com.google.gwt.query.client.impl.research;
import static com.google.gwt.query.client.js.JsUtils.eq;
import static com.google.gwt.query.client.js.JsUtils.truth;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.query.client.impl.SelectorEngine;
import com.google.gwt.query.client.impl.SelectorEngineImpl;
import com.google.gwt.query.client.impl.research.SelectorEngineJS.Sequence;
import com.google.gwt.query.client.impl.research.SelectorEngineJS.SplitRule;
import com.google.gwt.query.client.js.JsNodeArray;
import com.google.gwt.query.client.js.JsObjectArray;
import com.google.gwt.query.client.js.JsRegexp;
import com.google.gwt.query.client.js.JsUtils;
/**
* Runtime selector engine implementation which translates selectors to XPath
* and delegates to document.evaluate().
*/
public class SelectorEngineXPath extends SelectorEngineImpl {
private static String attrToXPath(String match, String p1, String p2,
String p3) {
if (eq("^", p2)) {
return "starts-with(@" + p1 + ", '" + p3 + "')";
}
if (eq("$", p2)) {
return "substring(@" + p1 + ", (string-length(@" + p1 + ") - "
+ (p3.length() - 1) + "), " + p3.length() + ") = '" + p3 + "'";
}
if (eq("*", p2)) {
return "contains(concat(' ', @" + p1 + ", ' '), '" + p3 + "')";
}
if (eq("|", p2)) {
return "(@" + p1 + "='" + p3 + "' or starts-with(@" + p1 + ", '" + p3
+ "-'))";
}
if (eq("~", p2)) {
return "contains(concat(' ', @" + p1 + ", ' '), ' " + p3 + " ')";
}
return "@" + p1 + (truth(p3) ? "='" + p3 + "'" : "");
}
private JsRegexp cssSelectorRegExp;
private JsRegexp selectorSplitRegExp;
private JsRegexp combinator;
public SelectorEngineXPath() {
}
public NodeList select(String sel, Node ctx) {
init();
String selectors[] = sel.replaceAll("\\s*(,)\\s*", "$1").split(",");
boolean identical = false;
JsNodeArray elm = JsNodeArray.create();
for (int a = 0, len = selectors.length; a < len; a++) {
if (a > 0) {
identical = false;
for (int b = 0, bl = a; b < bl; b++) {
if (eq(selectors[a], selectors[b])) {
identical = true;
break;
}
}
if (identical) {
continue;
}
}
String currentRule = selectors[a];
JsObjectArray cssSelectors = selectorSplitRegExp.match(currentRule);
String xPathExpression = ".";
for (int i = 0, slen = cssSelectors.length(); i < slen; i++) {
String rule = cssSelectors.get(i);
JsObjectArray cssSelector = cssSelectorRegExp.exec(rule);
SplitRule splitRule = new SplitRule(
!truth(cssSelector.get(1)) || eq(cssSelector.get(3), "*")
? "*" : cssSelector.get(1),
!eq(cssSelector.get(3), "*") ? cssSelector.get(2) : null,
cssSelector.get(4), cssSelector.get(6),
cssSelector.get(10), cssSelector.get(22));
if (truth(splitRule.tagRelation)) {
if (eq(">", splitRule.tagRelation)) {
xPathExpression += "/child::";
} else if (eq("+", splitRule.tagRelation)) {
xPathExpression += "/following-sibling::*[1]/self::";
} else if (eq("~", splitRule.tagRelation)) {
xPathExpression += "/following-sibling::";
}
} else {
xPathExpression +=
(i > 0 && combinator.test(cssSelectors.get(i - 1)))
? splitRule.tag : ("/descendant::" + splitRule.tag);
}
if (truth(splitRule.id)) {
xPathExpression += "[@id = '" + splitRule.id.replaceAll("^#", "")
+ "']";
}
if (truth(splitRule.allClasses)) {
xPathExpression += splitRule.allClasses
.replaceAll("\\.([\\w\\u00C0-\\uFFFF\\-_]+)",
"[contains(concat(' ', @class, ' '), ' $1 ')]");
}
if (truth(splitRule.allAttr)) {
xPathExpression += replaceAttr(
JsUtils.or(splitRule.allAttr, ""));
}
if (truth(splitRule.allPseudos)) {
JsRegexp pseudoSplitRegExp = new JsRegexp(
":(\\w[\\w\\-]*)(\\(([^\\)]+)\\))?");
JsRegexp pseudoMatchRegExp = new JsRegexp(
"(:\\w+[\\w\\-]*)(\\([^\\)]+\\))?", "g");
JsObjectArray allPseudos = pseudoMatchRegExp.match(splitRule.allPseudos);
for (int k = 0, kl = allPseudos.length(); k < kl; k++) {
JsObjectArray pseudo = pseudoSplitRegExp.match(allPseudos.get(k));
String pseudoClass = truth(pseudo.get(1)) ? pseudo.get(1)
.toLowerCase() : null;
String pseudoValue = truth(pseudo.get(3)) ? pseudo.get(3)
: null;
String xpath = pseudoToXPath(splitRule.tag, pseudoClass,
pseudoValue);
if (xpath.length() > 0) {
xPathExpression += "[" + xpath + "]";
}
}
}
}
SelectorEngine.xpathEvaluate(xPathExpression, ctx, elm);
}
return JsUtils.unique(elm.> cast()).cast();
}
private void init() {
if (cssSelectorRegExp == null) {
cssSelectorRegExp =
new JsRegexp(
"^(\\w+)?(#[\\w\\u00C0-\\uFFFF\\-\\_]+|(\\*))?((\\.[\\w\\u00C0-\\uFFFF\\-_]+)*)?((\\[\\w+(\\^|\\$|\\*|\\||~)?(=[\"']*[\\w\\u00C0-\\uFFFF\\s\\-\\_\\.]+[\"']*)?\\]+)*)?(((:\\w+[\\w\\-]*)(\\((odd|even|\\-?\\d*n?((\\+|\\-)\\d+)?|[\\w\\u00C0-\\uFFFF\\-_]+|((\\w*\\.[\\w\\u00C0-\\uFFFF\\-_]+)*)?|(\\[#?\\w+(\\^|\\$|\\*|\\||~)?=?[\\w\\u00C0-\\uFFFF\\s\\-\\_\\.]+\\]+)|(:\\w+[\\w\\-]*))\\))?)*)?(>|\\+|~)?");
selectorSplitRegExp = new JsRegexp("[^\\s]+", "g");
combinator = new JsRegexp("(>|\\+|~)");
}
}
private String pseudoToXPath(String tag, String pseudoClass,
String pseudoValue) {
String xpath = "";
if (eq("first-child", pseudoClass)) {
xpath = "not(preceding-sibling::*)";
} else if (eq("first-of-type", pseudoClass)) {
xpath = "not(preceding-sibling::" + tag + ")";
} else if (eq("last-child", pseudoClass)) {
xpath = "not(following-sibling::*)";
} else if (eq("last-of-type", pseudoClass)) {
xpath = "not(following-sibling::" + tag + ")";
} else if (eq("only-child", pseudoClass)) {
xpath = "not(preceding-sibling::* or following-sibling::*)";
} else if (eq("only-of-type", pseudoClass)) {
xpath = "not(preceding-sibling::" + tag + " or following-sibling::" + tag
+ ")";
} else if (eq("nth-child", pseudoClass)) {
if (!eq("n", pseudoClass)) {
Sequence sequence = SelectorEngineJS.getSequence(pseudoValue);
if (sequence != null) {
if (sequence.start == sequence.max) {
xpath = "count(preceding-sibling::*) = " + (sequence.start - 1);
} else {
xpath = "(count(preceding-sibling::*) + 1) mod " + sequence.add
+ " = " + sequence.modVal
+ ((sequence.start > 1) ? " and count(preceding-sibling::*) >= "
+ (sequence.start - 1) : "") + ((sequence.max > 0) ?
" and count(preceding-sibling::*) <= " + (sequence.max - 1)
: "");
}
}
}
} else if (eq("nth-of-type", pseudoClass)) {
if (!pseudoValue.startsWith("n")) {
Sequence sequence = SelectorEngineJS.getSequence(pseudoValue);
if (sequence != null) {
if (sequence.start == sequence.max) {
xpath = pseudoValue;
} else {
xpath = "position() mod " + sequence.add + " = " + sequence.modVal
+ ((sequence.start > 1) ? " and position() >= " + sequence.start
: "") + ((sequence.max > 0) ? " and position() <= "
+ sequence.max : "");
}
}
}
} else if (eq("empty", pseudoClass)) {
xpath = "count(child::*) = 0 and string-length(text()) = 0";
} else if (eq("contains", pseudoClass)) {
xpath = "contains(., '" + pseudoValue + "')";
} else if (eq("enabled", pseudoClass)) {
xpath = "not(@disabled)";
} else if (eq("disabled", pseudoClass)) {
xpath = "@disabled";
} else if (eq("checked", pseudoClass)) {
xpath = "@checked='checked'"; // Doesn't work in Opera 9.24
} else if (eq("not", pseudoClass)) {
if (new JsRegexp("^(:\\w+[\\w\\-]*)$").test(pseudoValue)) {
xpath = "not(" + pseudoToXPath(tag, pseudoValue.substring(1), "") + ")";
} else {
pseudoValue = pseudoValue
.replaceFirst("^\\[#([\\w\\u00C0-\\uFFFF\\-\\_]+)\\]$", "[id=$1]");
String notSelector = pseudoValue.replaceFirst("^(\\w+)", "self::$1");
notSelector = notSelector.replaceAll("^\\.([\\w\\u00C0-\\uFFFF\\-_]+)",
"contains(concat(' ', @class, ' '), ' $1 ')");
notSelector = replaceAttr2(notSelector);
xpath = "not(" + notSelector + ")";
}
} else {
xpath = "@" + pseudoClass + "='" + pseudoValue + "'";
}
return xpath;
}
private native String replaceAttr(String allAttr) /*-{
if(!allAttr) return "";
return allAttr.replace(/["']+/g,'').replace(/(\w+)(\^|\$|\*|\||~)?=?([\w\u00C0-\uFFFF\s\-_\.]+)?/g,
function(a,b,c,d) {
return @com.google.gwt.query.client.impl.research.SelectorEngineXPath::attrToXPath(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)(a,b || "",c || "",d || "");
});
}-*/;
private native String replaceAttr2(String allAttr) /*-{
if(!allAttr) return "";
return allAttr.replace(/\[(\w+)(\^|\$|\*|\||~)?=?([\w\u00C0-\uFFFF\s\-_\.]+)?\]/g, @com.google.gwt.query.client.impl.research.SelectorEngineXPath::attrToXPath(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;));
}-*/;
}