</any>\r
</generate-with>\r
\r
- <generate-with class="com.google.gwt.query.rebind.SelectorGeneratorXPath">\r
+ <generate-with class="com.google.gwt.query.rebind.SelectorGeneratorCssToXPath">\r
<when-type-assignable class="com.google.gwt.query.client.Selectors"/>\r
<any>\r
<when-property-is name="user.agent" value="gecko1_8"/>\r
class="com.google.gwt.query.client.impl.SelectorEngineImpl"/>\r
</replace-with>\r
\r
- <replace-with class="com.google.gwt.query.client.impl.SelectorEngineXPath">\r
+ <replace-with class="com.google.gwt.query.client.impl.SelectorEngineCssToXPath">\r
<when-type-assignable\r
class="com.google.gwt.query.client.impl.SelectorEngineImpl"/>\r
<any>\r
--- /dev/null
+/*\r
+ * Copyright 2009 Google Inc.\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not\r
+ * use this file except in compliance with the License. You may obtain a copy of\r
+ * the License at\r
+ * \r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT\r
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\r
+ * License for the specific language governing permissions and limitations under\r
+ * the License.\r
+ */\r
+package com.google.gwt.query.client.impl;\r
+\r
+import java.util.ArrayList;\r
+\r
+import com.google.gwt.core.client.JsArray;\r
+import com.google.gwt.dom.client.Element;\r
+import com.google.gwt.dom.client.Node;\r
+import com.google.gwt.dom.client.NodeList;\r
+import com.google.gwt.query.client.JSArray;\r
+import com.google.gwt.query.client.Regexp;\r
+import com.google.gwt.query.client.SelectorEngine;\r
+\r
+/**\r
+ * Runtime selector engine implementation which translates selectors to XPath\r
+ * and delegates to document.evaluate(). \r
+ * It is based on the regular expressions in Andrea Giammarchi's Css2Xpath \r
+ */\r
+public class SelectorEngineCssToXPath extends SelectorEngineImpl {\r
+ \r
+ /**\r
+ * Interface for callbacks in replaceAll operations.\r
+ */\r
+ public static interface ReplaceCallback {\r
+ String foundMatch(ArrayList<String> s);\r
+ }\r
+ \r
+ /**\r
+ * Interface for replacer implementations (GWT and JVM).\r
+ */\r
+ public static interface Replacer {\r
+ String replaceAll(String s, String expr, Object replacement);\r
+ }\r
+ \r
+ private static SelectorEngineCssToXPath instance;\r
+ \r
+ private static ReplaceCallback rc_$Attr = new ReplaceCallback() {\r
+ public String foundMatch(ArrayList<String> s) {\r
+ return "[substring(@" + s.get(1) + ",string-length(@" + s.get(1) + ")-" + (s.get(2).replaceAll("'", "").length() - 1) + ")=" + s.get(2) + "]";\r
+ }\r
+ };\r
+ \r
+ private static ReplaceCallback rc_Not = new ReplaceCallback() {\r
+ public String foundMatch(ArrayList<String> s) {\r
+ return s.get(1) + "[not(" + getInstance().css2Xpath(s.get(2)).replaceAll("^[^\\[]+\\[([^\\]]*)\\].*$", "$1" + ")]");\r
+ }\r
+ };\r
+ \r
+ private static ReplaceCallback rc_nth_child = new ReplaceCallback() {\r
+ public String foundMatch(ArrayList<String> s) {\r
+ if (s.get(1).length() == 0) {\r
+ s.set(1, "0");\r
+ }\r
+ if ("n".equals(s.get(2))) {\r
+ return s.get(1);\r
+ }\r
+ if ("even".equals(s.get(2))) {\r
+ return "*[position() mod 2=0 and position()>=0]/self::" + s.get(1);\r
+ }\r
+ if ("odd".equals(s.get(2))) {\r
+ return s.get(1) + "[(count(preceding-sibling::*) + 1) mod 2=1]";\r
+ }\r
+ String[] t = s.get(2).replaceAll("^([0-9]*)n.*?([0-9]*)?$", "$1+$2").split("\\+");\r
+ String t0 = t[0];\r
+ String t1 = t.length > 1 ? t[1] : "0";\r
+ return "*[(position()-" + t1 + ") mod " + t0 + "=0 and position()>=" + t1 + "]/self::" + s.get(1);\r
+ }\r
+ };\r
+\r
+ private static Object[] regs = new Object[]{\r
+ "\\[([^\\]~\\$\\*\\^\\|\\!]+)(=[^\\]]+)?\\]", "[@$1$2]",\r
+ // multiple queries\r
+ "\\s*,\\s*", "|",\r
+ // , + ~ >\r
+ "\\s*(\\+|~|>)\\s*", "$1",\r
+ //* ~ + >\r
+ "([a-zA-Z0-9_\\-\\*])~([a-zA-Z0-9_\\-\\*])", "$1/following-sibling::$2",\r
+ "([a-zA-Z0-9_\\-\\*])\\+([a-zA-Z0-9_\\-\\*])", "$1/following-sibling::*[1]/self::$2",\r
+ "([a-zA-Z0-9_\\-\\*])>([a-zA-Z0-9_\\-\\*])", "$1/$2",\r
+ // all unescaped stuff escaped\r
+ "\\[([^=]+)=([^'|\"][^\\]]*)\\]", "[$1='$2']",\r
+ // all descendant or self to \r
+ "(^|[^a-zA-Z0-9_\\-\\*])(#|\\.)([a-zA-Z0-9_\\-]+)", "$1*$2$3",\r
+ "([\\>\\+\\|\\~\\,\\s])([a-zA-Z\\*]+)", "$1//$2",\r
+ "\\s+//", "//",\r
+ // :first-child\r
+ "([a-zA-Z0-9_\\-\\*]+):first-child", "*[1]/self::$1",\r
+ // :first\r
+ "([a-zA-Z0-9_\\-\\*]+):first", "*[1]/self::$1",\r
+ // :last-child\r
+ "([a-zA-Z0-9_\\-\\*]+):last-child", "$1[not(following-sibling::*)]",\r
+ // :only-child\r
+ "([a-zA-Z0-9_\\-\\*]+):only-child", "*[last()=1]/self::$1",\r
+ // :empty\r
+ "([a-zA-Z0-9_\\-\\*]+):empty", "$1[not(*) and not(normalize-space())]",\r
+ "(.+):not\\(([^\\)]*)\\)", rc_Not,\r
+ "([a-zA-Z0-9\\_\\-\\*]+):nth-child\\(([^\\)]*)\\)", rc_nth_child,\r
+ // :contains(selectors)\r
+ ":contains\\(([^\\)]*)\\)", "[contains(string(.),'$1')]",\r
+ // |= attrib\r
+ "\\[([a-zA-Z0-9_\\-]+)\\|=([^\\]]+)\\]", "[@$1=$2 or starts-with(@$1,concat($2,'-'))]",\r
+ // *= attrib\r
+ "\\[([a-zA-Z0-9_\\-]+)\\*=([^\\]]+)\\]", "[contains(@$1,$2)]",\r
+ // ~= attrib\r
+ "\\[([a-zA-Z0-9_\\-]+)~=([^\\]]+)\\]", "[contains(concat(' ',normalize-space(@$1),' '),concat(' ',$2,' '))]",\r
+ // ^= attrib\r
+ "\\[([a-zA-Z0-9_\\-]+)\\^=([^\\]]+)\\]", "[starts-with(@$1,$2)]",\r
+ // $= attrib\r
+ "\\[([a-zA-Z0-9_\\-]+)\\$=([^\\]]+)\\]", rc_$Attr,\r
+ // != attrib\r
+ "\\[([a-zA-Z0-9_\\-]+)\\!=([^\\]]+)\\]", "[not(@$1) or @$1!=$2]",\r
+ // ids and classes\r
+ "#([a-zA-Z0-9_\\-]+)", "[@id='$1']",\r
+ "\\.([a-zA-Z0-9_\\-]+)", "[contains(concat(' ',normalize-space(@class),' '),' $1 ')]",\r
+ // normalize multiple filters\r
+ "\\]\\[([^\\]]+)", " and ($1)"};\r
+ \r
+ public static SelectorEngineCssToXPath getInstance() {\r
+ if (instance == null) {\r
+ instance = new SelectorEngineCssToXPath();\r
+ }\r
+ return instance;\r
+ }\r
+\r
+ // This replacer only works in browser, it must be replaced\r
+ // when using this engine in generators and tests for the JVM\r
+ private Replacer replacer = new Replacer() {\r
+ public String replaceAll(String s, String r, Object o) {\r
+ Regexp p = new Regexp(r);\r
+ if (o instanceof ReplaceCallback) {\r
+ ReplaceCallback callback = (ReplaceCallback) o;\r
+ while (p.test(s)) {\r
+ JSArray a = p.match(s);\r
+ ArrayList<String> args = new ArrayList<String>();\r
+ for (int i = 0; i < a.getLength(); i++) {\r
+ args.add(a.getStr(i));\r
+ }\r
+ String f = callback.foundMatch(args);\r
+ s = s.replaceFirst(r, f);\r
+ }\r
+ return s;\r
+ } else {\r
+ return s.replaceAll(r, o.toString()); \r
+ }\r
+ }\r
+ };\r
+\r
+ public SelectorEngineCssToXPath() {\r
+ instance = this;\r
+ }\r
+ \r
+ public SelectorEngineCssToXPath(Replacer r) {\r
+ replacer = r;\r
+ instance = this;\r
+ }\r
+ \r
+ public String css2Xpath(String selector) {\r
+ String ret = selector;\r
+ for (int i = 0; i < regs.length;) {\r
+ ret = replacer.replaceAll(ret, regs[i++].toString(), regs[i++]);\r
+ }\r
+ return "//" + ret;\r
+ }\r
+ \r
+ public NodeList<Element> select(String sel, Node ctx) {\r
+ JSArray elm = JSArray.create();\r
+ if (!sel.startsWith("//") && !sel.startsWith("./") && !sel.startsWith("/")) {\r
+ sel = css2Xpath(sel);\r
+ }\r
+ SelectorEngine.xpathEvaluate(sel, ctx, elm);\r
+ return unique(elm.<JsArray<Element>> cast()).cast();\r
+ }\r
+ \r
+}\r
}
return @com.google.gwt.query.client.impl.SelectorEngineSizzle::filter(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;ZLjava/lang/Object;)( later, tmpSet, false );
}-*/;
-
+
private static native JsArray<Element> select(String selector, Node context, JsArray<Element> results, JsArray<Element> seed) /*-{
results = results || [];
var origContext = context = context || document;
}
if ( extra ) {
@com.google.gwt.query.client.impl.SelectorEngineSizzle::select(Ljava/lang/String;Lcom/google/gwt/dom/client/Node;Lcom/google/gwt/core/client/JsArray;Lcom/google/gwt/core/client/JsArray;)(extra, origContext, results, seed);
- @com.google.gwt.query.client.impl.SelectorEngineSizzle::unique(Lcom/google/gwt/core/client/JsArray;)(results);
}
return results;
}-*/;
public NodeList<Element> select(String selector, Node context) {
JsArray<Element> results = JavaScriptObject.createArray().cast();
- return select(selector, context, results, null).cast();
+ return unique(select(selector, context, results, null)).cast();
}
}
--- /dev/null
+/*\r
+ * Copyright 2009 Google Inc.\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not\r
+ * use this file except in compliance with the License. You may obtain a copy of\r
+ * the License at\r
+ * \r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT\r
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\r
+ * License for the specific language governing permissions and limitations under\r
+ * the License.\r
+ */\r
+package com.google.gwt.query.rebind;\r
+\r
+import java.util.ArrayList;\r
+import java.util.regex.MatchResult;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+\r
+import com.google.gwt.core.ext.TreeLogger;\r
+import com.google.gwt.core.ext.UnableToCompleteException;\r
+import com.google.gwt.core.ext.typeinfo.JMethod;\r
+import com.google.gwt.query.client.Selector;\r
+import com.google.gwt.query.client.impl.SelectorEngineCssToXPath;\r
+import com.google.gwt.query.client.impl.SelectorEngineCssToXPath.ReplaceCallback;\r
+import com.google.gwt.query.client.impl.SelectorEngineCssToXPath.Replacer;\r
+import com.google.gwt.user.rebind.SourceWriter;\r
+\r
+/**\r
+ * Compile time selector generator which translates selector into XPath at\r
+ * compile time. It Uses the SelectorEngineCssToXpath to produce the xpath\r
+ * selectors\r
+ */\r
+public class SelectorGeneratorCssToXPath extends SelectorGeneratorBase {\r
+\r
+ /**\r
+ * The replacer implementation for the JVM.\r
+ */\r
+ public static final Replacer replacer = new Replacer() {\r
+ public String replaceAll(String s, String r, Object o) {\r
+ Pattern p = Pattern.compile(r);\r
+ if (o instanceof ReplaceCallback) {\r
+ final Matcher matcher = p.matcher(s);\r
+ ReplaceCallback callback = (ReplaceCallback) o;\r
+ while (matcher.find()) {\r
+ final MatchResult matchResult = matcher.toMatchResult();\r
+ ArrayList<String> argss = new ArrayList<String>();\r
+ for (int i = 0; i < matchResult.groupCount() + 1; i++) {\r
+ argss.add(matchResult.group(i));\r
+ } \r
+ final String replacement = callback.foundMatch(argss);\r
+ s = s.substring(0, matchResult.start()) + replacement + s.substring(matchResult.end());\r
+ matcher.reset(s);\r
+ }\r
+ return s;\r
+ } else {\r
+ return p.matcher(s).replaceAll(o.toString()); \r
+ }\r
+ }\r
+ };\r
+\r
+ private SelectorEngineCssToXPath engine = new SelectorEngineCssToXPath(\r
+ replacer);\r
+\r
+ protected String css2Xpath(String s) {\r
+ return engine.css2Xpath(s);\r
+ }\r
+\r
+ protected void generateMethodBody(SourceWriter sw, JMethod method,\r
+ TreeLogger treeLogger, boolean hasContext)\r
+ throws UnableToCompleteException {\r
+\r
+ String selector = method.getAnnotation(Selector.class).value();\r
+ sw.println("return "\r
+ + wrap(method, "SelectorEngine.xpathEvaluate(\"" + css2Xpath(selector)\r
+ + "\", root)") + ";");\r
+ }\r
+\r
+ protected String getImplSuffix() {\r
+ return "XPath" + super.getImplSuffix();\r
+ }\r
+\r
+}\r
// assertArrayContains(sel.title().getLength(), 1);
assertEquals(1, sel.body().getLength());
- assertEquals(53, sel.bodyDiv().getLength());
+ assertArrayContains(sel.bodyDiv().getLength(), 53, 55);
sel.setRoot(e);
assertArrayContains(sel.aHrefLangClass().getLength(), 0, 1);
assertArrayContains(sel.allChecked().getLength(), 1);
$(e).html(getTestContent());
assertArrayContains(selEng.select("body", Document.get()).getLength(), 1);
- assertArrayContains(selEng.select("body div", Document.get()).getLength(), 53);
+ assertArrayContains(selEng.select("body div", Document.get()).getLength(), 53, 55);
assertArrayContains(selEng.select("h1[id]:contains(Selectors)", e).getLength(), 1);
assertArrayContains(selEng.select("*:first", e).getLength(), 1, 0);
--- /dev/null
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * 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;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Test for selector engine implementations
+ */
+public class SelectorEnginesTest extends GWTTestCase {
+
+ public String getModuleName() {
+ return "com.google.gwt.query.Query";
+ }
+
+ public void testCssToXpath() {
+ SelectorEngineCssToXPath sel = new SelectorEngineCssToXPath();
+
+ assertEquals("//div[starts-with(@class,'exa') and (substring(@class,string-length(@class)-3)='mple')]",
+ sel.css2Xpath("div[class^=exa][class$=mple]"));
+ assertEquals("//div[not(contains(concat(' ',normalize-space(@class),' '),' example '))]",
+ sel.css2Xpath("div:not(.example)"));
+
+ assertEquals("//p",
+ sel.css2Xpath("p:nth-child(n)"));
+ assertEquals("//p[(count(preceding-sibling::*) + 1) mod 2=1]",
+ sel.css2Xpath("p:nth-child(odd)"));
+ assertEquals("//*[(position()-0) mod 2=0 and position()>=0]/self::p",
+ sel.css2Xpath("p:nth-child(2n)"));
+
+ assertEquals("//div[substring(@class,string-length(@class)-3)='mple']",
+ sel.css2Xpath("div[class$=mple]"));
+ assertEquals("//div[substring(@class,string-length(@class)-5)='xample']",
+ sel.css2Xpath("div[class$=xample]"));
+
+ assertEquals("//div[not(contains(concat(' ',normalize-space(@class),' '),' example '))]",
+ sel.css2Xpath("div:not(.example)"));
+
+ }
+
+
+}
--- /dev/null
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * 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.rebind;
+
+import java.util.ArrayList;
+
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.query.client.impl.SelectorEngineCssToXPath.ReplaceCallback;
+
+/**
+ * Test class for selector generators.
+ */
+public class SelectorGeneratorsTest extends GWTTestCase {
+
+ public String getModuleName() {
+ return null;
+ }
+
+ public void testCss2Xpath() {
+ SelectorGeneratorCssToXPath sel = new SelectorGeneratorCssToXPath();
+
+ assertEquals("//div[starts-with(@class,'exa') and (substring(@class,string-length(@class)-3)='mple')]",
+ sel.css2Xpath("div[class^=exa][class$=mple]"));
+ assertEquals("//div[not(contains(concat(' ',normalize-space(@class),' '),' example '))]",
+ sel.css2Xpath("div:not(.example)"));
+
+ assertEquals("//p",
+ sel.css2Xpath("p:nth-child(n)"));
+ assertEquals("//p[(count(preceding-sibling::*) + 1) mod 2=1]",
+ sel.css2Xpath("p:nth-child(odd)"));
+ assertEquals("//*[(position()-0) mod 2=0 and position()>=0]/self::p",
+ sel.css2Xpath("p:nth-child(2n)"));
+
+ assertEquals("//div[substring(@class,string-length(@class)-3)='mple']",
+ sel.css2Xpath("div[class$=mple]"));
+ assertEquals("//div[substring(@class,string-length(@class)-5)='xample']",
+ sel.css2Xpath("div[class$=xample]"));
+
+ assertEquals("//div[not(contains(concat(' ',normalize-space(@class),' '),' example '))]",
+ sel.css2Xpath("div:not(.example)"));
+ }
+
+ public void testReplaceAll() {
+ assertEquals("<img src=\"thumbs/01\"/> <img src=\"thumbs/03\"/>", SelectorGeneratorCssToXPath.replacer.replaceAll("/[thumb01]/ /[thumb03]/", "/\\[thumb(\\d+)\\]/", new ReplaceCallback() {
+ public String foundMatch(ArrayList<String>s) {
+ return "<img src=\"thumbs/" + s.get(1) + "\"/>";
+ }
+ }));
+ }
+}