@@ -0,0 +1,28 @@ | |||
Introduction | |||
------------ | |||
GwtQuery is a jQuery-like API written in GWT, which allows GWT to be used in | |||
progressive enhancement scenarios where perhaps GWT widgets are too | |||
heavyweight. | |||
Currently, only a part of the jQuery API is written. Most CSS3 selectors are | |||
supported. People feel free to contribute patches to bring the API more in | |||
line with jQuery. | |||
This code is alpha, so expect it to break, and expect the API to change | |||
in the future. | |||
I would like the thank John Resig for writing jQuery, a kick ass library, | |||
that is a pleasure to use, and I hope to capture that feeling in GWT. Also, | |||
thanks to Robery Nyman for writing the fastest CSS Selector API | |||
implementation (DOMAssistant), which I used as a guide for the GWT | |||
impementation. GwtQuery is in large part, a port of DOMAssistant. | |||
I am releasing this under the MIT License in the spirit for Robert Nyman's | |||
choice, since the performance of this library wouldn't have been possible | |||
without him. | |||
Thanks, | |||
Ray Cromwell <ray@timefire.com> | |||
CTO, TimeFire |
@@ -0,0 +1,95 @@ | |||
<module> | |||
<inherits name='com.google.gwt.user.User'/> | |||
<!--<define-property name="selectorCapability" values="native,xpath,js"/>--> | |||
<!-- enable for native getClassByName shortcut acceleration --> | |||
<define-property name="selectorCapability" | |||
values="native,js"/> | |||
<property-provider name="selectorCapability"> | |||
<![CDATA[ | |||
// useful for benchmarking tests when you want to force non-accelerated queries | |||
//if(window.location.href.indexOf("_selector_force_js") != -1) return "js"; | |||
if(document.querySelectorAll && /native/.test(document.querySelectorAll.toString())) { | |||
return "native"; | |||
} | |||
return "js" | |||
]]> | |||
</property-provider> | |||
<generate-with class="gquery.rebind.SelectorGeneratorJS"> | |||
<when-type-assignable class="gquery.client.Selectors"/> | |||
<any> | |||
<when-property-is name="user.agent" value="gecko"/> | |||
<when-property-is name="user.agent" value="ie6"/> | |||
</any> | |||
</generate-with> | |||
<generate-with class="gquery.rebind.SelectorGeneratorXPath"> | |||
<when-type-assignable class="gquery.client.Selectors"/> | |||
<any> | |||
<when-property-is name="user.agent" value="gecko1_8"/> | |||
<when-property-is name="user.agent" value="opera"/> | |||
<all> | |||
<when-property-is name="selectorCapability" value="js"/> | |||
<when-property-is name="user.agent" value="safari"/> | |||
</all> | |||
</any> | |||
</generate-with> | |||
<generate-with class="gquery.rebind.gebcn.SelectorGeneratorNativeGEBCN"> | |||
<when-type-assignable class="gquery.client.Selectors"/> | |||
<all> | |||
<when-property-is name="selectorCapability" value="native"/> | |||
<when-property-is name="user.agent" value="safari"/> | |||
</all> | |||
</generate-with> | |||
<!--versions which handle native getElementsByClassName --> | |||
<!--<generate-with class="gquery.rebind.gebcn.SelectorGeneratorJSGEBCN">--> | |||
<!--<when-type-assignable class="gquery.client.Selectors"/>--> | |||
<!--<when-property-is name="selectorCapability" value="js_gebcn"/>--> | |||
<!--</generate-with>--> | |||
<!--<generate-with class="gquery.rebind.gebcn.SelectorGeneratorXPathGEBCN">--> | |||
<!--<when-type-assignable class="gquery.client.Selectors"/>--> | |||
<!--<when-property-is name="selectorCapability" value="xpath_gebcn"/>--> | |||
<!--</generate-with>--> | |||
<!--<generate-with class="gquery.rebind.gebcn.SelectorGeneratorNativeGEBCN">--> | |||
<!--<when-type-assignable class="gquery.client.Selectors"/>--> | |||
<!--<when-property-is name="selectorCapability" value="native_gebcn"/>--> | |||
<!--</generate-with>--> | |||
<replace-with class="gquery.client.impl.SelectorEngineJS"> | |||
<when-type-assignable class="gquery.client.impl.SelectorEngineImpl"/> | |||
<when-property-is name="user.agent" value="gecko"/> | |||
</replace-with> | |||
<replace-with class="gquery.client.impl.SelectorEngineJSIE"> | |||
<when-type-assignable class="gquery.client.impl.SelectorEngineImpl"/> | |||
<when-property-is name="user.agent" value="ie6"/> | |||
</replace-with> | |||
<replace-with class="gquery.client.impl.SelectorEngineXPath"> | |||
<when-type-assignable class="gquery.client.impl.SelectorEngineImpl"/> | |||
<any> | |||
<when-property-is name="user.agent" value="gecko1_8"/> | |||
<when-property-is name="user.agent" value="opera"/> | |||
<all> | |||
<when-property-is name="selectorCapability" value="js"/> | |||
<when-property-is name="user.agent" value="safari"/> | |||
</all> | |||
</any> | |||
</replace-with> | |||
<replace-with class="gquery.client.impl.SelectorEngineNative"> | |||
<when-type-assignable class="gquery.client.impl.SelectorEngineImpl"/> | |||
<all> | |||
<when-property-is name="user.agent" value="safari"/> | |||
<when-property-is name="selectorCapability" value="native"/> | |||
</all> | |||
</replace-with> | |||
<!--<set-property name="selectorCapability" value="js"/>--> | |||
</module> |
@@ -0,0 +1,5 @@ | |||
<module> | |||
<inherits name='gquery.GQuery'/> | |||
<entry-point class='gquery.client.GQueryBenchModule'/> | |||
</module> | |||
@@ -0,0 +1,5 @@ | |||
<module> | |||
<inherits name='gquery.GQuery'/> | |||
<entry-point class='gquery.client.GQueryDemoModule'/> | |||
</module> | |||
@@ -0,0 +1,5 @@ | |||
<module> | |||
<inherits name='gquery.GQuery'/> | |||
<entry-point class='gquery.client.GQuerySampleModule'/> | |||
</module> | |||
@@ -0,0 +1,14 @@ | |||
package gquery.client; | |||
import com.google.gwt.dom.client.Node; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.NodeList; | |||
/** | |||
* A compiled selector that can be lazily turned into a GQuery | |||
*/ | |||
public interface DeferredGQuery { | |||
String getSelector(); | |||
GQuery eval(Node ctx); | |||
NodeList<Element> array(Node ctx); | |||
} |
@@ -0,0 +1,83 @@ | |||
package gquery.client; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.NodeList; | |||
import com.google.gwt.user.client.animation.Animation; | |||
public class Effects extends GQuery { | |||
static { | |||
GQuery.registerPlugin(Effects.class, new EffectsPlugin()); | |||
} | |||
public static final Class<Effects> Effects = Effects.class; | |||
public Effects(Element element) { | |||
super(element); | |||
} | |||
public Effects(JSArray elements) { | |||
super(elements); | |||
} | |||
public Effects(NodeList list) { | |||
super(list); | |||
} | |||
public Effects fadeOut() { | |||
Animation a = new Animation() { | |||
public void onCancel() { | |||
} | |||
public void onComplete() { | |||
for (int i = 0; i < elements.getLength(); i++) { | |||
elements.getItem(i).getStyle().setProperty("opacity", "0"); | |||
elements.getItem(i).getStyle().setProperty("display", "none"); | |||
} | |||
} | |||
public void onStart() { | |||
} | |||
public void onUpdate(double progress) { | |||
for (int i = 0; i < elements.getLength(); i++) { | |||
elements.getItem(i).getStyle() | |||
.setProperty("opacity", String.valueOf(1.0 - progress)); | |||
} | |||
} | |||
}; | |||
a.run(1000); | |||
return this; | |||
} | |||
public Effects fadeIn() { | |||
Animation a = new Animation() { | |||
public void onCancel() { | |||
} | |||
public void onComplete() { | |||
} | |||
public void onStart() { | |||
} | |||
public void onUpdate(double progress) { | |||
for (int i = 0; i < elements.getLength(); i++) { | |||
elements.getItem(i).getStyle() | |||
.setProperty("opacity", String.valueOf(progress)); | |||
} | |||
} | |||
}; | |||
a.run(1000); | |||
return this; | |||
} | |||
public static class EffectsPlugin implements Plugin<Effects> { | |||
public Effects init(GQuery gq) { | |||
return new Effects(gq.get()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,22 @@ | |||
package gquery.client; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.user.client.Event; | |||
/** | |||
* Extend this class to implement functions. | |||
*/ | |||
public abstract class Function { | |||
public void f(Element e) { | |||
} | |||
public boolean f(Event e, Object data) { | |||
return f(e); | |||
} | |||
public boolean f(Event e) { | |||
f(e.getCurrentTarget()); | |||
return true; | |||
} | |||
} |
@@ -0,0 +1,629 @@ | |||
package gquery.client; | |||
import com.google.gwt.core.client.GWT; | |||
import com.google.gwt.dom.client.ButtonElement; | |||
import com.google.gwt.dom.client.Document; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.InputElement; | |||
import com.google.gwt.dom.client.Node; | |||
import com.google.gwt.dom.client.NodeList; | |||
import com.google.gwt.dom.client.OptionElement; | |||
import com.google.gwt.dom.client.SelectElement; | |||
import com.google.gwt.dom.client.TextAreaElement; | |||
import com.google.gwt.user.client.DOM; | |||
import com.google.gwt.user.client.Event; | |||
import com.google.gwt.user.client.EventListener; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
/** | |||
* | |||
*/ | |||
public class GQuery { | |||
public static class Offset { | |||
public int top; | |||
public int left; | |||
Offset(int left, int top) { | |||
this.left = left; | |||
this.top = top; | |||
} | |||
} | |||
private static Map<Class<? extends GQuery>, Plugin<? extends GQuery>> plugins; | |||
/** | |||
* This function accepts a string containing a CSS selector which is then used | |||
* to match a set of elements, or it accepts raw HTML creating a GQuery | |||
* element containing those elements. | |||
*/ | |||
public static GQuery $(String selectorOrHtml) { | |||
if (selectorOrHtml.trim().charAt(0) == '<') { | |||
return innerHtml(selectorOrHtml); | |||
} | |||
return $(selectorOrHtml, Document.get()); | |||
} | |||
public static <T extends GQuery> T $(T gq) { | |||
return gq; | |||
} | |||
/** | |||
* This function accepts a string containing a CSS selector which is then used | |||
* to match a set of elements, or it accepts raw HTML creating a GQuery | |||
* element containing those elements. The second parameter is is a class | |||
* reference to a plugin to be used. | |||
*/ | |||
public static <T extends GQuery> T $(String selector, Class<T> plugin) { | |||
try { | |||
if (plugins != null) { | |||
T gquery = (T) plugins.get(plugin).init($(selector, Document.get())); | |||
return gquery; | |||
} | |||
throw new RuntimeException("No plugin for class " + plugin); | |||
} catch (Exception e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
/** | |||
* This function accepts a string containing a CSS selector which is then used | |||
* to match a set of elements, or it accepts raw HTML creating a GQuery | |||
* element containing those elements. The second parameter is the context to | |||
* use for the selector. | |||
*/ | |||
public static GQuery $(String selector, Node context) { | |||
return new GQuery(select(selector, context)); | |||
} | |||
/** | |||
* This function accepts a string containing a CSS selector which is then used | |||
* to match a set of elements, or it accepts raw HTML creating a GQuery | |||
* element containing those elements. The second parameter is the context to | |||
* use for the selector. The third parameter is the class plugin to use. | |||
*/ | |||
public static <T extends GQuery> GQuery $(String selector, Node context, | |||
Class<T> plugin) { | |||
try { | |||
if (plugins != null) { | |||
T gquery = (T) plugins.get(plugin) | |||
.init(new GQuery(select(selector, context))); | |||
return gquery; | |||
} | |||
throw new RuntimeException("No plugin for class " + plugin); | |||
} catch (Exception e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
/** | |||
* Wrap a GQuery around existing Elements. | |||
*/ | |||
public static GQuery $(NodeList<Element> elements) { | |||
return new GQuery(elements); | |||
} | |||
public static GQuery $(Element element) { | |||
JSArray a = JSArray.create(); | |||
a.addNode(element); | |||
return new GQuery(a); | |||
} | |||
/** | |||
* Wrap a JSON object | |||
*/ | |||
public static Properties $$(String properties) { | |||
return Properties.create(properties); | |||
} | |||
public static <T extends Node> T[] asArray(NodeList<T> nl) { | |||
if (GWT.isScript()) { | |||
return reinterpretCast(nl); | |||
} else { | |||
Node[] elts = new Node[nl.getLength()]; | |||
for (int i = 0; i < elts.length; i++) { | |||
elts[i] = nl.getItem(i); | |||
} | |||
return (T[]) elts; | |||
} | |||
} | |||
public static void registerPlugin(Class<? extends GQuery> plugin, | |||
Plugin<? extends GQuery> pluginFactory) { | |||
if (plugins == null) { | |||
plugins = new HashMap(); | |||
} | |||
plugins.put(plugin, pluginFactory); | |||
} | |||
/** | |||
* Copied from UIObject * | |||
*/ | |||
protected static void setStyleName(Element elem, String style, boolean add) { | |||
style = style.trim(); | |||
// Get the current style string. | |||
String oldStyle = elem.getClassName(); | |||
int idx = oldStyle.indexOf(style); | |||
// Calculate matching index. | |||
while (idx != -1) { | |||
if (idx == 0 || oldStyle.charAt(idx - 1) == ' ') { | |||
int last = idx + style.length(); | |||
int lastPos = oldStyle.length(); | |||
if ((last == lastPos) || ((last < lastPos) && (oldStyle.charAt(last) | |||
== ' '))) { | |||
break; | |||
} | |||
} | |||
idx = oldStyle.indexOf(style, idx + 1); | |||
} | |||
if (add) { | |||
// Only add the style if it's not already present. | |||
if (idx == -1) { | |||
if (oldStyle.length() > 0) { | |||
oldStyle += " "; | |||
} | |||
DOM.setElementProperty(elem.<com.google.gwt.user.client.Element>cast(), | |||
"className", oldStyle + style); | |||
} | |||
} else { | |||
// Don't try to remove the style if it's not there. | |||
if (idx != -1) { | |||
// Get the leading and trailing parts, without the removed name. | |||
String begin = oldStyle.substring(0, idx).trim(); | |||
String end = oldStyle.substring(idx + style.length()).trim(); | |||
// Some contortions to make sure we don't leave extra spaces. | |||
String newClassName; | |||
if (begin.length() == 0) { | |||
newClassName = end; | |||
} else if (end.length() == 0) { | |||
newClassName = begin; | |||
} else { | |||
newClassName = begin + " " + end; | |||
} | |||
DOM.setElementProperty(elem.<com.google.gwt.user.client.Element>cast(), | |||
"className", newClassName); | |||
} | |||
} | |||
} | |||
private static boolean hasClass(Element e, String clz) { | |||
return e.getClassName().matches("\\s" + clz + "\\s"); | |||
} | |||
private static GQuery innerHtml(String html) { | |||
Element div = DOM.createDiv(); | |||
div.setInnerHTML(html); | |||
return new GQuery((NodeList<Element>) (NodeList<?>) div.getChildNodes()); | |||
} | |||
private static native <T extends Node> T[] reinterpretCast(NodeList<T> nl) /*-{ | |||
return nl; | |||
}-*/; | |||
private static NodeList select(String selector, Node context) { | |||
return new SelectorEngine().select(selector, context); | |||
} | |||
protected NodeList<Element> elements = null; | |||
public GQuery(NodeList<Element> list) { | |||
elements = list; | |||
} | |||
public GQuery(JSArray elements) { | |||
this.elements = elements; | |||
} | |||
public GQuery(Element element) { | |||
elements = JSArray.create(element); | |||
} | |||
/** | |||
* Adds the specified classes to each matched element. | |||
*/ | |||
public GQuery addClass(String... classes) { | |||
for (Element e : elements()) { | |||
for (String clz : classes) { | |||
setStyleName(e, clz, true); | |||
} | |||
} | |||
return this; | |||
} | |||
/** | |||
* Convert to Plugin interface provided by Class literal. | |||
*/ | |||
public <T extends GQuery> T as(Class<T> plugin) { | |||
if (plugins != null) { | |||
return (T) plugins.get(plugin).init(this); | |||
} | |||
throw new RuntimeException("No plugin registered for class " + plugin); | |||
} | |||
public String attr(String name) { | |||
return elements.getItem(0).getAttribute(name); | |||
} | |||
public GQuery attr(String key, String value) { | |||
for (Element e : elements()) { | |||
e.setAttribute(key, value); | |||
} | |||
return this; | |||
} | |||
public GQuery attr(Properties properties) { | |||
for (Element e : elements()) { | |||
for (String name : properties.keys()) { | |||
e.setAttribute(name, properties.get(name)); | |||
} | |||
} | |||
return this; | |||
} | |||
public GQuery bind(int eventbits, final Object data, final Function f) { | |||
EventListener listener = new EventListener() { | |||
public void onBrowserEvent(Event event) { | |||
if (!f.f(event, data)) { | |||
event.cancelBubble(true); | |||
event.preventDefault(); | |||
} | |||
} | |||
}; | |||
for (Element e : elements()) { | |||
DOM.sinkEvents((com.google.gwt.user.client.Element) e, eventbits); | |||
DOM.setEventListener((com.google.gwt.user.client.Element) e, listener); | |||
} | |||
return this; | |||
} | |||
public GQuery blur(Function f) { | |||
return bind(Event.ONBLUR, null, f); | |||
} | |||
public GQuery change(Function f) { | |||
return bind(Event.ONCHANGE, null, f); | |||
} | |||
public GQuery click(final Function f) { | |||
return bind(Event.ONCLICK, null, f); | |||
} | |||
public String css(String name) { | |||
return elements.getItem(0).getStyle().getProperty(name); | |||
} | |||
public GQuery css(Properties properties) { | |||
for (String property : properties.keys()) { | |||
css(property, properties.get(property)); | |||
} | |||
return this; | |||
} | |||
public GQuery css(String prop, String val) { | |||
for (Element e : elements()) { | |||
e.getStyle().setProperty(prop, val); | |||
} | |||
return this; | |||
} | |||
public GQuery dblclick(Function f) { | |||
return bind(Event.ONDBLCLICK, null, f); | |||
} | |||
/** | |||
* Run one or more Functions over each element of the GQuery. | |||
*/ | |||
public GQuery each(Function... f) { | |||
for (Function f1 : f) { | |||
for (Element e : elements()) { | |||
f1.f(e); | |||
} | |||
} | |||
return this; | |||
} | |||
/** | |||
* Returns the working set of nodes as a Java array. <b>Do NOT</b attempt to | |||
* modify this array, e.g. assign to its elements, or call Arrays.sort() | |||
*/ | |||
public Element[] elements() { | |||
return asArray(elements); | |||
} | |||
/** | |||
* Reduce GQuery to element in the specified position. | |||
*/ | |||
public GQuery eq(int pos) { | |||
return $(elements.getItem(pos)); | |||
} | |||
public GQuery error(Function f) { | |||
return bind(Event.ONERROR, null, f); | |||
} | |||
public GQuery focus(Function f) { | |||
return bind(Event.ONFOCUS, null, f); | |||
} | |||
/** | |||
* Return all elements matched in the GQuery as a NodeList. @see #elements() | |||
* for a method which returns them as an immutable Java array. | |||
*/ | |||
public NodeList<Element> get() { | |||
return elements; | |||
} | |||
/** | |||
* Return the ith element matched. | |||
*/ | |||
public Element get(int i) { | |||
return elements.getItem(i); | |||
} | |||
/** | |||
* Returns true any of the specified classes are present on any of the matched | |||
* elements. | |||
*/ | |||
public boolean hasClass(String... classes) { | |||
for (Element e : elements()) { | |||
for (String clz : classes) { | |||
if (hasClass(e, clz)) { | |||
return true; | |||
} | |||
} | |||
} | |||
return false; | |||
} | |||
/** | |||
* Set the height of every element in the matched set. | |||
*/ | |||
public GQuery height(int height) { | |||
for (Element e : elements()) { | |||
e.getStyle().setPropertyPx("height", height); | |||
} | |||
return this; | |||
} | |||
/** | |||
* Get the innerHTML of the first matched element. | |||
*/ | |||
public String html() { | |||
return get(0).getInnerHTML(); | |||
} | |||
/** | |||
* Set the innerHTML of every matched element. | |||
*/ | |||
public GQuery html(String html) { | |||
for (Element e : elements()) { | |||
e.setInnerHTML(html); | |||
} | |||
return this; | |||
} | |||
/** | |||
* Find the index of the specified Element | |||
*/ | |||
public int index(Element element) { | |||
for (int i = 0; i < elements.getLength(); i++) { | |||
if (elements.getItem(i) == element) { | |||
return i; | |||
} | |||
} | |||
return -1; | |||
} | |||
public GQuery keydown(Function f) { | |||
return bind(Event.ONKEYDOWN, null, f); | |||
} | |||
public GQuery keypressed(Function f) { | |||
return bind(Event.ONKEYPRESS, null, f); | |||
} | |||
public GQuery keyup(Function f) { | |||
return bind(Event.ONKEYUP, null, f); | |||
} | |||
public GQuery load(Function f) { | |||
return bind(Event.ONLOAD, null, f); | |||
} | |||
public GQuery mousedown(Function f) { | |||
return bind(Event.ONMOUSEDOWN, null, f); | |||
} | |||
public GQuery mousemove(Function f) { | |||
return bind(Event.ONMOUSEMOVE, null, f); | |||
} | |||
public GQuery mouseout(Function f) { | |||
return bind(Event.ONMOUSEOUT, null, f); | |||
} | |||
public GQuery mouseover(Function f) { | |||
return bind(Event.ONMOUSEOVER, null, f); | |||
} | |||
public GQuery mouseup(Function f) { | |||
return bind(Event.ONMOUSEUP, null, f); | |||
} | |||
public Offset offset() { | |||
return new Offset(get(0).getOffsetLeft(), get(0).getOffsetTop()); | |||
} | |||
/** | |||
* Remove the named attribute from every element in the matched set. | |||
*/ | |||
public GQuery removeAttr(String key) { | |||
for (Element e : elements()) { | |||
e.removeAttribute(key); | |||
} | |||
return this; | |||
} | |||
/** | |||
* Removes the specified classes to each matched element. | |||
*/ | |||
public GQuery removeClass(String... classes) { | |||
for (Element e : elements()) { | |||
for (String clz : classes) { | |||
setStyleName(e, clz, false); | |||
} | |||
} | |||
return this; | |||
} | |||
public GQuery scroll(Function f) { | |||
return bind(Event.ONSCROLL, null, f); | |||
} | |||
/** | |||
* Return the number of elements in the matched set. | |||
*/ | |||
public int size() { | |||
return elements.getLength(); | |||
} | |||
public GQuery slice(int start, int end) { | |||
JSArray slice = JSArray.create(); | |||
if (end == -1 || end > elements.getLength()) { | |||
end = elements.getLength(); | |||
} | |||
for (int i = start; i < elements.getLength(); i++) { | |||
slice.addNode(elements.getItem(i)); | |||
} | |||
return new GQuery(slice); | |||
} | |||
/** | |||
* Return the text contained in the first matched element. | |||
*/ | |||
public String text() { | |||
return elements.getItem(0).getInnerText(); | |||
} | |||
/** | |||
* Set the innerText of every matched element. | |||
*/ | |||
public GQuery text(String txt) { | |||
for (Element e : asArray(elements)) { | |||
e.setInnerText(txt); | |||
} | |||
return this; | |||
} | |||
/** | |||
* Adds or removes the specified classes to each matched element. | |||
*/ | |||
public GQuery toggleClass(String... classes) { | |||
for (Element e : elements()) { | |||
for (String clz : classes) { | |||
if (hasClass(e, clz)) { | |||
setStyleName(e, clz, false); | |||
} else { | |||
setStyleName(e, clz, true); | |||
} | |||
} | |||
} | |||
return this; | |||
} | |||
/** | |||
* Get the content of the value attribute of the first matched element, | |||
* returns more than one value if it is a multiple select. | |||
*/ | |||
public String[] val() { | |||
if (size() > 0) { | |||
Element e = get(0); | |||
if (e.getNodeName().equals("select")) { | |||
SelectElement se = SelectElement.as(e); | |||
if (se.getMultiple() != null) { | |||
NodeList<OptionElement> oel = se.getOptions(); | |||
int count = 0; | |||
for (OptionElement oe : asArray(oel)) { | |||
if (oe.isSelected()) { | |||
count++; | |||
} | |||
} | |||
String result[] = new String[count]; | |||
count = 0; | |||
for (OptionElement oe : asArray(oel)) { | |||
if (oe.isSelected()) { | |||
result[count++] = oe.getValue(); | |||
} | |||
} | |||
return result; | |||
} else { | |||
int index = se.getSelectedIndex(); | |||
if (index != -1) { | |||
return new String[]{se.getOptions().getItem(index).getValue()}; | |||
} | |||
} | |||
} else if (e.getNodeName().equals("input")) { | |||
InputElement ie = InputElement.as(e); | |||
return new String[]{ie.getValue()}; | |||
} | |||
} | |||
return new String[0]; | |||
} | |||
public GQuery val(String... values) { | |||
for (Element e : elements()) { | |||
String name = e.getNodeName(); | |||
if ("select".equals(name)) { | |||
} else if ("input".equals(name)) { | |||
InputElement ie = InputElement.as(e); | |||
String type = ie.getType(); | |||
if ("radio".equals((type)) || "checkbox".equals(type)) { | |||
if ("checkbox".equals(type)) { | |||
for (String val : values) { | |||
if (ie.getValue().equals(val)) { | |||
ie.setChecked(true); | |||
} else if (ie.getValue().equals(val)) { | |||
ie.setChecked(true); | |||
} | |||
} | |||
} | |||
} else { | |||
ie.setValue(values[0]); | |||
} | |||
} else if ("textarea".equals(name)) { | |||
TextAreaElement.as(e).setValue(values[0]); | |||
} else if ("button".equals(name)) { | |||
ButtonElement.as(e).setValue(values[0]); | |||
} | |||
} | |||
return this; | |||
} | |||
/** | |||
* Set the width of every matched element. | |||
*/ | |||
public GQuery width(int width) { | |||
for (Element e : elements()) { | |||
e.getStyle().setPropertyPx("width", width); | |||
} | |||
return this; | |||
} | |||
private void init(GQuery gQuery) { | |||
this.elements = gQuery.elements; | |||
} | |||
} |
@@ -0,0 +1,210 @@ | |||
package gquery.client; | |||
import com.google.gwt.core.client.EntryPoint; | |||
import com.google.gwt.core.client.GWT; | |||
import com.google.gwt.dom.client.Document; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.user.client.DeferredCommand; | |||
import com.google.gwt.user.client.IncrementalCommand; | |||
import static gquery.client.GQuery.$; | |||
public class GQueryBenchModule implements EntryPoint { | |||
private StringBuffer log = new StringBuffer(); | |||
private static final int MIN_TIME = 200; | |||
private static final String GCOMPILED = "gcompiled"; | |||
private static final String DOMASSISTANT = "dresult"; | |||
private static final String GDYNAMIC = "gresult"; | |||
public void onModuleLoad() { | |||
final MySelectors m = GWT.create(MySelectors.class); | |||
final DeferredGQuery dg[] = m.getAllSelectors(); | |||
initResultsTable(dg, "Compiled GQuery", GCOMPILED, "DOMAssistant-2.7" /*"DOMAssistant 2.7" */, | |||
DOMASSISTANT, "Dynamic GQuery", GDYNAMIC); | |||
runBenchmarks(dg, new GQueryCompiledBenchmark(), | |||
new DomAssistantBenchmark(), new GQueryDynamicBenchmark()); | |||
} | |||
public interface Benchmark { | |||
public int runSelector(DeferredGQuery dq, String selector); | |||
String getId(); | |||
} | |||
public void runBenchmarks(final DeferredGQuery[] dg, | |||
final Benchmark... benchmark) { | |||
DeferredCommand.addCommand(new IncrementalCommand() { | |||
int selectorNumber = 0; | |||
int numCalls = 0; | |||
int benchMarkNumber = 0; | |||
double totalTimes[] = new double[benchmark.length]; | |||
long cumTime = 0; | |||
int numRuns = 0; | |||
int winner = -1; | |||
double winTime = Double.MAX_VALUE; | |||
public boolean execute() { | |||
if (benchMarkNumber >= benchmark.length) { | |||
benchMarkNumber = 0; | |||
numCalls = 0; | |||
cumTime = 0; | |||
numRuns = 0; | |||
setResultClass(benchmark[winner].getId(), selectorNumber, "win"); | |||
for (int i = 0; i < benchmark.length; i++) { | |||
if (i != winner) | |||
setResultClass(benchmark[i].getId(), selectorNumber, "lose"); | |||
} | |||
selectorNumber++; | |||
winner = -1; | |||
winTime = Double.MAX_VALUE; | |||
if (selectorNumber >= dg.length) { | |||
double min = Double.MAX_VALUE; | |||
for (int i = 0; i < totalTimes.length; i++) { | |||
if (totalTimes[i] < min) min = totalTimes[i]; | |||
} | |||
for (int i = 0; i < totalTimes.length; i++) { | |||
d(benchmark[i].getId(), dg.length, (((int) (totalTimes[i] * 100)) / 100.0) + " ms"); | |||
setResultClass(benchmark[i].getId(), | |||
dg.length, totalTimes[i] <= min ? "win" : "lose"); | |||
} | |||
return false; | |||
} | |||
} | |||
DeferredGQuery d = dg[selectorNumber]; | |||
long start = System.currentTimeMillis(); | |||
int num = 0; | |||
long end = start; | |||
Benchmark m = benchmark[benchMarkNumber]; | |||
String selector = d.getSelector(); | |||
do { | |||
num += m.runSelector(d, selector); | |||
end = System.currentTimeMillis(); | |||
numCalls++; | |||
} while (end - start < MIN_TIME); | |||
double runtime = (double) (end - start) / numCalls; | |||
if (runtime < winTime) { | |||
winTime = runtime; | |||
winner = benchMarkNumber; | |||
} | |||
d(m.getId(), selectorNumber, runtime, (num / numCalls)); | |||
totalTimes[benchMarkNumber] += runtime; | |||
numCalls = 0; | |||
benchMarkNumber++; | |||
return true; | |||
} | |||
}); | |||
} | |||
private void setResultClass(String id, int i, String clz) { | |||
Element td = Document.get().getElementById(id + i); | |||
td.setClassName(clz); | |||
} | |||
private void d(String type, int i, String text) { | |||
Element td = Document.get().getElementById(type + i); | |||
td.setInnerHTML(text); | |||
} | |||
private void d(String type, int i, double v, int i1) { | |||
Element td = Document.get().getElementById(type + i); | |||
td.setInnerHTML( | |||
"" + (((int) (v * 100)) / 100.0) + " ms, found " + i1 + " nodes"); | |||
} | |||
private void initResultsTable(DeferredGQuery[] dg, String... options) { | |||
int numRows = dg.length; | |||
Document doc = Document.get(); | |||
Element table = doc.getElementById("resultstable"); | |||
Element thead = doc.createTHeadElement(); | |||
table.appendChild(thead); | |||
Element selectorHeader = doc.createTHElement(); | |||
Element theadtr = doc.createTRElement(); | |||
selectorHeader.setInnerHTML("Selector"); | |||
theadtr.appendChild(selectorHeader); | |||
thead.appendChild(theadtr); | |||
Element tbody = doc.createTBodyElement(); | |||
table.appendChild(tbody); | |||
for (int i = 0; i < options.length; i += 2) { | |||
Element th = doc.createTHElement(); | |||
th.setInnerHTML(options[i]); | |||
theadtr.appendChild(th); | |||
} | |||
for (int i = 0; i < numRows; i++) { | |||
Element tr = doc.createTRElement(); | |||
Element lab = doc.createTHElement(); | |||
lab.setInnerHTML(dg[i].getSelector()); | |||
tr.appendChild(lab); | |||
for (int j = 0; j < options.length; j += 2) { | |||
Element placeholder = doc.createTDElement(); | |||
placeholder.setInnerHTML("Not Tested"); | |||
placeholder.setId(options[j + 1] + i); | |||
tr.appendChild(placeholder); | |||
} | |||
tbody.appendChild(tr); | |||
} | |||
Element totalRow = doc.createTRElement(); | |||
Element totalLab = doc.createTHElement(); | |||
totalLab.setInnerHTML("Total"); | |||
totalRow.appendChild(totalLab); | |||
for (int j = 0; j < options.length; j += 2) { | |||
Element placeholder = doc.createTDElement(); | |||
placeholder.setInnerHTML("0"); | |||
placeholder.setId(options[j + 1] + numRows); | |||
totalRow.appendChild(placeholder); | |||
} | |||
tbody.appendChild(totalRow); | |||
} | |||
private void d(String s) { | |||
log.append(s + "<br>"); | |||
} | |||
private static class GQueryCompiledBenchmark implements Benchmark { | |||
public int runSelector(DeferredGQuery dq, String selector) { | |||
return dq.array(null).getLength(); | |||
} | |||
public String getId() { | |||
return GCOMPILED; | |||
} | |||
} | |||
private static class DomAssistantBenchmark implements Benchmark { | |||
public native int runSelector(DeferredGQuery dq, String selector) /*-{ | |||
return $wnd.$(selector).length; | |||
}-*/; | |||
public String getId() { | |||
return DOMASSISTANT; | |||
} | |||
} | |||
private static class GQueryDynamicBenchmark implements Benchmark { | |||
private SelectorEngine engine; | |||
private GQueryDynamicBenchmark() { | |||
engine = new SelectorEngine(); | |||
} | |||
public int runSelector(DeferredGQuery dq, String selector) { | |||
return engine.select(selector, Document.get()).getLength(); | |||
} | |||
public String getId() { | |||
return GDYNAMIC; | |||
} | |||
} | |||
} |
@@ -0,0 +1,82 @@ | |||
package gquery.client; | |||
import com.google.gwt.core.client.EntryPoint; | |||
import com.google.gwt.core.client.GWT; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.NodeList; | |||
import com.google.gwt.dom.client.Document; | |||
import com.google.gwt.dom.client.Node; | |||
import com.google.gwt.user.client.Event; | |||
import static gquery.client.GQuery.$; | |||
import static gquery.client.Effects.Effects; | |||
/** | |||
* | |||
*/ | |||
public class GQueryDemoModule implements EntryPoint { | |||
// Compile-time Selectors! | |||
interface Slide extends Selectors { | |||
// find all LI elements in DIV.slide elements | |||
@Selector("div.slide li") | |||
NodeList<Element> allSlideBullets(); | |||
// find all LI elements rooted at ctx | |||
@Selector("li") | |||
NodeList<Element> slideBulletsCtx(Node ctx); | |||
// Find all DIV elements with class 'slide' | |||
@Selector("div.slide") | |||
NodeList<Element> allSlides(); | |||
} | |||
public void onModuleLoad() { | |||
// Ask GWT compiler to generate our interface | |||
final Slide s = GWT.create(Slide.class); | |||
// Find all slides, set css to display: none | |||
// change first slide to display: block | |||
$(s.allSlides()).css("display", "none") | |||
.eq(0).css("display", "block"); | |||
// we initially hide all bullets by setting opacity to 0 | |||
$(s.allSlideBullets()).css("opacity", "0"); | |||
// add onclick handler to body element | |||
$(Document.get().getBody()).click(new Function() { | |||
// two state variables to note current slide being shown | |||
// and current bullet | |||
int curSlide = 0; | |||
int curBullets = 0; | |||
// query and store all slides, and bullets of current slide | |||
GQuery slides = $(s.allSlides()); | |||
GQuery bullets = $(s.slideBulletsCtx(slides.get(curSlide))); | |||
public boolean f(Event e) { | |||
// onclick, if not all bullets shown, show a bullet and increment | |||
if (curBullets < bullets.size()) { | |||
bullets.eq(curBullets++).as(Effects).fadeIn(); | |||
} else { | |||
// all bullets shown, hide them and current slide | |||
bullets.css("opacity","0"); | |||
slides.eq(curSlide).css("display", "none"); | |||
// move to next slide, checking for wrap around | |||
curSlide++; | |||
if(curSlide == slides.size()) { | |||
curSlide = 0; | |||
} | |||
curBullets = 0; | |||
// query for new set of bullets, and show next slide | |||
// by changing opacity to 1 and display to block | |||
bullets = $(s.slideBulletsCtx(slides.get(curSlide))); | |||
slides.eq(curSlide).css("display", "block").as(Effects).fadeIn(); | |||
} | |||
return true; | |||
} | |||
}); | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
package gquery.client; | |||
import com.google.gwt.core.client.EntryPoint; | |||
import com.google.gwt.core.client.GWT; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.user.client.Window; | |||
/** | |||
* Copyright 2007 Timepedia.org | |||
* 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. | |||
* | |||
* @author Ray Cromwell <ray@timepedia.log> | |||
*/ | |||
public class GQuerySampleModule implements EntryPoint { | |||
interface Sample extends Selectors { | |||
@Selector(".note") | |||
GQuery allNotes(); | |||
} | |||
public void onModuleLoad() { | |||
Sample s = GWT.create(Sample.class); | |||
s.allNotes().html("This was a note"); | |||
} | |||
} |
@@ -0,0 +1,72 @@ | |||
package gquery.client; | |||
import com.google.gwt.core.client.JavaScriptObject; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.Node; | |||
import com.google.gwt.dom.client.NodeList; | |||
/** | |||
*/ | |||
public class JSArray extends NodeList<Element> { | |||
public static JSArray create() { | |||
return (JSArray) JavaScriptObject.createArray(); | |||
} | |||
public static native JSArray create(Node node) /*-{ | |||
return [node]; | |||
}-*/; | |||
public static native JSArray create(NodeList nl) /*-{ | |||
var r = [], len=nl.length; | |||
for(var i=0; i<len; i++) { | |||
r.push(nl[i]); | |||
} | |||
return r; | |||
}-*/; | |||
protected JSArray() { | |||
} | |||
public final native void addInt(int i) /*-{ | |||
this[this.length]=i; | |||
}-*/; | |||
public final native void addNode(Node n) /*-{ | |||
this[this.length]=n; | |||
}-*/; | |||
public final native void addNode(Node ci, int i) /*-{ | |||
this[i]=ci; | |||
}-*/; | |||
public final native void concat(JSArray ary) /*-{ | |||
this.concat(ary); | |||
}-*/; | |||
public final native Element getElement(int i) /*-{ | |||
return this[i]; | |||
}-*/; | |||
public final native int getInt(int i) /*-{ | |||
return this[i] || 0; | |||
}-*/; | |||
public final native Node getNode(int i) /*-{ | |||
return this[i]; | |||
}-*/; | |||
public final native String getStr(int i) /*-{ | |||
return this[i] || null; | |||
}-*/; | |||
public final void pushAll(JSArray prevElem) { | |||
for (int i = 0, ilen = prevElem.size(); i < ilen; i++) { | |||
addNode(prevElem.getNode(i)); | |||
} | |||
} | |||
public final native int size() /*-{ | |||
return this.length; | |||
}-*/; | |||
} |
@@ -0,0 +1,130 @@ | |||
package gquery.client; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.NodeList; | |||
/** | |||
*/ | |||
public interface MySelectors extends Selectors { | |||
@Selector("#title,h1#title") | |||
NodeList<Element> titleAndh1Title(); | |||
@Selector("body") | |||
NodeList<Element> body(); | |||
@Selector("body div") | |||
NodeList<Element> bodyDiv(); | |||
@Selector("div p") | |||
NodeList<Element> divP(); | |||
@Selector("div > div") | |||
NodeList<Element> divGtP(); | |||
@Selector("div + p") | |||
NodeList<Element> divPlusP(); | |||
@Selector("div ~ p") | |||
NodeList<Element> divTildeP(); | |||
@Selector("div[class^=exa][class$=mple]") | |||
NodeList<Element> divPrefixExaSuffixMple(); | |||
@Selector("div p a") | |||
NodeList<Element> divPA(); | |||
// @Selector("div, p a") | |||
// NodeList<Element> divCommaPA(); | |||
@Selector(".note") | |||
NodeList<Element> note(); | |||
@Selector("div .example") | |||
NodeList<Element> divExample(); | |||
@Selector("ul .tocline2") | |||
NodeList<Element> ulTocline2(); | |||
@Selector("#title") | |||
NodeList<Element> title(); | |||
@Selector("h1#title") | |||
NodeList<Element> h1Title(); | |||
@Selector("div #title") | |||
NodeList<Element> divSpaceTitle(); | |||
@Selector("ui.toc li.tocline2") | |||
NodeList<Element> ulTocLiTocLine2(); | |||
@Selector("h1#title + div > p") | |||
NodeList<Element> h1TitlePlusDivGtP(); | |||
// @Selector("h1[id]:contains(Selectors)") | |||
// NodeList<Element> h1IdContainsSelectors(); | |||
// | |||
@Selector("a[href][lang][class]") | |||
NodeList<Element> aHrefLangClass(); | |||
@Selector("div[class]") | |||
NodeList<Element> divWithClass(); | |||
@Selector("div[class=example]") | |||
NodeList<Element> divWithClassExample(); | |||
@Selector("div[class^=exa]") | |||
NodeList<Element> divWithClassPrefixExa(); | |||
@Selector("div[class$=mple]") | |||
NodeList<Element> divWithClassSuffixMple(); | |||
@Selector("div[class~=dialog]") | |||
NodeList<Element> divWithClassContainsDialog(); | |||
@Selector("div[class*=e]") | |||
NodeList<Element> divWithClassContainsE(); | |||
// @Selector("div[class!=madeup]") | |||
// NodeList<Element> divWithClassNotContainsMadeup(); | |||
// | |||
@Selector("div[class~=dialog]") | |||
NodeList<Element> divWithClassListContainsDialog(); | |||
@Selector("*:checked") | |||
NodeList<Element> allChecked(); | |||
// @Selector("*:first") | |||
// NodeList<Element> allFirst(); | |||
// | |||
@Selector("div:not(.example)") | |||
NodeList<Element> divNotExample(); | |||
// @Selector("p:contains(selectors)") | |||
// NodeList<Element> pContainsSelectors(); | |||
@Selector("p:nth-child(even)") | |||
NodeList<Element> nThChildEven(); | |||
@Selector("p:nth-child(2n)") | |||
NodeList<Element> nThChild2n(); | |||
@Selector("p:nth-child(odd)") | |||
NodeList<Element> nThChildOdd(); | |||
@Selector("p:nth-child(2n+1)") | |||
NodeList<Element> nThChild2nPlus1(); | |||
@Selector("p:nth-child(n)") | |||
NodeList<Element> nthChild(); | |||
@Selector("p:only-child") | |||
NodeList<Element> onlyChild(); | |||
@Selector("p:last-child") | |||
NodeList<Element> lastChild(); | |||
@Selector("p:first-child") | |||
NodeList<Element> firstChild(); | |||
} |
@@ -0,0 +1,8 @@ | |||
package gquery.client; | |||
/** | |||
* A GQuery plugin | |||
*/ | |||
public interface Plugin<T extends GQuery> { | |||
T init(GQuery gq); | |||
} |
@@ -0,0 +1,35 @@ | |||
package gquery.client; | |||
import com.google.gwt.core.client.JavaScriptObject; | |||
/** | |||
* | |||
*/ | |||
public class Properties extends JavaScriptObject { | |||
protected Properties() { } | |||
public native static Properties create(String properties) /*-{ | |||
return eval(properties); | |||
}-*/; | |||
public final native String get(String name) /*-{ | |||
return this[name]; | |||
}-*/; | |||
public final native int getInt(String name) /*-{ | |||
return this[name]; | |||
}-*/; | |||
public final native float getFloat(String name) /*-{ | |||
return this[name]; | |||
}-*/; | |||
public final native String[] keys() /*-{ | |||
var key, keys=[]; | |||
for(key in this) { | |||
keys.push(key); | |||
} | |||
return keys; | |||
}-*/; | |||
} |
@@ -0,0 +1,55 @@ | |||
package gquery.client; | |||
import com.google.gwt.core.client.JavaScriptObject; | |||
/** | |||
*/ | |||
public class Regexp { | |||
private final JavaScriptObject regexp; | |||
public Regexp(String pattern) { | |||
this.regexp = compile(pattern); | |||
} | |||
public Regexp(String pat, String flags) { | |||
this.regexp = compileFlags(pat, flags); | |||
} | |||
public static native JavaScriptObject compile(String pat) /*-{ | |||
return new RegExp(pat); | |||
}-*/; | |||
public static native JavaScriptObject compileFlags(String pat, String flags) /*-{ | |||
return new RegExp(pat, flags); | |||
}-*/; | |||
public JSArray exec(String str) { | |||
return exec0(regexp, str); | |||
} | |||
private static native JSArray exec0(JavaScriptObject regexp, String str) /*-{ | |||
return regexp.exec(str); | |||
}-*/; | |||
public JSArray match(String currentRule) { | |||
return match0(regexp, currentRule); | |||
} | |||
private native JSArray match0(JavaScriptObject regexp, String currentRule)/*-{ | |||
return currentRule.match(regexp); | |||
}-*/; | |||
public boolean test(String rule) { | |||
return test0(regexp, rule); | |||
} | |||
private native boolean test0(JavaScriptObject regexp, String rule) /*-{ | |||
return regexp.test(rule); | |||
}-*/; | |||
public static JSArray match(String regexp, String flags, String string) { | |||
return new Regexp(regexp, flags).match(string); | |||
} | |||
} |
@@ -0,0 +1,20 @@ | |||
package gquery.client; | |||
import java.lang.annotation.Target; | |||
import java.lang.annotation.Retention; | |||
import java.lang.annotation.RetentionPolicy; | |||
import static java.lang.annotation.ElementType.TYPE; | |||
import static java.lang.annotation.ElementType.FIELD; | |||
import static java.lang.annotation.ElementType.METHOD; | |||
import static java.lang.annotation.ElementType.PARAMETER; | |||
import static java.lang.annotation.ElementType.CONSTRUCTOR; | |||
import static java.lang.annotation.ElementType.LOCAL_VARIABLE; | |||
/** | |||
* Used to pass a CSS Selector to a generator at compile time | |||
*/ | |||
@Target({METHOD}) | |||
@Retention(RetentionPolicy.SOURCE) | |||
public @interface Selector { | |||
String value(); | |||
} |
@@ -0,0 +1,97 @@ | |||
package gquery.client; | |||
import com.google.gwt.core.client.GWT; | |||
import com.google.gwt.core.client.JavaScriptObject; | |||
import com.google.gwt.dom.client.Document; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.Node; | |||
import com.google.gwt.dom.client.NodeList; | |||
import gquery.client.impl.SelectorEngineImpl; | |||
/** | |||
* | |||
*/ | |||
public class SelectorEngine { | |||
private SelectorEngineImpl impl; | |||
public SelectorEngine() { | |||
impl = (SelectorEngineImpl) GWT | |||
.create(SelectorEngineImpl.class); | |||
} | |||
public static native boolean eq(String s1, String s2) /*-{ | |||
return s1 == s2; | |||
}-*/; | |||
public static native NodeList<Element> getElementsByClassName(String clazz, | |||
Node ctx) /*-{ | |||
return ctx.getElementsByClassName(clazz); | |||
}-*/; | |||
public static native String or(String s1, String s2) /*-{ | |||
return s1 || s2; | |||
}-*/; | |||
public static native NodeList<Element> querySelectorAll(String selector) /*-{ | |||
return $doc.querySelectorAll(selector); | |||
}-*/; | |||
public static native NodeList<Element> querySelectorAll(String selector, | |||
Node ctx) /*-{ | |||
return ctx.querySelectorAll(selector); | |||
}-*/; | |||
public NodeList<Element> select(String selector, Node ctx) { | |||
return impl.select(selector, ctx); | |||
} | |||
public static boolean truth(String a) { | |||
return GWT.isScript() ? truth0(a) : a != null && !"".equals(a); | |||
} | |||
public static boolean truth(JavaScriptObject a) { | |||
return GWT.isScript() ? truth0(a) : a != null; | |||
} | |||
public static NodeList<Element> xpathEvaluate(String selector, Node ctx) { | |||
return xpathEvaluate(selector, ctx, JSArray.create()); | |||
} | |||
public static native NodeList<Element> xpathEvaluate(String selector, | |||
Node ctx, JSArray r) /*-{ | |||
var node; | |||
var result = $doc.evaluate(selector, ctx, null, 0, null); | |||
while ((node = result.iterateNext())) { | |||
r.push(node); | |||
} | |||
return r; | |||
}-*/; | |||
private static native boolean truth0(String a) /*-{ | |||
return a; | |||
}-*/; | |||
private static native boolean truth0(JavaScriptObject a) /*-{ | |||
return a; | |||
}-*/; | |||
protected JSArray veryQuickId(Node context, String id) { | |||
JSArray r = JSArray.create(); | |||
if (context.getNodeType() == Node.DOCUMENT_NODE) { | |||
r.addNode(((Document) context).getElementById(id)); | |||
return r; | |||
} else { | |||
r.addNode(context.getOwnerDocument().getElementById(id)); | |||
return r; | |||
} | |||
} | |||
public static native Node getNextSibling(Node n) /*-{ | |||
return n.nextSibling || null; | |||
}-*/; | |||
public static native Node getPreviousSibling(Node n) /*-{ | |||
return n.previousSibling || null; | |||
}-*/; | |||
} |
@@ -0,0 +1,7 @@ | |||
package gquery.client; | |||
/** | |||
*/ | |||
public interface Selectors { | |||
DeferredGQuery[] getAllSelectors(); | |||
} |
@@ -0,0 +1,104 @@ | |||
package gquery.client.impl; | |||
import gquery.client.Regexp; | |||
import gquery.client.JSArray; | |||
import gquery.client.SelectorEngine; | |||
import com.google.gwt.dom.client.*; | |||
/** | |||
* Copyright 2007 Timepedia.org | |||
* 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. | |||
* | |||
* @author Ray Cromwell <ray@timepedia.log> | |||
*/ | |||
public abstract class SelectorEngineImpl { | |||
public abstract NodeList<Element> select(String selector, Node ctx); | |||
protected static Sequence getSequence(String expression) { | |||
int start = 0, add = 2, max = -1, modVal = -1; | |||
Regexp expressionRegExp = new Regexp( | |||
"^((odd|even)|([1-9]\\d*)|((([1-9]\\d*)?)n((\\+|\\-)(\\d+))?)|(\\-(([1-9]\\d*)?)n\\+(\\d+)))$"); | |||
JSArray pseudoValue = expressionRegExp.exec(expression); | |||
if (!SelectorEngine.truth(pseudoValue)) { | |||
return null; | |||
} else { | |||
if (SelectorEngine.truth(pseudoValue.getStr(2))) { // odd or even | |||
start = (SelectorEngine.eq(pseudoValue.getStr(2), "odd")) ? 1 : 2; | |||
modVal = (start == 1) ? 1 : 0; | |||
} else if (SelectorEngine.truth(pseudoValue.getStr(3))) { // single digit | |||
start = Integer.parseInt(pseudoValue.getStr(3), 10); | |||
add = 0; | |||
max = start; | |||
} else if (SelectorEngine.truth(pseudoValue.getStr(4))) { // an+b | |||
add = SelectorEngine.truth(pseudoValue.getStr(6)) ? Integer | |||
.parseInt(pseudoValue.getStr(6), 10) : 1; | |||
start = SelectorEngine.truth(pseudoValue.getStr(7)) ? Integer | |||
.parseInt((pseudoValue.getStr(8).charAt(0) == '+' ? "" : pseudoValue.getStr(8)) + pseudoValue.getStr(9), 10) : 0; | |||
while (start < 1) { | |||
start += add; | |||
} | |||
modVal = (start > add) ? (start - add) % add | |||
: ((start == add) ? 0 : start); | |||
} else if (SelectorEngine.truth(pseudoValue.getStr(10))) { // -an+b | |||
add = SelectorEngine.truth(pseudoValue.getStr(12)) ? Integer | |||
.parseInt(pseudoValue.getStr(12), 10) : 1; | |||
start = max = Integer.parseInt(pseudoValue.getStr(13), 10); | |||
while (start > add) { | |||
start -= add; | |||
} | |||
modVal = (max > add) ? (max - add) % add : ((max == add) ? 0 : max); | |||
} | |||
} | |||
Sequence s = new Sequence(); | |||
s.start = start; | |||
s.add = add; | |||
s.max = max; | |||
s.modVal = modVal; | |||
return s; | |||
} | |||
public static class Sequence { | |||
public int start; | |||
public int max; | |||
public int add; | |||
public int modVal; | |||
} | |||
public static class SplitRule { | |||
public String tag; | |||
public String id; | |||
public String allClasses; | |||
public String allAttr; | |||
public String allPseudos; | |||
public String tagRelation; | |||
public SplitRule(String tag, String id, String allClasses, String allAttr, | |||
String allPseudos) { | |||
this.tag = tag; | |||
this.id = id; | |||
this.allClasses = allClasses; | |||
this.allAttr = allAttr; | |||
this.allPseudos = allPseudos; | |||
} | |||
public SplitRule(String tag, String id, String allClasses, String allAttr, | |||
String allPseudos, String tagRelation) { | |||
this.tag = tag; | |||
this.id = id; | |||
this.allClasses = allClasses; | |||
this.allAttr = allAttr; | |||
this.allPseudos = allPseudos; | |||
this.tagRelation = tagRelation; | |||
} | |||
} | |||
} |
@@ -0,0 +1,698 @@ | |||
package gquery.client.impl; | |||
import com.google.gwt.dom.client.Document; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.Node; | |||
import com.google.gwt.dom.client.NodeList; | |||
import gquery.client.JSArray; | |||
import gquery.client.Regexp; | |||
import gquery.client.SelectorEngine; | |||
/** | |||
* Copyright 2007 Timepedia.org 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. | |||
* | |||
* @author Ray Cromwell <ray@timepedia.log> | |||
*/ | |||
public class SelectorEngineJS extends SelectorEngineImpl { | |||
private Regexp cssSelectorRegExp; | |||
private Regexp selectorSplitRegExp; | |||
private Regexp childOrSiblingRefRegExp; | |||
public SelectorEngineJS() { | |||
selectorSplitRegExp = new Regexp("[^\\s]+", "g"); | |||
childOrSiblingRefRegExp = new Regexp("^(>|\\+|~)$"); | |||
cssSelectorRegExp = new Regexp( | |||
"^(\\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\\-]*))\\))?)*)?"); | |||
} | |||
public static void clearAdded(JSArray a) { | |||
for (int i = 0, len = a.size(); i < len; i++) { | |||
clearAdded(a.getNode(i)); | |||
} | |||
} | |||
public static native void clearAdded(Node node) /*-{ | |||
node.added = null; | |||
}-*/; | |||
public static native NodeList<Element> getElementsByClassName(String clazz, | |||
Node ctx) /*-{ | |||
return ctx.getElementsByClassName(clazz); | |||
}-*/; | |||
public static native boolean isAdded(Node prevRef) /*-{ | |||
return prevRef.added || false; | |||
}-*/; | |||
public static native void setAdded(Node prevRef, boolean added) /*-{ | |||
prevRef.added = added; | |||
}-*/; | |||
public static native void setSkipTag(JSArray prevElem, boolean skip) /*-{ | |||
prevElem.skipTag = skip; | |||
}-*/; | |||
private static String attrToRegExp(String attrVal, String op) { | |||
if (SelectorEngine.eq("^", op)) { | |||
return "^" + attrVal; | |||
} | |||
if (SelectorEngine.eq("$", op)) { | |||
return attrVal + "$"; | |||
} | |||
if (SelectorEngine.eq("*", op)) { | |||
return attrVal; | |||
} | |||
if (SelectorEngine.eq("|", op)) { | |||
return "(^" + attrVal + "(\\-\\w+)*$)"; | |||
} | |||
if (SelectorEngine.eq("~", op)) { | |||
return "\\b" + attrVal + "\\b"; | |||
} | |||
return SelectorEngine.truth(attrVal) ? "^" + attrVal + "$" : null; | |||
} | |||
private static void clearChildElms(JSArray prevParents) { | |||
for (int n = 0, nl = prevParents.size(); n < nl; n++) { | |||
setHasChildElms(prevParents.getNode(n), false); | |||
} | |||
} | |||
protected String getAttr(Element current, String name) { | |||
return current.getAttribute(name); | |||
} | |||
private static void getDescendantNodes(JSArray matchingElms, | |||
String nextTagStr, Node prevRef) { | |||
NodeList<Element> children = getElementsByTagName(nextTagStr, prevRef); | |||
for (int k = 0, klen = children.getLength(); k < klen; k++) { | |||
Node child = children.getItem(k); | |||
if (child.getParentNode() == prevRef) { | |||
matchingElms.addNode(child); | |||
} | |||
} | |||
} | |||
private JSArray getElementsByPseudo(JSArray previousMatch, String pseudoClass, | |||
String pseudoValue) { | |||
JSArray prevParents = JSArray.create(); | |||
boolean previousDir = pseudoClass.startsWith("first") ? true : false; | |||
JSArray matchingElms = JSArray.create(); | |||
Node prev, next, previous; | |||
if (SelectorEngine.eq("first-child", pseudoClass) || SelectorEngine | |||
.eq("last-child", pseudoClass)) { | |||
getFirstChildPseudo(previousMatch, previousDir, matchingElms); | |||
} else if (SelectorEngine.eq("only-child", pseudoClass)) { | |||
getOnlyChildPseudo(previousMatch, matchingElms); | |||
} else if (SelectorEngine.eq("nth-child", pseudoClass)) { | |||
matchingElms = getNthChildPseudo(previousMatch, pseudoValue, prevParents, | |||
matchingElms); | |||
} else if (SelectorEngine.eq("first-of-type", pseudoClass) || SelectorEngine | |||
.eq("last-of-type", pseudoClass)) { | |||
getFirstOfTypePseudo(previousMatch, previousDir, matchingElms); | |||
} else if (SelectorEngine.eq("only-of-type", pseudoClass)) { | |||
getOnlyOfTypePseudo(previousMatch, matchingElms); | |||
} else if (SelectorEngine.eq("nth-of-type", pseudoClass)) { | |||
matchingElms = getNthOfTypePseudo(previousMatch, pseudoValue, prevParents, | |||
matchingElms); | |||
} else if (SelectorEngine.eq("empty", pseudoClass)) { | |||
getEmptyPseudo(previousMatch, matchingElms); | |||
} else if (SelectorEngine.eq("enabled", pseudoClass)) { | |||
getEnabledPseudo(previousMatch, matchingElms); | |||
} else if (SelectorEngine.eq("disabled", pseudoClass)) { | |||
getDisabledPseudo(previousMatch, matchingElms); | |||
} else if (SelectorEngine.eq("checked", pseudoClass)) { | |||
getCheckedPseudo(previousMatch, matchingElms); | |||
} else if (SelectorEngine.eq("contains", pseudoClass)) { | |||
getContainsPseudo(previousMatch, pseudoValue, matchingElms); | |||
} else if (SelectorEngine.eq("not", pseudoClass)) { | |||
matchingElms = getNotPseudo(previousMatch, pseudoValue, matchingElms); | |||
} else { | |||
getDefaultPseudo(previousMatch, pseudoClass, pseudoValue, matchingElms); | |||
} | |||
return matchingElms; | |||
} | |||
private void getDefaultPseudo(JSArray previousMatch, String pseudoClass, | |||
String pseudoValue, JSArray matchingElms) { | |||
Node previous; | |||
for (int w = 0, wlen = previousMatch.size(); w < wlen; w++) { | |||
previous = previousMatch.getElement(w); | |||
if (SelectorEngine | |||
.eq(((Element) previous).getAttribute(pseudoClass), pseudoValue)) { | |||
matchingElms.addNode(previous); | |||
} | |||
} | |||
} | |||
private JSArray getNotPseudo(JSArray previousMatch, String pseudoValue, | |||
JSArray matchingElms) { | |||
if (new Regexp("(:\\w+[\\w\\-]*)$").test(pseudoValue)) { | |||
matchingElms = subtractArray(previousMatch, | |||
getElementsByPseudo(previousMatch, pseudoValue.substring(1), "")); | |||
} else { | |||
pseudoValue = pseudoValue | |||
.replace("^\\[#([\\w\\u00C0-\\uFFFF\\-\\_]+)\\]$", "[id=$1]"); | |||
JSArray notTag = new Regexp("^(\\w+)").exec(pseudoValue); | |||
JSArray notClass = new Regexp("^\\.([\\w\u00C0-\uFFFF\\-_]+)") | |||
.exec(pseudoValue); | |||
JSArray notAttr = new Regexp( | |||
"\\[(\\w+)(\\^|\\$|\\*|\\||~)?=?([\\w\\u00C0-\\uFFFF\\s\\-_\\.]+)?\\]") | |||
.exec(pseudoValue); | |||
Regexp notRegExp = new Regexp( | |||
"(^|\\s)" + (SelectorEngine.truth(notTag) ? notTag | |||
.getStr(1) | |||
: SelectorEngine.truth(notClass) ? notClass.getStr(1) : "") | |||
+ "(\\s|$)", "i"); | |||
if (SelectorEngine.truth(notAttr)) { | |||
String notAttribute = SelectorEngine.truth(notAttr.getStr(3)) ? notAttr | |||
.getStr(3) | |||
.replace("\\.", "\\.") : null; | |||
String notMatchingAttrVal = attrToRegExp(notAttribute, | |||
notAttr.getStr(2)); | |||
notRegExp = new Regexp(notMatchingAttrVal, "i"); | |||
} | |||
for (int v = 0, vlen = previousMatch.size(); v < vlen; v++) { | |||
Element notElm = previousMatch.getElement(v); | |||
Element addElm = null; | |||
if (SelectorEngine.truth(notTag) && !notRegExp | |||
.test(notElm.getNodeName())) { | |||
addElm = notElm; | |||
} else if (SelectorEngine.truth(notClass) && !notRegExp | |||
.test(notElm.getClassName())) { | |||
addElm = notElm; | |||
} else if (SelectorEngine.truth(notAttr)) { | |||
String att = getAttr(notElm, notAttr.getStr(1)); | |||
if (!SelectorEngine.truth(att) || !notRegExp.test(att)) { | |||
addElm = notElm; | |||
} | |||
} | |||
if (SelectorEngine.truth(addElm) && !isAdded(addElm)) { | |||
setAdded(addElm, true); | |||
matchingElms.addNode(addElm); | |||
} | |||
} | |||
} | |||
return matchingElms; | |||
} | |||
private void getContainsPseudo(JSArray previousMatch, String pseudoValue, | |||
JSArray matchingElms) { | |||
Node previous; | |||
for (int q = 0, qlen = previousMatch.size(); q < qlen; q++) { | |||
previous = previousMatch.getNode(q); | |||
if (!isAdded(previous)) { | |||
if (((Element) previous).getInnerText().indexOf(pseudoValue) != -1) { | |||
setAdded(previous, true); | |||
matchingElms.addNode(previous); | |||
} | |||
} | |||
} | |||
} | |||
private void getCheckedPseudo(JSArray previousMatch, JSArray matchingElms) { | |||
Node previous; | |||
for (int q = 0, qlen = previousMatch.size(); q < qlen; q++) { | |||
previous = previousMatch.getNode(q); | |||
if (checked(previous)) { | |||
matchingElms.addNode(previous); | |||
} | |||
} | |||
} | |||
private void getDisabledPseudo(JSArray previousMatch, JSArray matchingElms) { | |||
Node previous; | |||
for (int q = 0, qlen = previousMatch.size(); q < qlen; q++) { | |||
previous = previousMatch.getNode(q); | |||
if (!enabled(previous)) { | |||
matchingElms.addNode(previous); | |||
} | |||
} | |||
} | |||
private void getEnabledPseudo(JSArray previousMatch, JSArray matchingElms) { | |||
Node previous; | |||
for (int q = 0, qlen = previousMatch.size(); q < qlen; q++) { | |||
previous = previousMatch.getNode(q); | |||
if (enabled(previous)) { | |||
matchingElms.addNode(previous); | |||
} | |||
} | |||
} | |||
private void getEmptyPseudo(JSArray previousMatch, JSArray matchingElms) { | |||
Node previous; | |||
for (int q = 0, qlen = previousMatch.size(); q < qlen; q++) { | |||
previous = previousMatch.getNode(q); | |||
if (!previous.hasChildNodes()) { | |||
matchingElms.addNode(previous); | |||
} | |||
} | |||
} | |||
private JSArray getNthOfTypePseudo(JSArray previousMatch, String pseudoValue, | |||
JSArray prevParents, JSArray matchingElms) { | |||
Node previous; | |||
if (pseudoValue.startsWith("n")) { | |||
matchingElms = previousMatch; | |||
} else { | |||
Sequence sequence = getSequence(pseudoValue); | |||
if (sequence != null) { | |||
for (int p = 0, plen = previousMatch.size(); p < plen; p++) { | |||
previous = previousMatch.getNode(p); | |||
Node prevParent = previous.getParentNode(); | |||
if (!hasChildElms(prevParent)) { | |||
int iteratorNext = sequence.start; | |||
int childCount = 0; | |||
Node childElm = prevParent.getFirstChild(); | |||
while (SelectorEngine.truth(childElm) && (sequence.max < 0 | |||
|| iteratorNext <= sequence.max)) { | |||
if (SelectorEngine | |||
.eq(childElm.getNodeName(), previous.getNodeName())) { | |||
if (++childCount == iteratorNext) { | |||
matchingElms.addNode(childElm); | |||
iteratorNext += sequence.add; | |||
} | |||
} | |||
childElm = SelectorEngine.getNextSibling(childElm); | |||
} | |||
setHasChildElms(prevParent, true); | |||
prevParents.addNode(prevParent); | |||
} | |||
} | |||
clearChildElms(prevParents); | |||
} | |||
} | |||
return matchingElms; | |||
} | |||
private void getOnlyOfTypePseudo(JSArray previousMatch, | |||
JSArray matchingElms) { | |||
Node previous; | |||
Node next; | |||
Node prev; | |||
Node oParent = null; | |||
for (int o = 0, olen = previousMatch.size(); o < olen; o++) { | |||
prev = next = previous = previousMatch.getNode(o); | |||
Node prevParent = previous.getParentNode(); | |||
if (prevParent != oParent) { | |||
while ( | |||
SelectorEngine.truth(prev = SelectorEngine.getPreviousSibling(prev)) | |||
&& !SelectorEngine | |||
.eq(prev.getNodeName(), previous.getNodeName())) { | |||
} | |||
while (SelectorEngine.truth(next = SelectorEngine.getNextSibling(next)) | |||
&& !SelectorEngine.eq(next.getNodeName(), previous.getNodeName())) { | |||
} | |||
if (!SelectorEngine.truth(prev) && !SelectorEngine.truth(next)) { | |||
matchingElms.addNode(previous); | |||
} | |||
oParent = prevParent; | |||
} | |||
} | |||
} | |||
private void getFirstOfTypePseudo(JSArray previousMatch, boolean previousDir, | |||
JSArray matchingElms) { | |||
Node previous; | |||
Node next; | |||
for (int n = 0, nlen = previousMatch.size(); n < nlen; n++) { | |||
next = previous = previousMatch.getNode(n); | |||
if (previousDir) { | |||
while ( | |||
SelectorEngine.truth(next = SelectorEngine.getPreviousSibling(next)) | |||
&& !SelectorEngine | |||
.eq(next.getNodeName(), previous.getNodeName())) { | |||
} | |||
} else { | |||
while (SelectorEngine.truth(next = SelectorEngine.getNextSibling(next)) | |||
&& !SelectorEngine.eq(next.getNodeName(), previous.getNodeName())) { | |||
} | |||
} | |||
if (!SelectorEngine.truth(next)) { | |||
matchingElms.addNode(previous); | |||
} | |||
} | |||
} | |||
private JSArray getNthChildPseudo(JSArray previousMatch, String pseudoValue, | |||
JSArray prevParents, JSArray matchingElms) { | |||
Node previous; | |||
if (SelectorEngine.eq(pseudoValue, "n")) { | |||
matchingElms = previousMatch; | |||
} else { | |||
Sequence sequence = getSequence(pseudoValue); | |||
if (sequence != null) { | |||
for (int l = 0, llen = previousMatch.size(); l < llen; l++) { | |||
previous = previousMatch.getNode(l); | |||
Node prevParent = previous.getParentNode(); | |||
if (!hasChildElms(prevParent)) { | |||
int iteratorNext = sequence.start; | |||
int childCount = 0; | |||
Node childElm = prevParent.getFirstChild(); | |||
while (childElm != null && (sequence.max < 0 | |||
|| iteratorNext <= sequence.max)) { | |||
if (childElm.getNodeType() == Node.ELEMENT_NODE) { | |||
if (++childCount == iteratorNext) { | |||
if (SelectorEngine | |||
.eq(childElm.getNodeName(), previous.getNodeName())) { | |||
matchingElms.addNode(childElm); | |||
} | |||
iteratorNext += sequence.add; | |||
} | |||
} | |||
childElm = SelectorEngine.getNextSibling(childElm); | |||
} | |||
setHasChildElms(prevParent, true); | |||
prevParents.addNode(prevParent); | |||
} | |||
} | |||
clearChildElms(prevParents); | |||
} | |||
} | |||
return matchingElms; | |||
} | |||
private void getOnlyChildPseudo(JSArray previousMatch, JSArray matchingElms) { | |||
Node previous; | |||
Node next; | |||
Node prev; | |||
Node kParent = null; | |||
for (int k = 0, klen = previousMatch.size(); k < klen; k++) { | |||
prev = next = previous = previousMatch.getNode(k); | |||
Node prevParent = previous.getParentNode(); | |||
if (prevParent != kParent) { | |||
while ( | |||
SelectorEngine.truth(prev = SelectorEngine.getPreviousSibling(prev)) | |||
&& prev.getNodeType() != Node.ELEMENT_NODE) { | |||
} | |||
while (SelectorEngine.truth(next = SelectorEngine.getNextSibling(next)) | |||
&& next.getNodeType() != Node.ELEMENT_NODE) { | |||
} | |||
if (!SelectorEngine.truth(prev) && !SelectorEngine.truth(next)) { | |||
matchingElms.addNode(previous); | |||
} | |||
kParent = prevParent; | |||
} | |||
} | |||
} | |||
private void getFirstChildPseudo(JSArray previousMatch, boolean previousDir, | |||
JSArray matchingElms) { | |||
Node next; | |||
Node previous; | |||
for (int j = 0, jlen = previousMatch.size(); j < jlen; j++) { | |||
previous = next = previousMatch.getElement(j); | |||
if (previousDir) { | |||
while (SelectorEngine | |||
.truth((next = SelectorEngine.getPreviousSibling(next))) | |||
&& next.getNodeType() != Node.ELEMENT_NODE) { | |||
} | |||
} else { | |||
while ( | |||
SelectorEngine.truth((next = SelectorEngine.getNextSibling(next))) | |||
&& next.getNodeType() != Node.ELEMENT_NODE) { | |||
} | |||
} | |||
if (!SelectorEngine.truth(next)) { | |||
matchingElms.addNode(previous); | |||
} | |||
} | |||
} | |||
private static native boolean checked(Node previous) /*-{ | |||
return previous.checked || false; | |||
}-*/; | |||
private static native boolean enabled(Node node) /*-{ | |||
return !node.disabled; | |||
}-*/; | |||
private static NodeList<Element> getElementsByTagName(String tag, Node ctx) { | |||
return ((Element) ctx).getElementsByTagName(tag); | |||
} | |||
private static void getGeneralSiblingNodes(JSArray matchingElms, | |||
JSArray nextTag, Regexp nextRegExp, Node prevRef) { | |||
while ( | |||
SelectorEngine.truth((prevRef = SelectorEngine.getNextSibling(prevRef))) | |||
&& !isAdded(prevRef)) { | |||
if (!SelectorEngine.truth(nextTag) || nextRegExp | |||
.test(prevRef.getNodeName())) { | |||
setAdded(prevRef, true); | |||
matchingElms.addNode(prevRef); | |||
} | |||
} | |||
} | |||
private static void getSiblingNodes(JSArray matchingElms, JSArray nextTag, | |||
Regexp nextRegExp, Node prevRef) { | |||
while ( | |||
SelectorEngine.truth(prevRef = SelectorEngine.getNextSibling(prevRef)) | |||
&& prevRef.getNodeType() != Node.ELEMENT_NODE) { | |||
} | |||
if (SelectorEngine.truth(prevRef)) { | |||
if (!SelectorEngine.truth(nextTag) || nextRegExp | |||
.test(prevRef.getNodeName())) { | |||
matchingElms.addNode(prevRef); | |||
} | |||
} | |||
} | |||
private static native boolean hasChildElms(Node prevParent) /*-{ | |||
return prevParent.childElms || false; | |||
}-*/; | |||
private static native boolean isSkipped(JSArray prevElem) /*-{ | |||
return prevElem.skipTag || false; | |||
}-*/; | |||
private static native void setHasChildElms(Node prevParent, boolean bool) /*-{ | |||
prevParent.childElms = bool ? bool : null; | |||
}-*/; | |||
private static native JSArray subtractArray(JSArray previousMatch, | |||
JSArray elementsByPseudo) /*-{ | |||
for (var i=0, src1; (src1=arr1[i]); i++) { | |||
var found = false; | |||
for (var j=0, src2; (src2=arr2[j]); j++) { | |||
if (src2 === src1) { | |||
found = true; | |||
break; | |||
} | |||
} | |||
if (found) { | |||
arr1.splice(i--, 1); | |||
} | |||
} | |||
return arr; | |||
}-*/; | |||
public NodeList<Element> select(String sel, Node ctx) { | |||
String selectors[] = sel.replace("\\s*(,)\\s*", "$1").split(","); | |||
boolean identical = false; | |||
JSArray elm = JSArray.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 (SelectorEngine.eq(selectors[a], selectors[b])) { | |||
identical = true; | |||
break; | |||
} | |||
} | |||
if (identical) { | |||
continue; | |||
} | |||
} | |||
String currentRule = selectors[a]; | |||
JSArray cssSelectors = selectorSplitRegExp.match(currentRule); | |||
JSArray prevElem = JSArray.create(ctx); | |||
for (int i = 0, slen = cssSelectors.size(); i < slen; i++) { | |||
JSArray matchingElms = JSArray.create(); | |||
String rule = cssSelectors.getStr(i); | |||
if (i > 0 && childOrSiblingRefRegExp.test(rule)) { | |||
JSArray childOrSiblingRef = childOrSiblingRefRegExp.exec(rule); | |||
if (SelectorEngine.truth(childOrSiblingRef)) { | |||
JSArray nextTag = new Regexp("^\\w+") | |||
.exec(cssSelectors.getStr(i + 1)); | |||
Regexp nextRegExp = null; | |||
String nextTagStr = null; | |||
if (SelectorEngine.truth(nextTag)) { | |||
nextTagStr = nextTag.getStr(0); | |||
nextRegExp = new Regexp("(^|\\s)" + nextTagStr + "(\\s|$)", "i"); | |||
} | |||
for (int j = 0, jlen = prevElem.size(); j < jlen; j++) { | |||
Node prevRef = prevElem.getNode(j); | |||
String ref = childOrSiblingRef.getStr(0); | |||
if (SelectorEngine.eq(">", ref)) { | |||
getDescendantNodes(matchingElms, nextTagStr, prevRef); | |||
} else if (SelectorEngine.eq("+", ref)) { | |||
getSiblingNodes(matchingElms, nextTag, nextRegExp, prevRef); | |||
} else if (SelectorEngine.eq("~", ref)) { | |||
getGeneralSiblingNodes(matchingElms, nextTag, nextRegExp, | |||
prevRef); | |||
} | |||
} | |||
prevElem = matchingElms; | |||
clearAdded(prevElem); | |||
rule = cssSelectors.getStr(++i); | |||
if (new Regexp("^\\w+$").test(rule)) { | |||
continue; | |||
} | |||
setSkipTag(prevElem, true); | |||
} | |||
} | |||
JSArray cssSelector = cssSelectorRegExp.exec(rule); | |||
SplitRule splitRule = new SplitRule( | |||
!SelectorEngine.truth(cssSelector.getStr(1)) || SelectorEngine | |||
.eq(cssSelector.getStr(3), "*") ? "*" : cssSelector.getStr(1), | |||
!SelectorEngine.eq(cssSelector.getStr(3), "*") ? cssSelector | |||
.getStr(2) : null, cssSelector.getStr(4), cssSelector.getStr(6), | |||
cssSelector.getStr(10)); | |||
if (SelectorEngine.truth(splitRule.id)) { | |||
Element domelem = Document.get() | |||
.getElementById(splitRule.id.substring(1)); | |||
if (SelectorEngine.truth(domelem)) { | |||
matchingElms = JSArray.create(domelem); | |||
} | |||
prevElem = matchingElms; | |||
} else if (SelectorEngine.truth(splitRule.tag) && !isSkipped(prevElem)) { | |||
if (i == 0 && matchingElms.size() == 0 && prevElem.size() == 1) { | |||
prevElem = matchingElms = JSArray.create( | |||
getElementsByTagName(splitRule.tag, prevElem.getNode(0))); | |||
} else { | |||
NodeList<Element> tagCollectionMatches; | |||
for (int l = 0, ll = prevElem.size(); l < ll; l++) { | |||
tagCollectionMatches = getElementsByTagName(splitRule.tag, | |||
prevElem.getNode(l)); | |||
for (int m = 0, mlen = tagCollectionMatches.getLength(); m < mlen; | |||
m++) { | |||
Node tagMatch = tagCollectionMatches.getItem(m); | |||
if (!isAdded(tagMatch)) { | |||
setAdded(tagMatch, true); | |||
matchingElms.addNode(tagMatch); | |||
} | |||
} | |||
} | |||
prevElem = matchingElms; | |||
clearAdded(prevElem); | |||
} | |||
if (matchingElms.size() == 0) { | |||
break; | |||
} | |||
setSkipTag(prevElem, false); | |||
if (SelectorEngine.truth(splitRule.allClasses)) { | |||
String[] allClasses = splitRule.allClasses.replaceFirst("^\\.", "") | |||
.split("\\."); | |||
Regexp[] regExpClassNames = new Regexp[allClasses.length]; | |||
for (int n = 0, nl = allClasses.length; n < nl; n++) { | |||
regExpClassNames[n] = new Regexp( | |||
"(^|\\s)" + allClasses[n] + "(\\s|$)"); | |||
} | |||
JSArray matchingClassElms = JSArray.create(); | |||
for (int o = 0, olen = prevElem.size(); o < olen; o++) { | |||
Element current = prevElem.getElement(o); | |||
String elmClass = current.getClassName(); | |||
boolean addElm = false; | |||
if (SelectorEngine.truth(elmClass) && !isAdded(current)) { | |||
for (int p = 0, pl = regExpClassNames.length; p < pl; p++) { | |||
addElm = regExpClassNames[p].test(elmClass); | |||
if (!addElm) { | |||
break; | |||
} | |||
} | |||
if (addElm) { | |||
setAdded(current, true); | |||
matchingClassElms.addNode(current); | |||
} | |||
} | |||
} | |||
clearAdded(prevElem); | |||
prevElem = matchingElms = matchingClassElms; | |||
} | |||
if (SelectorEngine.truth(splitRule.allAttr)) { | |||
JSArray allAttr = Regexp | |||
.match("\\[[^\\]]+\\]", "g", splitRule.allAttr); | |||
Regexp[] regExpAttributes = new Regexp[allAttr.size()]; | |||
String[] regExpAttributesStr = new String[allAttr.size()]; | |||
Regexp attributeMatchRegExp = new Regexp( | |||
"(\\w+)(\\^|\\$|\\*|\\||~)?=?([\\w\u00C0-\uFFFF\\s\\-_\\.]+)?"); | |||
for (int q = 0, ql = allAttr.size(); q < ql; q++) { | |||
JSArray attributeMatch = attributeMatchRegExp | |||
.exec(allAttr.getStr(q)); | |||
String attributeValue = | |||
SelectorEngine.truth(attributeMatch.getStr(3)) | |||
? attributeMatch.getStr(3).replaceAll("\\.", "\\.") | |||
: null; | |||
String attrVal = attrToRegExp(attributeValue, | |||
(SelectorEngine.or(attributeMatch.getStr(2), null))); | |||
regExpAttributes[q] = (SelectorEngine.truth(attrVal) ? new Regexp( | |||
attrVal) : null); | |||
regExpAttributesStr[q] = attributeMatch.getStr(1); | |||
} | |||
JSArray matchingAttributeElms = JSArray.create(); | |||
for (int r = 0, rlen = matchingElms.size(); r < rlen; r++) { | |||
Element current = matchingElms.getElement(r); | |||
boolean addElm = false; | |||
for (int s = 0, sl = regExpAttributes.length, attributeRegExp; | |||
s < sl; s++) { | |||
addElm = false; | |||
Regexp attributeRegExp2 = regExpAttributes[s]; | |||
String currentAttr = getAttr(current, regExpAttributesStr[s]); | |||
if (SelectorEngine.truth(currentAttr) | |||
&& currentAttr.length() != 0) { | |||
if (attributeRegExp2 == null || attributeRegExp2 | |||
.test(currentAttr)) { | |||
addElm = true; | |||
} | |||
} | |||
if (!addElm) { | |||
break; | |||
} | |||
} | |||
if (addElm) { | |||
matchingAttributeElms.addNode(current); | |||
} | |||
} | |||
prevElem = matchingElms = matchingAttributeElms; | |||
} | |||
if (SelectorEngine.truth(splitRule.allPseudos)) { | |||
Regexp pseudoSplitRegExp = new Regexp( | |||
":(\\w[\\w\\-]*)(\\(([^\\)]+)\\))?"); | |||
JSArray allPseudos = Regexp.match( | |||
"(:\\w+[\\w\\-]*)(\\([^\\)]+\\))?", "g", splitRule.allPseudos); | |||
for (int t = 0, tl = allPseudos.size(); t < tl; t++) { | |||
JSArray pseudo = pseudoSplitRegExp.match(allPseudos.getStr(t)); | |||
String pseudoClass = SelectorEngine.truth(pseudo.getStr(1)) | |||
? pseudo.getStr(1) | |||
.toLowerCase() : null; | |||
String pseudoValue = SelectorEngine.truth(pseudo.getStr(3)) | |||
? pseudo.getStr(3) : null; | |||
matchingElms = getElementsByPseudo(matchingElms, pseudoClass, | |||
pseudoValue); | |||
clearAdded(matchingElms); | |||
} | |||
prevElem = matchingElms; | |||
} | |||
} | |||
} | |||
elm.pushAll(prevElem); | |||
} | |||
return elm; | |||
} | |||
} |
@@ -0,0 +1,20 @@ | |||
package gquery.client.impl; | |||
import com.google.gwt.dom.client.Element; | |||
/** | |||
* Fix some DOM ops for IE | |||
*/ | |||
public class SelectorEngineJSIE extends SelectorEngineJS { | |||
public native String getAttr(Element elm, String attr) /*-{ | |||
switch (attr) { | |||
case "id": | |||
return elm.id; | |||
case "for": | |||
return elm.htmlFor; | |||
case "class": | |||
return elm.className; | |||
} | |||
return elm.getAttribute(attr, 2); | |||
}-*/; | |||
} |
@@ -0,0 +1,16 @@ | |||
package gquery.client.impl; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.NodeList; | |||
import com.google.gwt.dom.client.Node; | |||
import gquery.client.SelectorEngine; | |||
/** | |||
* | |||
*/ | |||
public class SelectorEngineNative extends SelectorEngineImpl { | |||
public NodeList<Element> select(String selector, Node ctx) { | |||
return SelectorEngine.querySelectorAll(selector, ctx); | |||
} | |||
} |
@@ -0,0 +1,237 @@ | |||
package gquery.client.impl; | |||
import com.google.gwt.core.client.GWT; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.Node; | |||
import com.google.gwt.dom.client.NodeList; | |||
import gquery.client.JSArray; | |||
import gquery.client.Regexp; | |||
import gquery.client.SelectorEngine; | |||
import static gquery.client.SelectorEngine.eq; | |||
import static gquery.client.SelectorEngine.truth; | |||
/** | |||
* Copyright 2007 Timepedia.org 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. | |||
* | |||
* @author Ray Cromwell <ray@timepedia.log> | |||
*/ | |||
public class SelectorEngineXPath extends SelectorEngineImpl { | |||
private Regexp cssSelectorRegExp; | |||
private Regexp selectorSplitRegExp; | |||
private Regexp COMBINATOR; | |||
public SelectorEngineXPath() { | |||
} | |||
private void init() { | |||
if (cssSelectorRegExp == null) { | |||
cssSelectorRegExp = new Regexp( | |||
"^(\\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 Regexp("[^\\s]+", "g"); | |||
COMBINATOR = new Regexp("(>|\\+|~)"); | |||
} | |||
} | |||
public NodeList<Element> select(String sel, Node ctx) { | |||
init(); | |||
String selectors[] = sel.replaceAll("\\s*(,)\\s*", "$1").split(","); | |||
boolean identical = false; | |||
JSArray elm = JSArray.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]; | |||
JSArray cssSelectors = selectorSplitRegExp.match(currentRule); | |||
String xPathExpression = "."; | |||
for (int i = 0, slen = cssSelectors.size(); i < slen; i++) { | |||
String rule = cssSelectors.getStr(i); | |||
JSArray cssSelector = cssSelectorRegExp.exec(rule); | |||
SplitRule splitRule = new SplitRule(!truth(cssSelector.getStr(1)) || eq( | |||
cssSelector.getStr(3), "*") ? "*" : cssSelector.getStr(1), | |||
!eq(cssSelector.getStr(3), "*") ? cssSelector.getStr(2) : null, | |||
cssSelector.getStr(4), cssSelector.getStr(6), | |||
cssSelector.getStr(10), cssSelector.getStr(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.getStr(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)) { | |||
GWT.log("AllAttr is " + splitRule.allAttr, null); | |||
xPathExpression += replaceAttr( | |||
SelectorEngine.or(splitRule.allAttr, "")); | |||
} | |||
if (truth(splitRule.allPseudos)) { | |||
Regexp pseudoSplitRegExp = new Regexp( | |||
":(\\w[\\w\\-]*)(\\(([^\\)]+)\\))?"); | |||
Regexp pseudoMatchRegExp = new Regexp( | |||
"(:\\w+[\\w\\-]*)(\\([^\\)]+\\))?", "g"); | |||
JSArray allPseudos = pseudoMatchRegExp.match(splitRule.allPseudos); | |||
for (int k = 0, kl = allPseudos.size(); k < kl; k++) { | |||
JSArray pseudo = pseudoSplitRegExp.match(allPseudos.getStr(k)); | |||
String pseudoClass = truth(pseudo.getStr(1)) ? pseudo.getStr(1) | |||
.toLowerCase() : null; | |||
String pseudoValue = truth(pseudo.getStr(3)) ? pseudo.getStr(3) | |||
: null; | |||
String xpath = pseudoToXPath(splitRule.tag, pseudoClass, | |||
pseudoValue); | |||
if (xpath.length() > 0) { | |||
xPathExpression += "[" + xpath + "]"; | |||
} | |||
} | |||
} | |||
} | |||
SelectorEngine.xpathEvaluate(xPathExpression, ctx, elm); | |||
} | |||
return elm; | |||
} | |||
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 = 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 = 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 Regexp("^(:\\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 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 native String replaceAttr(String allAttr) /*-{ | |||
if(!allAttr) return ""; | |||
return allAttr.replace(/(\w+)(\^|\$|\*|\||~)?=?([\w\u00C0-\uFFFF\s\-_\.]+)?/g, | |||
function(a,b,c,d) { | |||
return @gquery.client.impl.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, @gquery.client.impl.SelectorEngineXPath::attrToXPath(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)); | |||
}-*/; | |||
} |
@@ -0,0 +1,34 @@ | |||
<html> | |||
<head> | |||
<title>GQuery Demo</title> | |||
<script language="javascript" src="gquery.GQueryDemo.nocache.js"></script> | |||
<style type="text/css"> | |||
.slide { border: 1px solid black; width: 800px; height: 600px; display: none} | |||
.slide { font-size: 200%; } | |||
</style> | |||
</head> | |||
<body> | |||
<p> | |||
Short example of how to do animated powerpoint-like slides with GQuery Effects | |||
module. | |||
</p> | |||
<div class="slide transition-fade"> | |||
Slide 1 | |||
<ul class="transition-slidein"> | |||
<li>jQuery is</li> | |||
<li>such a</li> | |||
<li>Cool Library</li> | |||
</ul> | |||
</div> | |||
<div class="slide transition-fade"> | |||
Slide 2 | |||
<ul class="transition-dropin"> | |||
<li>Now GWT</li> | |||
<li>has a</li> | |||
<li>jQuery-like API Too!</li> | |||
<li>GwtQuery Rocks!</li> | |||
</ul> | |||
</div> | |||
</body> | |||
</html> | |||
@@ -0,0 +1,10 @@ | |||
<html> | |||
<head> | |||
<title>GQuery Demo</title> | |||
<script language="javascript" src="gquery.GQuerySample.nocache.js"></script> | |||
</head> | |||
<body> | |||
<div>Foo <span class="note">bar</span> baz</div> | |||
</body> | |||
</html> | |||
@@ -0,0 +1,186 @@ | |||
package gquery.rebind; | |||
import com.google.gwt.core.ext.Generator; | |||
import com.google.gwt.core.ext.GeneratorContext; | |||
import com.google.gwt.core.ext.TreeLogger; | |||
import com.google.gwt.core.ext.UnableToCompleteException; | |||
import com.google.gwt.core.ext.typeinfo.JClassType; | |||
import com.google.gwt.core.ext.typeinfo.JMethod; | |||
import com.google.gwt.core.ext.typeinfo.JParameter; | |||
import com.google.gwt.core.ext.typeinfo.TypeOracle; | |||
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; | |||
import com.google.gwt.user.rebind.SourceWriter; | |||
import java.io.PrintWriter; | |||
import gquery.client.Selector; | |||
/** | |||
* | |||
*/ | |||
public abstract class SelectorGeneratorBase extends Generator { | |||
private TreeLogger treeLogger; | |||
protected JClassType NODE_TYPE = null; | |||
public String generate(TreeLogger treeLogger, | |||
GeneratorContext generatorContext, String requestedClass) | |||
throws UnableToCompleteException { | |||
this.treeLogger = treeLogger; | |||
TypeOracle oracle = generatorContext.getTypeOracle(); | |||
NODE_TYPE = oracle.findType("com.google.gwt.dom.client.Node"); | |||
JClassType selectorType = oracle.findType(requestedClass); | |||
SourceWriter sw = getSourceWriter(treeLogger, generatorContext, | |||
selectorType.getPackage().getName(), | |||
selectorType.getSimpleSourceName() + getImplSuffix(), requestedClass); | |||
if (sw != null) { | |||
for (JMethod method : selectorType.getMethods()) { | |||
generateMethod(sw, method, treeLogger); | |||
} | |||
genGetAllMethod(sw, selectorType.getMethods(), treeLogger); | |||
sw.commit(treeLogger); | |||
} | |||
return selectorType.getPackage().getName() + "." | |||
+ selectorType.getSimpleSourceName() + getImplSuffix(); | |||
} | |||
protected String getImplSuffix() { | |||
return "Impl"; | |||
} | |||
// used by benchmark harness | |||
private void genGetAllMethod(SourceWriter sw, JMethod[] methods, | |||
TreeLogger treeLogger) { | |||
sw.println("public DeferredGQuery[] getAllSelectors() {"); | |||
sw.indent(); | |||
sw.println( | |||
"DeferredGQuery[] dg = new DeferredGQuery[" + (methods.length) + "];"); | |||
int i = 0; | |||
for (JMethod m : methods) { | |||
String selector = m.getAnnotation(Selector.class).value(); | |||
sw.println("dg[" + i + "]=new DeferredGQuery() {"); | |||
sw.indent(); | |||
sw.println( | |||
"public String getSelector() { return \"" + selector + "\"; }"); | |||
sw.println("public GQuery eval(Node ctx) { return " + wrapJS(m, m.getName() | |||
+ (m.getParameters().length == 0 ? "()" : "(ctx)")+"") + " ;}"); | |||
sw.println("public NodeList<Element> array(Node ctx) { return "+("NodeList".equals(m.getReturnType().getSimpleSourceName()) ? | |||
(m.getName() | |||
+ (m.getParameters().length == 0 ? "(); " : "(ctx); ")) : | |||
"eval"+(m.getParameters().length == 0 ? "(null).get(); " : "(ctx).get(); "))+"}"); | |||
i++; | |||
sw.outdent(); | |||
sw.println("};"); | |||
} | |||
sw.println("return dg;"); | |||
sw.outdent(); | |||
sw.println("}"); | |||
} | |||
public void generateMethod(SourceWriter sw, JMethod method, TreeLogger logger) | |||
throws UnableToCompleteException { | |||
String selector = method.getAnnotation(Selector.class).value(); | |||
JParameter[] params = method.getParameters(); | |||
sw.indent(); | |||
String retType = method.getReturnType().getParameterizedQualifiedSourceName(); | |||
sw.print("public final "+retType+" "+method.getName()); | |||
boolean hasContext = false; | |||
if (params.length == 0) { | |||
sw.print("()"); | |||
} else if (params.length == 1) { | |||
JClassType type = params[0].getType().isClassOrInterface(); | |||
if (type != null && type.isAssignableTo(NODE_TYPE)) { | |||
sw.print("(Node root)"); | |||
hasContext = true; | |||
} | |||
} | |||
sw.println(" {"); | |||
sw.indent(); | |||
Selector sel = method.getAnnotation(Selector.class); | |||
// short circuit #foo | |||
if (sel != null && sel.value().matches("^#\\w+$")) { | |||
sw.println("return "+wrap(method, "JSArray.create(((Document)" + (hasContext ? "root" : "(Node)Document.get()") | |||
+ ").getElementById(\"" + sel.value().substring(1) + "\"))")+";"); | |||
} | |||
// short circuit FOO | |||
else if (sel != null && sel.value().matches("^\\w+$")) { | |||
sw.println("return "+wrap(method, "JSArray.create(((Element)"+(hasContext ? "root" : "(Node)Document.get()") | |||
+ ").getElementsByTagName(\"" + sel.value() + "\"))")+";"); | |||
} // short circuit .foo for browsers with native getElementsByClassName | |||
else if (sel != null && sel.value().matches("^\\.\\w+$") | |||
&& hasGetElementsByClassName()) { | |||
sw.println("return "+wrap(method, "JSArray.create(getElementsByClassName(\"" | |||
+ sel.value().substring(1) + "\", " + (hasContext ? "root" : "(Node)Document.get()") | |||
+ "))")+";"); | |||
} else { | |||
generateMethodBody(sw, method, logger, hasContext); | |||
} | |||
sw.outdent(); | |||
sw.println("}"); | |||
sw.outdent(); | |||
} | |||
protected boolean hasGetElementsByClassName() { | |||
return false; | |||
} | |||
protected void debug(String s) { | |||
// System.err.println(s); | |||
treeLogger.log(TreeLogger.DEBUG, s, null); | |||
} | |||
protected boolean notNull(String s) { | |||
return s != null && !"".equals(s); | |||
} | |||
protected SourceWriter getSourceWriter(TreeLogger logger, | |||
GeneratorContext context, String packageName, String className, | |||
String... interfaceNames) { | |||
PrintWriter printWriter = context.tryCreate(logger, packageName, className); | |||
if (printWriter == null) { | |||
return null; | |||
} | |||
ClassSourceFileComposerFactory composerFactory | |||
= new ClassSourceFileComposerFactory(packageName, className); | |||
composerFactory.setSuperclass("gquery.client.SelectorEngine"); | |||
composerFactory.addImport("com.google.gwt.core.client.GWT"); | |||
composerFactory.addImport("gquery.client.GQuery"); | |||
composerFactory.addImport("gquery.client.JSArray"); | |||
composerFactory.addImport("com.google.gwt.dom.client.*"); | |||
for (String interfaceName : interfaceNames) { | |||
composerFactory.addImplementedInterface(interfaceName); | |||
} | |||
return composerFactory.createSourceWriter(context, printWriter); | |||
} | |||
protected String wrap(JMethod method, String expr) { | |||
if("NodeList".equals(method.getReturnType().getSimpleSourceName())) { | |||
return expr; | |||
} | |||
else { | |||
return "new GQuery("+expr+")"; | |||
} | |||
} | |||
protected String wrapJS(JMethod method, String expr) { | |||
if("GQuery".equals(method.getReturnType().getSimpleSourceName())) { | |||
return expr; | |||
} | |||
else { | |||
return "new GQuery("+expr+")"; | |||
} | |||
} | |||
protected abstract void generateMethodBody(SourceWriter sw, JMethod method, | |||
TreeLogger logger, boolean hasContext) throws UnableToCompleteException; | |||
} |
@@ -0,0 +1,174 @@ | |||
package gquery.rebind; | |||
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.user.rebind.SourceWriter; | |||
import java.util.regex.Pattern; | |||
import gquery.client.Selector; | |||
/** | |||
* | |||
*/ | |||
public class SelectorGeneratorJS extends SelectorGeneratorBase { | |||
protected static Pattern nonSpace = Pattern.compile("\\S/"); | |||
private static final String trimReStr = "^\\s+|\\s+$"; | |||
protected static Pattern trimRe = Pattern.compile(trimReStr); | |||
protected static Pattern tplRe = Pattern.compile("\\{(\\d+)\\}"); | |||
protected static Pattern modeRe = Pattern | |||
.compile("^(\\s?[\\/>+~]\\s?|\\s|$)"); | |||
protected static Pattern tagTokenRe = Pattern | |||
.compile("^(#)?([a-zA-Z_0-9-\\*]+)"); | |||
protected static Pattern nthRe = Pattern.compile("(\\d*)n\\+?(\\d*)"); | |||
protected static Pattern nthRe2 = Pattern.compile("\\D"); | |||
protected static RuleMatcher[] matchers = new RuleMatcher[]{new RuleMatcher( | |||
"^\\.([a-zA-Z_0-9-]+)", "n = byClassName(n, null, \"{0}\");"), | |||
new RuleMatcher("^\\:([a-zA-Z_0-9-]+)(?:\\(((?:[^ >]*|.*?))\\))?", | |||
"n = byPseudo(n, \"{0}\", \"{1}\");"), new RuleMatcher( | |||
"^(?:([\\[\\{])(?:@)?([a-zA-Z_0-9-]+)\\s?(?:(=|.=)\\s?['\"]?(.*?)[\"']?)?[\\]\\}])", | |||
"n = byAttribute(n, \"{1}\", \"{3}\", \"{2}\", \"{0}\");"), | |||
new RuleMatcher("^#([a-zA-Z_0-9-]+)", "n = byId(n, null, \"{0}\");")}; | |||
protected String getImplSuffix() { | |||
return "JS"+super.getImplSuffix(); | |||
} | |||
protected void generateMethodBody(SourceWriter sw, JMethod method, | |||
TreeLogger treeLogger, boolean hasContext) throws UnableToCompleteException { | |||
String selector = method.getAnnotation(Selector.class).value(); | |||
if(!hasContext) sw.println("Node root = Document.get();"); | |||
sw.println("return "+wrap(method, "new SelectorEngine().select(\""+selector+"\", root)")+";"); | |||
// sw.println("JSArray n = JSArray.create();"); | |||
// if(!hasContext) { | |||
// sw.println("Node root = Document.get();"); | |||
// } | |||
// | |||
// // add root node as context. | |||
// // TODO: support any context | |||
// sw.println("n.addNode(root);"); | |||
// String q = selector, lq = null; | |||
// Matcher lmode = modeRe.matcher(q); | |||
// Matcher mm = null; | |||
// String mode = ""; | |||
// if (lmode.lookingAt() && notNull(lmode.group(1))) { | |||
// mode = lmode.group(1).replaceAll(trimReStr, "").trim(); | |||
// q = q.replaceFirst("\\Q" + lmode.group(1) + "\\E", ""); | |||
// } | |||
// | |||
// while (notNull(q) && !q.equals(lq)) { | |||
// debug("Doing q=" + q); | |||
// | |||
// lq = q; | |||
// Matcher tm = tagTokenRe.matcher(q); | |||
// if (tm.lookingAt()) { | |||
// if ("#".equals(tm.group(1))) { | |||
// sw.println("n = quickId(n, \"" + mode + "\", root, \"" + tm.group(2) | |||
// + "\");"); | |||
// } else { | |||
// String tagName = tm.group(2); | |||
// tagName = "".equals(tagName) ? "*" : tagName; | |||
// // sw.println("if (n.size() == 0) { n=JSArray.create(); }"); | |||
// String func = ""; | |||
// if ("".equals(mode)) { | |||
// func = "getDescendentNodes"; | |||
// } else if (">".equals(mode)) { | |||
// func = "getChildNodes"; | |||
// } else if ("+".equals(mode)) { | |||
// func = "getSiblingNodes"; | |||
// } else if ("~".equals(mode)) { | |||
// func = "getGeneralSiblingNodes"; | |||
// } else { | |||
// treeLogger.log(TreeLogger.ERROR, "Error parsing selector, combiner " | |||
// + mode + " not recognized in " + selector, null); | |||
// throw new UnableToCompleteException(); | |||
// } | |||
// sw.println("n = " + func + "(n, \"" + tagName + "\");"); | |||
// } | |||
// debug("replacing in q, the value " + tm.group(0)); | |||
// q = q.replaceFirst("\\Q" + tm.group(0) + "\\E", ""); | |||
// } else { | |||
// String func = ""; | |||
// String tagName = "*"; | |||
// if ("".equals(mode)) { | |||
// func = "getDescendentNodes"; | |||
// } else if (">".equals(mode)) { | |||
// func = "getChildNodes"; | |||
// } else if ("+".equals(mode)) { | |||
// func = "getSiblingNodes"; | |||
// } else if ("~".equals(mode)) { | |||
// func = "getGeneralSiblingNodes"; | |||
// } else { | |||
// treeLogger.log(TreeLogger.ERROR, "Error parsing selector, combiner " | |||
// + mode + " not recognized in " + selector, null); | |||
// throw new UnableToCompleteException(); | |||
// } | |||
// sw.println("n = " + func + "(n, \"" + tagName + "\");"); | |||
// } | |||
// | |||
// while (!(mm = modeRe.matcher(q)).lookingAt()) { | |||
// debug("Looking at " + q); | |||
// boolean matched = false; | |||
// for (RuleMatcher rm : matchers) { | |||
// Matcher rmm = rm.re.matcher(q); | |||
// if (rmm.lookingAt()) { | |||
// String res[] = new String[rmm.groupCount()]; | |||
// for (int i = 1; i <= rmm.groupCount(); i++) { | |||
// res[i - 1] = rmm.group(i); | |||
// debug("added param " + res[i - 1]); | |||
// } | |||
// Object[] r = res; | |||
// // inline enum, perhaps type-tightening will allow inlined eval() | |||
// // call | |||
// if (rm.fnTemplate.indexOf("byPseudo") != -1) { | |||
// sw.println("n = Pseudo."+res[0].toUpperCase().replace("-", "_") + | |||
// ".eval(n, \""+res[1]+"\");"); | |||
// } else { | |||
// sw.println(MessageFormat.format(rm.fnTemplate, r)); | |||
// } | |||
// q = q.replaceFirst("\\Q" + rmm.group(0) + "\\E", ""); | |||
// matched = true; | |||
// break; | |||
// } | |||
// } | |||
// if (!matched) { | |||
// treeLogger | |||
// .log(TreeLogger.ERROR, "Error parsing selector at " + q, null); | |||
// throw new UnableToCompleteException(); | |||
// } | |||
// } | |||
// | |||
// if (notNull(mm.group(1))) { | |||
// mode = mm.group(1).replaceAll(trimReStr, ""); | |||
// debug("replacing q=" + q + " this part:" + mm.group(1)); | |||
// q = q.replaceFirst("\\Q" + mm.group(1) + "\\E", ""); | |||
// } | |||
// } | |||
// sw.println("return "+wrap(method, "nodup(n)")+";"); | |||
} | |||
static class RuleMatcher { | |||
public Pattern re; | |||
public String fnTemplate; | |||
RuleMatcher(String pat, String fnT) { | |||
this.re = Pattern.compile(pat); | |||
this.fnTemplate = fnT; | |||
} | |||
} | |||
} |
@@ -0,0 +1,32 @@ | |||
package gquery.rebind; | |||
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.user.rebind.SourceWriter; | |||
import gquery.client.Selector; | |||
/** | |||
* | |||
*/ | |||
public class SelectorGeneratorNative extends SelectorGeneratorBase { | |||
protected String getImplSuffix() { | |||
return "Native" + super.getImplSuffix(); | |||
} | |||
protected void generateMethodBody(SourceWriter sw, JMethod method, | |||
TreeLogger treeLogger, boolean hasContext) | |||
throws UnableToCompleteException { | |||
String selector = method.getAnnotation(Selector.class).value(); | |||
if (!hasContext) { | |||
sw.println("return " | |||
+ wrap(method, "querySelectorAll(\"" + selector + "\"") + ");"); | |||
} else { | |||
sw.println("return " | |||
+ wrap(method, "querySelectorAll(\"" + selector + "\", root)") | |||
+ ");"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,317 @@ | |||
package gquery.rebind; | |||
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.user.rebind.SourceWriter; | |||
import java.util.ArrayList; | |||
import java.util.regex.Matcher; | |||
import java.util.regex.Pattern; | |||
import gquery.client.Selector; | |||
/** | |||
* | |||
*/ | |||
public class SelectorGeneratorXPath extends SelectorGeneratorBase { | |||
private static Pattern cssSelectorRegExp = Pattern.compile( | |||
"^(\\w+)?(#[a-zA-Z_0-9\u00C0-\uFFFF\\-\\_]+|(\\*))?((\\.[a-zA-Z_0-9\u00C0-\uFFFF\\-_]+)*)?((\\[\\w+(\\^|\\$|\\*|\\||~)?(=[a-zA-Z_0-9\u00C0-\uFFFF\\s\\-\\_\\.]+)?\\]+)*)?(((:\\w+[a-zA-Z_0-9\\-]*)(\\((odd|even|\\-?\\d*n?((\\+|\\-)\\d+)?|[a-zA-Z_0-9\u00C0-\uFFFF\\-_]+|((\\w*\\.[a-zA-Z_0-9\u00C0-\uFFFF\\-_]+)*)?|(\\[#?\\w+(\\^|\\$|\\*|\\||~)?=?[a-zA-Z_0-9\u00C0-\uFFFF\\s\\-\\_\\.]+\\]+)|(:\\w+[a-zA-Z_0-9\\-]*))\\))?)*)?(>|\\+|~)?"); | |||
private static Pattern selectorSplitRegExp = Pattern | |||
.compile("(?:\\[[^\\[]*\\]|\\(.*\\)|[^\\s\\+>~\\[\\(])+|[\\+>~]"); | |||
private String prefix = ""; | |||
static class SplitRule { | |||
public String tag; | |||
public String id; | |||
public String allClasses; | |||
public String allAttr; | |||
public String allPseudos; | |||
public String tagRelation; | |||
} | |||
protected String getImplSuffix() { | |||
return "XPath"+super.getImplSuffix(); | |||
} | |||
protected void generateMethodBody(SourceWriter sw, JMethod method, | |||
TreeLogger treeLogger, boolean hasContext) | |||
throws UnableToCompleteException { | |||
String selector = method.getAnnotation(Selector.class).value(); | |||
String[] cssRules = selector.replaceAll("\\s*(,)\\s*", "$1").split(","); | |||
String currentRule; | |||
boolean identical = false; | |||
String xPathExpression = "."; | |||
for (int i = 0; i < cssRules.length; i++) { | |||
currentRule = cssRules[i]; | |||
if (i > 0) { | |||
identical = false; | |||
for (int x = 0, xl = i; x < xl; x++) { | |||
if (cssRules[i].equals(cssRules[x])) { | |||
identical = true; | |||
break; | |||
} | |||
} | |||
if (identical) { | |||
continue; | |||
} | |||
} | |||
ArrayList<String> cssSelectors = new ArrayList<String>(); | |||
Matcher selm = selectorSplitRegExp.matcher(currentRule); | |||
while (selm.find()) { | |||
cssSelectors.add(selm.group(0)); | |||
} | |||
Matcher cssSelector; | |||
for (int j = 0, jl = cssSelectors.size(); j < jl; j++) { | |||
cssSelector = cssSelectorRegExp.matcher(cssSelectors.get(j)); | |||
if (cssSelector.matches()) { | |||
SplitRule splitRule = new SplitRule(); | |||
splitRule.tag = prefix + ((!notNull(cssSelector.group(1)) || "*" | |||
.equals(cssSelector.group(3))) ? "*" : cssSelector.group(1)); | |||
splitRule.id = (!"*".equals(cssSelector.group(3))) ? cssSelector | |||
.group(2) : null; | |||
splitRule.allClasses = cssSelector.group(4); | |||
splitRule.allAttr = cssSelector.group(6); | |||
splitRule.allPseudos = cssSelector.group(10); | |||
splitRule.tagRelation = cssSelector.group(22); | |||
if (notNull(splitRule.tagRelation)) { | |||
if (">".equals(splitRule.tagRelation)) { | |||
xPathExpression += "/child::"; | |||
} else if ("+".equals(splitRule.tagRelation)) { | |||
xPathExpression += "/following-sibling::*[1]/self::"; | |||
} else if ("~".equals(splitRule.tagRelation)) { | |||
xPathExpression += "/following-sibling::"; | |||
} | |||
} else { | |||
xPathExpression += | |||
(j > 0 && cssSelectors.get(j - 1).matches("(>|\\+|~)")) | |||
? splitRule.tag : ("/descendant::" + splitRule.tag); | |||
} | |||
if (notNull(splitRule.id)) { | |||
xPathExpression += "[@id = '" + splitRule.id.replaceAll("^#", "") | |||
+ "']"; | |||
} | |||
if (notNull(splitRule.allClasses)) { | |||
xPathExpression += splitRule.allClasses.replaceAll( | |||
"\\.([a-zA-Z_0-9\u00C0 -\uFFFF\\-_]+)", | |||
"[contains(concat(' ', @class, ' '), ' $1 ')]"); | |||
} | |||
if (notNull(splitRule.allAttr)) { | |||
xPathExpression += attrToXPath(splitRule.allAttr, | |||
"(\\w+)(\\^|\\$|\\*|\\||~)?=?([a-zA-Z_0-9\u00C0-\uFFFF\\s\\-_\\.]+)?"); | |||
} | |||
if (notNull(splitRule.allPseudos)) { | |||
Pattern pseudoSplitRegExp = Pattern | |||
.compile(":(\\w[a-zA-Z_0-9\\-]*)(\\(([^\\)]+)\\))?"); | |||
Matcher m = Pattern | |||
.compile("(:\\w+[a-zA-Z_0-9\\-]*)(\\([^\\)]+\\))?") | |||
.matcher(splitRule.allPseudos); | |||
while (m.find()) { | |||
String str = m.group(0); | |||
Matcher pseudo = pseudoSplitRegExp | |||
.matcher(str == null ? "" : str); | |||
if (pseudo.matches()) { | |||
String pseudoClass = notNull(pseudo.group(1)) ? pseudo.group(1) | |||
.toLowerCase() : null; | |||
String pseudoValue = notNull(pseudo.group(3)) ? pseudo.group(3) | |||
: null; | |||
String xpath = pseudoToXPath(splitRule.tag, pseudoClass, | |||
pseudoValue); | |||
if (notNull(xpath)) { | |||
xPathExpression += "[" + xpath + "]"; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
if (!hasContext) { | |||
sw.println("Node root = Document.get();"); | |||
} | |||
sw.println("return "+wrap(method, "SelectorEngine.xpathEvaluate(\"" | |||
+ xPathExpression + "\", root)")+";"); | |||
} | |||
static class Sequence { | |||
public int start; | |||
public int max; | |||
public int add; | |||
public int modVal; | |||
} | |||
private String pseudoToXPath(String tag, String pseudoClass, | |||
String pseudoValue) { | |||
tag = pseudoClass.matches(".*\\-child$") ? "*" : tag; | |||
String xpath = ""; | |||
String pseudo[] = pseudoClass.split("-"); | |||
if ("first".equals(pseudo[0])) { | |||
xpath = "not(preceding-sibling::" + tag + ")"; | |||
} else if ("last".equals(pseudo[0])) { | |||
xpath = "not(following-sibling::" + tag + ")"; | |||
} else if ("only".equals(pseudo[0])) { | |||
xpath = "not(preceding-sibling::" + tag + " or following-sibling::" + tag | |||
+ ")"; | |||
} else if ("nth".equals(pseudo[0])) { | |||
if (!pseudoValue.matches("^n$")) { | |||
String position = (("last".equals(pseudo[1])) | |||
? "(count(following-sibling::" : "(count(preceding-sibling::") + tag | |||
+ ") + 1)"; | |||
Sequence sequence = getSequence(pseudoValue); | |||
if (sequence != null) { | |||
if (sequence.start == sequence.max) { | |||
xpath = position + " = " + sequence.start; | |||
} else { | |||
xpath = position + " mod " + sequence.add + " = " + sequence.modVal | |||
+ ((sequence.start > 1) ? " and " + position + " >= " + sequence | |||
.start : "") + ((sequence.max > 0) ? " and " + position + " <= " | |||
+ sequence.max : ""); | |||
} | |||
} | |||
} | |||
} else if ("empty".equals(pseudo[0])) { | |||
xpath = "count(child::*) = 0 and string-length(text()) = 0"; | |||
} else if ("contains".equals(pseudo[0])) { | |||
xpath = "contains(., '" + pseudoValue + "')"; | |||
} else if ("enabled".equals(pseudo[0])) { | |||
xpath = "not(@disabled)"; | |||
} else if ("disabled".equals(pseudo[0])) { | |||
xpath = "@disabled"; | |||
} else if ("checked".equals(pseudo[0])) { | |||
xpath = "@checked='checked'"; // Doesn't work in Opera 9.24 | |||
} else if ("not".equals(pseudo[0])) { | |||
if (pseudoValue.matches("^(:a-zA-Z_0-9+[a-zA-Z_0-9\\-]*)$")) { | |||
xpath = "not(" + pseudoToXPath(tag, pseudoValue.substring(1), "") + ")"; | |||
} else { | |||
pseudoValue = pseudoValue.replaceAll( | |||
"^\\[#([a-zA-Z_0-9\u00C0-\uFFFF\\-\\_]+)\\]$", "[id=$1]"); | |||
String notSelector = pseudoValue | |||
.replaceFirst("^(a-zA-Z_0-9+)", "self::$1"); | |||
notSelector = notSelector.replaceAll( | |||
"^\\.([a-zA-Z_0-9\u00C0-\uFFFF\\-_]+)", | |||
"contains(concat(' ', @class, ' '), ' $1 ')"); | |||
notSelector = attrToXPath(notSelector, | |||
"\\[(a-zA-Z_0-9+)(\\^|\\$|\\*|\\||~)?=?([a-zA-Z_0-9\u00C0-\uFFFF\\s\\-_\\.]+)?\\]"); | |||
xpath = "not(" + notSelector + ")"; | |||
} | |||
} else { | |||
xpath = "@" + pseudoClass + "='" + pseudoValue + "'"; | |||
} | |||
return xpath; | |||
} | |||
private String attrToXPath(String notSelector, String pattern) { | |||
Pattern p = Pattern.compile(pattern); | |||
Matcher m = p.matcher(notSelector); | |||
m.reset(); | |||
boolean result = m.find(); | |||
if (result) { | |||
StringBuffer sb = new StringBuffer(); | |||
do { | |||
String replacement; | |||
String p1 = m.group(1); | |||
String p2 = m.group(2); | |||
String p3 = m.group(3); | |||
if ("^".equals(p2)) { | |||
replacement = "starts-with(@" + p1 + ", '" + p3 + "')"; | |||
} else if ("$".equals(p2)) { | |||
replacement = "substring(@" + p1 + ", (string-length(@" + p1 + ") - " | |||
+ (p3.length() - 1) + "), " + p3.length() + ") = '" + p3 + "'"; | |||
} else if ("*".equals(p2)) { | |||
replacement = "contains(concat(' ', @" + p1 + ", ' '), '" + p3 + "')"; | |||
} else if ("|".equals(p2)) { | |||
replacement = "(@" + p1 + "='" + p3 + "' or starts-with(@" + p1 | |||
+ ", '" + p3 + "-'))"; | |||
} else if ("~".equals(p2)) { | |||
replacement = "contains(concat(' ', @" + p1 + ", ' '), ' " + p3 | |||
+ " ')"; | |||
} else { | |||
replacement = "@" + p1 + (notNull(p3) ? "='" + p3 + "'" : ""); | |||
} | |||
debug("p1=" + p1 + " p2=" + p2 + " p3=" + p3 + " replacement is " | |||
+ replacement); | |||
m.appendReplacement(sb, replacement.replace("$", "\\$")); | |||
result = m.find(); | |||
} while (result); | |||
m.appendTail(sb); | |||
return sb.toString(); | |||
} | |||
return notSelector; | |||
} | |||
private Sequence getSequence(String expression) { | |||
int start = 0, add = 2, max = -1, modVal = -1; | |||
Pattern expressionRegExp = Pattern.compile( | |||
"^((odd|even)|([1-9]\\d*)|((([1-9]\\d*)?)n([\\+\\-]\\d+)?)|(\\-(([1-9]\\d*)?)n\\+(\\d+)))$"); | |||
Matcher pseudoValue = expressionRegExp.matcher(expression); | |||
if (!pseudoValue.matches()) { | |||
return null; | |||
} else { | |||
if (notNull(pseudoValue.group(2))) { // odd or even | |||
start = ("odd".equals(pseudoValue.group(2))) ? 1 : 2; | |||
modVal = (start == 1) ? 1 : 0; | |||
} else if (notNull(pseudoValue.group(3))) { // single digit | |||
start = Integer.parseInt(pseudoValue.group(3), 10); | |||
add = 0; | |||
max = start; | |||
} else if (notNull(pseudoValue.group(4))) { // an+b | |||
add = notNull(pseudoValue.group(6)) ? getInt(pseudoValue.group(6), 1) | |||
: 1; | |||
start = notNull(pseudoValue.group(7)) ? getInt(pseudoValue.group(7), 0) | |||
: 0; | |||
while (start < 1) { | |||
start += add; | |||
} | |||
modVal = (start > add) ? (start - add) % add | |||
: ((start == add) ? 0 : start); | |||
} else if (notNull(pseudoValue.group(8))) { // -an+b | |||
add = notNull(pseudoValue.group(10)) ? Integer | |||
.parseInt(pseudoValue.group(10), 10) : 1; | |||
start = max = Integer.parseInt(pseudoValue.group(10), 10); | |||
while (start > add) { | |||
start -= add; | |||
} | |||
modVal = (max > add) ? (max - add) % add : ((max == add) ? 0 : max); | |||
} | |||
} | |||
Sequence s = new Sequence(); | |||
s.start = start; | |||
s.add = add; | |||
s.max = max; | |||
s.modVal = modVal; | |||
return s; | |||
} | |||
private int getInt(String s, int i) { | |||
try { | |||
if (s.startsWith("+")) { | |||
s = s.substring(1); | |||
} | |||
return Integer.parseInt(s); | |||
} catch (Exception e) { | |||
debug("error parsing Integer " + s); | |||
return i; | |||
} | |||
} | |||
} |
@@ -0,0 +1,12 @@ | |||
package gquery.rebind.gebcn; | |||
import gquery.rebind.SelectorGeneratorJS; | |||
/** | |||
*/ | |||
public class SelectorGeneratorJSGEBCN extends SelectorGeneratorJS { | |||
protected boolean hasGetElementsByClassName() { | |||
return true; | |||
} | |||
} |
@@ -0,0 +1,13 @@ | |||
package gquery.rebind.gebcn; | |||
import gquery.rebind.SelectorGeneratorNative; | |||
/** | |||
* | |||
*/ | |||
public class SelectorGeneratorNativeGEBCN extends SelectorGeneratorNative { | |||
protected boolean hasGetElementsByClassName() { | |||
return true; | |||
} | |||
} |
@@ -0,0 +1,12 @@ | |||
package gquery.rebind.gebcn; | |||
import gquery.rebind.SelectorGeneratorXPath; | |||
/** | |||
*/ | |||
public class SelectorGeneratorXPathGEBCN extends SelectorGeneratorXPath { | |||
protected boolean hasGetElementsByClassName() { | |||
return true; | |||
} | |||
} |