aboutsummaryrefslogtreecommitdiffstats
path: root/gwtquery-core
diff options
context:
space:
mode:
authorManolo Carrasco <manolo@apache.org>2010-06-01 09:09:26 +0000
committerManolo Carrasco <manolo@apache.org>2010-06-01 09:09:26 +0000
commitbb2ec811cb8abdcbbf1a66d5e77fb3d2e8e397ae (patch)
treededd743a402e81f16fee8e73aa297a35697a3853 /gwtquery-core
parentf47c9b3e4353a300fd55769bbf97ccce335dd46c (diff)
downloadgwtquery-bb2ec811cb8abdcbbf1a66d5e77fb3d2e8e397ae.tar.gz
gwtquery-bb2ec811cb8abdcbbf1a66d5e77fb3d2e8e397ae.zip
added a new XPath engine based in Andrea Giammarchi Css2Xpath library
Diffstat (limited to 'gwtquery-core')
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml4
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineCssToXPath.java188
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineSizzle.java5
-rw-r--r--gwtquery-core/src/main/java/com/google/gwt/query/rebind/SelectorGeneratorCssToXPath.java86
-rw-r--r--gwtquery-core/src/test/java/com/google/gwt/query/client/GQuerySelectorsTest.java4
-rw-r--r--gwtquery-core/src/test/java/com/google/gwt/query/client/impl/SelectorEnginesTest.java55
-rw-r--r--gwtquery-core/src/test/java/com/google/gwt/query/rebind/SelectorGeneratorsTest.java63
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) + "\"/>";
+ }
+ }));
+ }
+}