123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- /*
- * Copyright 2011, The gwtquery team.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
- package com.google.gwt.query.client.impl;
-
- import static com.google.gwt.query.client.GQuery.document;
- import static com.google.gwt.query.client.GQuery.window;
-
- 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.dom.client.Node;
- import com.google.gwt.dom.client.NodeList;
- import com.google.gwt.query.client.Predicate;
- import com.google.gwt.query.client.js.JsMap;
- import com.google.gwt.query.client.js.JsNodeArray;
- import com.google.gwt.query.client.js.JsUtils;
- import com.google.gwt.regexp.shared.MatchResult;
- import com.google.gwt.regexp.shared.RegExp;
-
- import java.util.HashSet;
-
- /**
- * Core Selector engine functions, and native JS utility functions.
- */
- public class SelectorEngine implements HasSelector {
-
- private static DocumentStyleImpl styleImpl;
-
- public static native NodeList<Element> getElementsByClassName(String clazz,
- Node ctx) /*-{
- return ctx.getElementsByClassName(clazz);
- }-*/;
-
- public static native Node getNextSibling(Node n) /*-{
- return n.nextSibling || null;
- }-*/;
-
- public static native Node getPreviousSibling(Node n) /*-{
- return n.previousSibling || null;
- }-*/;
-
- public NodeList<Element> querySelectorAll(String selector, Node ctx) {
- if (!hasQuerySelector) {
- return impl.select(selector, ctx);
- }
- try {
- return querySelectorAllImpl(selector, ctx);
- } catch (Exception e) {
- return impl.select(selector, ctx);
- }
- }
-
- public static native NodeList<Element> querySelectorAllImpl(String selector,
- Node ctx) /*-{
- return ctx.querySelectorAll(selector);
- }-*/;
-
- public static native NodeList<Element> elementsByTagName(String selector,
- Node ctx) /*-{
- return ctx.getElementsByTagName(selector);
- }-*/;
-
- public static native NodeList<Element> elementsByClassName(String selector,
- Node ctx) /*-{
- return ctx.getElementsByClassName(selector);
- }-*/;
-
- public static NodeList<Element> veryQuickId(String id, Node ctx) {
- Document d = ctx.getNodeType() == Node.DOCUMENT_NODE
- ? ctx.<Document> cast() : ctx.getOwnerDocument();
- return JsNodeArray.create(d.getElementById(id));
- }
-
- public static NodeList<Element> xpathEvaluate(String selector, Node ctx) {
- return xpathEvaluate(selector, ctx, JsNodeArray.create());
- }
-
- public static native NodeList<Element> xpathEvaluate(String selector,
- Node ctx, JsNodeArray r) /*-{
- var node;
- var ownerDoc = ctx && (ctx.ownerDocument || ctx );
- var evalDoc = ownerDoc ? ownerDoc : $doc;
- var result = evalDoc.evaluate(selector, ctx, null, 0, null);
- while ((node = result.iterateNext())) {
- r.push(node);
- }
- return r;
- }-*/;
-
- public final SelectorEngineImpl impl;
-
- /**
- * Set it to false if all your elements are attached to the DOM and you want to
- * increase filter performance using {@link GQuery#getSelectorEngine()}
- * method.
- */
- public boolean filterDetached = true;
-
- protected Node root = Document.get();
-
- public static final boolean hasQuerySelector = hasQuerySelectorAll();
-
- public static JsMap<String, Predicate> filters;
-
- static {
- filters = JsMap.create();
- filters.put("visible", new Predicate() {
- public boolean f(Element e, int index) {
- return (e.getOffsetWidth() + e.getOffsetHeight()) > 0 &&
- !"none".equalsIgnoreCase(styleImpl.curCSS(e, "display", true));
- }
- });
- filters.put("hidden", new Predicate() {
- public boolean f(Element e, int index) {
- return !filters.get("visible").f(e, index);
- }
- });
- filters.put("selected", new Predicate() {
- public boolean f(Element e, int index) {
- return e.getPropertyBoolean("selected");
- }
- });
- filters.put("input", new Predicate() {
- public boolean f(Element e, int index) {
- return e.getNodeName().toLowerCase().matches("input|select|textarea|button");
- }
- });
- filters.put("header", new Predicate() {
- public boolean f(Element e, int index) {
- return e.getNodeName().toLowerCase().matches("h\\d");
- }
- });
- }
-
- public SelectorEngine() {
- impl = (SelectorEngineImpl) GWT.create(SelectorEngineImpl.class);
- GWT.log("GQuery - Created SelectorEngineImpl: " + impl.getClass().getName());
- styleImpl = GWT.create(DocumentStyleImpl.class);
- GWT.log("GQuery - Created DocumentStyleImpl: " + styleImpl.getClass().getName());
- }
-
- public Node getRoot() {
- return root;
- }
-
- public NodeList<Element> filter(NodeList<Element> nodes, Predicate p) {
- JsNodeArray res = JsNodeArray.create();
- for (int i = 0, l = nodes.getLength(), j = 0; i < l; i++) {
- Element e = nodes.getItem(i);
- if (p.f(e, i)) {
- res.addNode(e, j++);
- }
- }
- return res;
- }
-
- public NodeList<Element> filter(NodeList<Element> nodes, String selector) {
- return filter(nodes, selector, filterDetached);
- }
-
- public NodeList<Element> filter(NodeList<Element> nodes, String selector, boolean filterDetached) {
- JsNodeArray res = JsNodeArray.create();
- if (selector.isEmpty()) {
- return res;
- }
- Element ghostParent = null;
- HashSet<Node> parents = new HashSet<>();
- HashSet<Node> elmList = new HashSet<>();
- for (int i = 0, l = nodes.getLength(); i < l; i++) {
- Node e = nodes.getItem(i);
- if (e == window || e == document || e.getNodeName() == null
- || "html".equalsIgnoreCase(e.getNodeName())) {
- continue;
- }
- elmList.add(e);
- if (filterDetached) {
- Element p = e.getParentElement();
- if (p == null) {
- if (ghostParent == null) {
- ghostParent = Document.get().createDivElement();
- parents.add(ghostParent);
- }
- p = ghostParent;
- p.appendChild(e);
- } else if (!parents.contains(p)) {
- parents.add(p);
- }
- } else if (parents.isEmpty()) {
- parents.add(document);
- }
- }
- for (Node e : parents) {
- NodeList<Element> n = select(selector, e);
- for (int i = 0, l = n.getLength(); i < l; i++) {
- Element el = n.getItem(i);
- if (elmList.remove(el)) {
- res.addNode(el);
- }
- }
- }
- if (ghostParent != null) {
- ghostParent.setInnerHTML(null);
- }
- return res;
- }
-
- // pseudo selectors which are computed by gquery in runtime
- RegExp gQueryPseudo =
- RegExp.compile(
- "(.*):((visible|hidden|selected|input|header)|((button|checkbox|file|hidden|image|password|radio|reset|submit|text)\\s*(,|$)))(.*)", "i");
- // pseudo selectors which work in engine
- RegExp nativePseudo = RegExp.compile(
- "(.*):([\\w]+):(disabled|checked|enabled|empty|focus)\\s*([:,].*|$)", "i");
-
- public NodeList<Element> select(String selector, Node ctx) {
-
- if (nativePseudo.test(selector)) {
- // move gQuery filters at the end to improve performance, and deal with issue #220
- MatchResult r;
- while ((r = nativePseudo.exec(selector)) != null) {
- selector = r.getGroup(1) + ":" + r.getGroup(3);
- if (!r.getGroup(3).equals(r.getGroup(2))) {
- selector += ":" + r.getGroup(2);
- }
- selector += r.getGroup(4);
- }
- }
-
- if (gQueryPseudo.test(selector)) {
- JsNodeArray res = JsNodeArray.create();
- for (String s : selector.trim().split("\\s*,\\s*")) {
- NodeList<Element> nodes;
- MatchResult a = gQueryPseudo.exec(s);
- if (a != null) {
- String select = a.getGroup(1).isEmpty() ? "*" : a.getGroup(1);
- String pseudo = a.getGroup(2);
- Predicate pred = filters.get(pseudo.toLowerCase());
- if (pred != null) {
- nodes = filter(select(select, ctx), pred);
- } else if (nativePseudo.test(pseudo)) {
- nodes = select(select, ctx);
- } else {
- nodes = select(select + "[type=" + pseudo + "]", ctx);
- }
- } else {
- nodes = select(s, ctx);
- }
- JsUtils.copyNodeList(res, nodes, false);
- }
- return res.<NodeList<Element>> cast();
- } else {
- return impl.select(selector, ctx);
- }
- }
-
- public native boolean contains(Element a, Element b) /*-{
- return a.contains ? a != b && a.contains(b) : !!(a.compareDocumentPosition(b) & 16)
- }-*/;
-
- public void setRoot(Node root) {
- assert root != null;
- this.root = root;
- }
-
- public String getName() {
- return getClass().getName().replaceAll("^.*\\.", "");
- }
-
- public boolean isDegradated() {
- return !hasQuerySelector;
- }
-
- /**
- * Check if the browser has native support for css selectors.
- */
- public static native boolean hasQuerySelectorAll() /*-{
- return $doc.location.href.indexOf("_force_no_native") < 0 &&
- typeof $doc.querySelectorAll == 'function';
- }-*/;
-
- public static native boolean hasXpathEvaluate() /*-{
- return !!$doc.evaluate;
- }-*/;
-
- /**
- * Return the DocumentStyleImpl used by this selector engine.
- */
- public DocumentStyleImpl getDocumentStyleImpl() {
- return styleImpl;
- }
- }
|