diff options
author | Manolo Carrasco <manolo@apache.org> | 2010-06-01 09:09:26 +0000 |
---|---|---|
committer | Manolo Carrasco <manolo@apache.org> | 2010-06-01 09:09:26 +0000 |
commit | bb2ec811cb8abdcbbf1a66d5e77fb3d2e8e397ae (patch) | |
tree | dedd743a402e81f16fee8e73aa297a35697a3853 /gwtquery-core | |
parent | f47c9b3e4353a300fd55769bbf97ccce335dd46c (diff) | |
download | gwtquery-bb2ec811cb8abdcbbf1a66d5e77fb3d2e8e397ae.tar.gz gwtquery-bb2ec811cb8abdcbbf1a66d5e77fb3d2e8e397ae.zip |
added a new XPath engine based in Andrea Giammarchi Css2Xpath library
Diffstat (limited to 'gwtquery-core')
7 files changed, 398 insertions, 7 deletions
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml b/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml index bd9a76c3..9d0e83ff 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml +++ b/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml @@ -33,7 +33,7 @@ </any>
</generate-with>
- <generate-with class="com.google.gwt.query.rebind.SelectorGeneratorXPath">
+ <generate-with class="com.google.gwt.query.rebind.SelectorGeneratorCssToXPath">
<when-type-assignable class="com.google.gwt.query.client.Selectors"/>
<any>
<when-property-is name="user.agent" value="gecko1_8"/>
@@ -68,7 +68,7 @@ class="com.google.gwt.query.client.impl.SelectorEngineImpl"/>
</replace-with>
- <replace-with class="com.google.gwt.query.client.impl.SelectorEngineXPath">
+ <replace-with class="com.google.gwt.query.client.impl.SelectorEngineCssToXPath">
<when-type-assignable
class="com.google.gwt.query.client.impl.SelectorEngineImpl"/>
<any>
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineCssToXPath.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineCssToXPath.java new file mode 100644 index 00000000..3e3d482b --- /dev/null +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineCssToXPath.java @@ -0,0 +1,188 @@ +/*
+ * Copyright 2009 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 java.util.ArrayList;
+
+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.JSArray;
+import com.google.gwt.query.client.Regexp;
+import com.google.gwt.query.client.SelectorEngine;
+
+/**
+ * Runtime selector engine implementation which translates selectors to XPath
+ * and delegates to document.evaluate().
+ * It is based on the regular expressions in Andrea Giammarchi's Css2Xpath
+ */
+public class SelectorEngineCssToXPath extends SelectorEngineImpl {
+
+ /**
+ * Interface for callbacks in replaceAll operations.
+ */
+ public static interface ReplaceCallback {
+ String foundMatch(ArrayList<String> s);
+ }
+
+ /**
+ * Interface for replacer implementations (GWT and JVM).
+ */
+ public static interface Replacer {
+ String replaceAll(String s, String expr, Object replacement);
+ }
+
+ private static SelectorEngineCssToXPath instance;
+
+ private static ReplaceCallback rc_$Attr = new ReplaceCallback() {
+ public String foundMatch(ArrayList<String> s) {
+ return "[substring(@" + s.get(1) + ",string-length(@" + s.get(1) + ")-" + (s.get(2).replaceAll("'", "").length() - 1) + ")=" + s.get(2) + "]";
+ }
+ };
+
+ private static ReplaceCallback rc_Not = new ReplaceCallback() {
+ public String foundMatch(ArrayList<String> s) {
+ return s.get(1) + "[not(" + getInstance().css2Xpath(s.get(2)).replaceAll("^[^\\[]+\\[([^\\]]*)\\].*$", "$1" + ")]");
+ }
+ };
+
+ private static ReplaceCallback rc_nth_child = new ReplaceCallback() {
+ public String foundMatch(ArrayList<String> s) {
+ if (s.get(1).length() == 0) {
+ s.set(1, "0");
+ }
+ if ("n".equals(s.get(2))) {
+ return s.get(1);
+ }
+ if ("even".equals(s.get(2))) {
+ return "*[position() mod 2=0 and position()>=0]/self::" + s.get(1);
+ }
+ if ("odd".equals(s.get(2))) {
+ return s.get(1) + "[(count(preceding-sibling::*) + 1) mod 2=1]";
+ }
+ String[] t = s.get(2).replaceAll("^([0-9]*)n.*?([0-9]*)?$", "$1+$2").split("\\+");
+ String t0 = t[0];
+ String t1 = t.length > 1 ? t[1] : "0";
+ return "*[(position()-" + t1 + ") mod " + t0 + "=0 and position()>=" + t1 + "]/self::" + s.get(1);
+ }
+ };
+
+ private static Object[] regs = new Object[]{
+ "\\[([^\\]~\\$\\*\\^\\|\\!]+)(=[^\\]]+)?\\]", "[@$1$2]",
+ // multiple queries
+ "\\s*,\\s*", "|",
+ // , + ~ >
+ "\\s*(\\+|~|>)\\s*", "$1",
+ //* ~ + >
+ "([a-zA-Z0-9_\\-\\*])~([a-zA-Z0-9_\\-\\*])", "$1/following-sibling::$2",
+ "([a-zA-Z0-9_\\-\\*])\\+([a-zA-Z0-9_\\-\\*])", "$1/following-sibling::*[1]/self::$2",
+ "([a-zA-Z0-9_\\-\\*])>([a-zA-Z0-9_\\-\\*])", "$1/$2",
+ // all unescaped stuff escaped
+ "\\[([^=]+)=([^'|\"][^\\]]*)\\]", "[$1='$2']",
+ // all descendant or self to
+ "(^|[^a-zA-Z0-9_\\-\\*])(#|\\.)([a-zA-Z0-9_\\-]+)", "$1*$2$3",
+ "([\\>\\+\\|\\~\\,\\s])([a-zA-Z\\*]+)", "$1//$2",
+ "\\s+//", "//",
+ // :first-child
+ "([a-zA-Z0-9_\\-\\*]+):first-child", "*[1]/self::$1",
+ // :first
+ "([a-zA-Z0-9_\\-\\*]+):first", "*[1]/self::$1",
+ // :last-child
+ "([a-zA-Z0-9_\\-\\*]+):last-child", "$1[not(following-sibling::*)]",
+ // :only-child
+ "([a-zA-Z0-9_\\-\\*]+):only-child", "*[last()=1]/self::$1",
+ // :empty
+ "([a-zA-Z0-9_\\-\\*]+):empty", "$1[not(*) and not(normalize-space())]",
+ "(.+):not\\(([^\\)]*)\\)", rc_Not,
+ "([a-zA-Z0-9\\_\\-\\*]+):nth-child\\(([^\\)]*)\\)", rc_nth_child,
+ // :contains(selectors)
+ ":contains\\(([^\\)]*)\\)", "[contains(string(.),'$1')]",
+ // |= attrib
+ "\\[([a-zA-Z0-9_\\-]+)\\|=([^\\]]+)\\]", "[@$1=$2 or starts-with(@$1,concat($2,'-'))]",
+ // *= attrib
+ "\\[([a-zA-Z0-9_\\-]+)\\*=([^\\]]+)\\]", "[contains(@$1,$2)]",
+ // ~= attrib
+ "\\[([a-zA-Z0-9_\\-]+)~=([^\\]]+)\\]", "[contains(concat(' ',normalize-space(@$1),' '),concat(' ',$2,' '))]",
+ // ^= attrib
+ "\\[([a-zA-Z0-9_\\-]+)\\^=([^\\]]+)\\]", "[starts-with(@$1,$2)]",
+ // $= attrib
+ "\\[([a-zA-Z0-9_\\-]+)\\$=([^\\]]+)\\]", rc_$Attr,
+ // != attrib
+ "\\[([a-zA-Z0-9_\\-]+)\\!=([^\\]]+)\\]", "[not(@$1) or @$1!=$2]",
+ // ids and classes
+ "#([a-zA-Z0-9_\\-]+)", "[@id='$1']",
+ "\\.([a-zA-Z0-9_\\-]+)", "[contains(concat(' ',normalize-space(@class),' '),' $1 ')]",
+ // normalize multiple filters
+ "\\]\\[([^\\]]+)", " and ($1)"};
+
+ public static SelectorEngineCssToXPath getInstance() {
+ if (instance == null) {
+ instance = new SelectorEngineCssToXPath();
+ }
+ return instance;
+ }
+
+ // This replacer only works in browser, it must be replaced
+ // when using this engine in generators and tests for the JVM
+ private Replacer replacer = new Replacer() {
+ public String replaceAll(String s, String r, Object o) {
+ Regexp p = new Regexp(r);
+ if (o instanceof ReplaceCallback) {
+ ReplaceCallback callback = (ReplaceCallback) o;
+ while (p.test(s)) {
+ JSArray a = p.match(s);
+ ArrayList<String> args = new ArrayList<String>();
+ for (int i = 0; i < a.getLength(); i++) {
+ args.add(a.getStr(i));
+ }
+ String f = callback.foundMatch(args);
+ s = s.replaceFirst(r, f);
+ }
+ return s;
+ } else {
+ return s.replaceAll(r, o.toString());
+ }
+ }
+ };
+
+ public SelectorEngineCssToXPath() {
+ instance = this;
+ }
+
+ public SelectorEngineCssToXPath(Replacer r) {
+ replacer = r;
+ instance = this;
+ }
+
+ public String css2Xpath(String selector) {
+ String ret = selector;
+ for (int i = 0; i < regs.length;) {
+ ret = replacer.replaceAll(ret, regs[i++].toString(), regs[i++]);
+ }
+ return "//" + ret;
+ }
+
+ public NodeList<Element> select(String sel, Node ctx) {
+ JSArray elm = JSArray.create();
+ if (!sel.startsWith("//") && !sel.startsWith("./") && !sel.startsWith("/")) {
+ sel = css2Xpath(sel);
+ }
+ SelectorEngine.xpathEvaluate(sel, ctx, elm);
+ return unique(elm.<JsArray<Element>> cast()).cast();
+ }
+
+}
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineSizzle.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineSizzle.java index 57af8277..389b424a 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineSizzle.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineSizzle.java @@ -628,7 +628,7 @@ public class SelectorEngineSizzle extends SelectorEngineImpl { } 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; @@ -718,7 +718,6 @@ public class SelectorEngineSizzle extends SelectorEngineImpl { } 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; }-*/; @@ -729,6 +728,6 @@ public class SelectorEngineSizzle extends SelectorEngineImpl { 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(); } } diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/rebind/SelectorGeneratorCssToXPath.java b/gwtquery-core/src/main/java/com/google/gwt/query/rebind/SelectorGeneratorCssToXPath.java new file mode 100644 index 00000000..04582af6 --- /dev/null +++ b/gwtquery-core/src/main/java/com/google/gwt/query/rebind/SelectorGeneratorCssToXPath.java @@ -0,0 +1,86 @@ +/*
+ * Copyright 2009 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 java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.query.client.Selector;
+import com.google.gwt.query.client.impl.SelectorEngineCssToXPath;
+import com.google.gwt.query.client.impl.SelectorEngineCssToXPath.ReplaceCallback;
+import com.google.gwt.query.client.impl.SelectorEngineCssToXPath.Replacer;
+import com.google.gwt.user.rebind.SourceWriter;
+
+/**
+ * Compile time selector generator which translates selector into XPath at
+ * compile time. It Uses the SelectorEngineCssToXpath to produce the xpath
+ * selectors
+ */
+public class SelectorGeneratorCssToXPath extends SelectorGeneratorBase {
+
+ /**
+ * The replacer implementation for the JVM.
+ */
+ public static final Replacer replacer = new Replacer() {
+ public String replaceAll(String s, String r, Object o) {
+ Pattern p = Pattern.compile(r);
+ if (o instanceof ReplaceCallback) {
+ final Matcher matcher = p.matcher(s);
+ ReplaceCallback callback = (ReplaceCallback) o;
+ while (matcher.find()) {
+ final MatchResult matchResult = matcher.toMatchResult();
+ ArrayList<String> argss = new ArrayList<String>();
+ for (int i = 0; i < matchResult.groupCount() + 1; i++) {
+ argss.add(matchResult.group(i));
+ }
+ final String replacement = callback.foundMatch(argss);
+ s = s.substring(0, matchResult.start()) + replacement + s.substring(matchResult.end());
+ matcher.reset(s);
+ }
+ return s;
+ } else {
+ return p.matcher(s).replaceAll(o.toString());
+ }
+ }
+ };
+
+ private SelectorEngineCssToXPath engine = new SelectorEngineCssToXPath(
+ replacer);
+
+ protected String css2Xpath(String s) {
+ return engine.css2Xpath(s);
+ }
+
+ protected void generateMethodBody(SourceWriter sw, JMethod method,
+ TreeLogger treeLogger, boolean hasContext)
+ throws UnableToCompleteException {
+
+ String selector = method.getAnnotation(Selector.class).value();
+ sw.println("return "
+ + wrap(method, "SelectorEngine.xpathEvaluate(\"" + css2Xpath(selector)
+ + "\", root)") + ";");
+ }
+
+ protected String getImplSuffix() {
+ return "XPath" + super.getImplSuffix();
+ }
+
+}
diff --git a/gwtquery-core/src/test/java/com/google/gwt/query/client/GQuerySelectorsTest.java b/gwtquery-core/src/test/java/com/google/gwt/query/client/GQuerySelectorsTest.java index 057679e5..7bbc438e 100644 --- a/gwtquery-core/src/test/java/com/google/gwt/query/client/GQuerySelectorsTest.java +++ b/gwtquery-core/src/test/java/com/google/gwt/query/client/GQuerySelectorsTest.java @@ -170,7 +170,7 @@ public class GQuerySelectorsTest extends GWTTestCase { // 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); @@ -325,7 +325,7 @@ public class GQuerySelectorsTest extends GWTTestCase { $(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); diff --git a/gwtquery-core/src/test/java/com/google/gwt/query/client/impl/SelectorEnginesTest.java b/gwtquery-core/src/test/java/com/google/gwt/query/client/impl/SelectorEnginesTest.java new file mode 100644 index 00000000..43d15416 --- /dev/null +++ b/gwtquery-core/src/test/java/com/google/gwt/query/client/impl/SelectorEnginesTest.java @@ -0,0 +1,55 @@ +/* + * 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)")); + + } + + +} diff --git a/gwtquery-core/src/test/java/com/google/gwt/query/rebind/SelectorGeneratorsTest.java b/gwtquery-core/src/test/java/com/google/gwt/query/rebind/SelectorGeneratorsTest.java new file mode 100644 index 00000000..4f605797 --- /dev/null +++ b/gwtquery-core/src/test/java/com/google/gwt/query/rebind/SelectorGeneratorsTest.java @@ -0,0 +1,63 @@ +/* + * 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) + "\"/>"; + } + })); + } +} |