]> source.dussan.org Git - gwtquery.git/commitdiff
added a new XPath engine based in Andrea Giammarchi Css2Xpath library
authorManolo Carrasco <manolo@apache.org>
Tue, 1 Jun 2010 09:09:26 +0000 (09:09 +0000)
committerManolo Carrasco <manolo@apache.org>
Tue, 1 Jun 2010 09:09:26 +0000 (09:09 +0000)
gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml
gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineCssToXPath.java [new file with mode: 0644]
gwtquery-core/src/main/java/com/google/gwt/query/client/impl/SelectorEngineSizzle.java
gwtquery-core/src/main/java/com/google/gwt/query/rebind/SelectorGeneratorCssToXPath.java [new file with mode: 0644]
gwtquery-core/src/test/java/com/google/gwt/query/client/GQuerySelectorsTest.java
gwtquery-core/src/test/java/com/google/gwt/query/client/impl/SelectorEnginesTest.java [new file with mode: 0644]
gwtquery-core/src/test/java/com/google/gwt/query/rebind/SelectorGeneratorsTest.java [new file with mode: 0644]

index bd9a76c3a5c7def9baad6d72eb1dd937649215a4..9d0e83ff6bdf693497867eb55376f64617914e29 100644 (file)
@@ -33,7 +33,7 @@
         </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
@@ -68,7 +68,7 @@
                 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
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 (file)
index 0000000..3e3d482
--- /dev/null
@@ -0,0 +1,188 @@
+/*\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
index 57af82778e9fc6be46a50cdff50d699d1c1a1fe3..389b424a8f822bc96849dafce92003b19531d7a4 100644 (file)
@@ -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 (file)
index 0000000..04582af
--- /dev/null
@@ -0,0 +1,86 @@
+/*\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
index 057679e5faed7ee86484ff1cbd4a64ab595f811d..7bbc438e8002247f0e4a43c407aed2e726342c8d 100644 (file)
@@ -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 (file)
index 0000000..43d1541
--- /dev/null
@@ -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 (file)
index 0000000..4f60579
--- /dev/null
@@ -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) + "\"/>";
+      }
+    }));
+  }
+}