com.cgxlib.xq.client.impl.SelectorEngine.java Source code

Java tutorial

Introduction

Here is the source code for com.cgxlib.xq.client.impl.SelectorEngine.java

Source

/*
 * 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.cgxlib.xq.client.impl;

/*
 * #%L
 * CGXlib
 * %%
 * Copyright (C) 2016 CGXlib (http://www.cgxlib.com)
 * %%
 * 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.
 * #L%
  Code is originally from gwtquery, and modified by CGXlib team.
 */

import com.cgxlib.xq.client.Predicate;
import com.cgxlib.xq.client.js.JsMap;
import com.cgxlib.xq.client.js.JsNodeArray;
import com.cgxlib.xq.client.js.JsUtils;
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.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;

import java.util.HashSet;

import static com.cgxlib.xq.client.XQ.document;
import static com.cgxlib.xq.client.XQ.window;

/**
 * Core Selector engine functions, and native JS utility functions.
 */
public class SelectorEngine implements HasSelector {

    public static final boolean hasQuerySelector = hasQuerySelectorAll();
    public static JsMap<String, Predicate> filters;
    private static DocumentStyleImpl styleImpl;

    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 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 XQ#getSelectorEngine()}
     * method.
     */
    public boolean filterDetached = true;
    protected Node root = Document.get();
    // pseudo selectors which are computed by xq in runtime
    RegExp xqPseudo = 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 SelectorEngine() {
        impl = (SelectorEngineImpl) GWT.create(SelectorEngineImpl.class);
        GWT.log("XQ - Created SelectorEngineImpl: " + impl.getClass().getName());
        styleImpl = GWT.create(DocumentStyleImpl.class);
        GWT.log("XQ - Created DocumentStyleImpl: " + styleImpl.getClass().getName());
    }

    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 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;
                                                                                                   }-*/;

    /**
     * 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;
                                                    }-*/;

    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 Node getRoot() {
        return root;
    }

    public void setRoot(Node root) {
        assert root != null;
        this.root = 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<Node>();
        HashSet<Node> elmList = new HashSet<Node>();
        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;
    }

    public NodeList<Element> select(String selector, Node ctx) {

        if (nativePseudo.test(selector)) {
            // move xq 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 (xqPseudo.test(selector)) {
            JsNodeArray res = JsNodeArray.create();
            for (String s : selector.trim().split("\\s*,\\s*")) {
                NodeList<Element> nodes;
                MatchResult a = xqPseudo.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 String getName() {
        return getClass().getName().replaceAll("^.*\\.", "");
    }

    public boolean isDegradated() {
        return !hasQuerySelector;
    }

    /**
     * Return the DocumentStyleImpl used by this selector engine.
     */
    public DocumentStyleImpl getDocumentStyleImpl() {
        return styleImpl;
    }
}