com.cgxlib.xq.rebind.SelectorGeneratorXPath.java Source code

Java tutorial

Introduction

Here is the source code for com.cgxlib.xq.rebind.SelectorGeneratorXPath.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.rebind;

/*
 * #%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.Selector;
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;

/**
 * Compile time selector generator which translates selector into XPath at
 * compile time.
 */
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 = "";

    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 + "]";
                                }
                            }
                        }
                    }
                }
            }
        }

        sw.println(
                "return " + wrap(method, "SelectorEngine.xpathEvaluate(\"" + xPathExpression + "\", root)") + ";");
    }

    protected String getImplSuffix() {
        return "XPath" + super.getImplSuffix();
    }

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

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

    static class Sequence {

        public int start;

        public int max;

        public int add;

        public int modVal;
    }

    static class SplitRule {

        public String tag;

        public String id;

        public String allClasses;

        public String allAttr;

        public String allPseudos;

        public String tagRelation;
    }
}