com.google.common.css.compiler.passes.ProcessRefiners.java Source code

Java tutorial

Introduction

Here is the source code for com.google.common.css.compiler.passes.ProcessRefiners.java

Source

/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.css.compiler.passes;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
import com.google.common.css.compiler.ast.CssCompilerPass;
import com.google.common.css.compiler.ast.CssPseudoClassNode;
import com.google.common.css.compiler.ast.CssPseudoClassNode.FunctionType;
import com.google.common.css.compiler.ast.CssPseudoElementNode;
import com.google.common.css.compiler.ast.CssRefinerListNode;
import com.google.common.css.compiler.ast.CssRefinerNode;
import com.google.common.css.compiler.ast.DefaultTreeVisitor;
import com.google.common.css.compiler.ast.ErrorManager;
import com.google.common.css.compiler.ast.GssError;
import com.google.common.css.compiler.ast.MutatingVisitController;

/**
 * Compiler pass which ensures that refiners are correctly formated because
 * not every invalid format is rejected by the parser. This pass checks for
 * a correct nth-format and can make it compact. In addition, the pass checks
 * the constraints for the :not pseudo-class.
 *
 * @author fbenz@google.com (Florian Benz)
 */
public class ProcessRefiners extends DefaultTreeVisitor implements CssCompilerPass {
    @VisibleForTesting
    static final String INVALID_NTH_ERROR_MESSAGE = "the format for NTH is not in the form an+b, 'odd', or 'even' where a "
            + "and b are (signed) integers that can be omitted";
    @VisibleForTesting
    static final String INVALID_NOT_SELECTOR_ERROR_MESSAGE = "a :not selector and pseudo-elements ('::') are not allowed inside of"
            + " a :not";
    @VisibleForTesting
    static final String NOT_LANG_ERROR_MESSAGE = "a pseudo-class which takes arguments has to be ':lang()' or has to "
            + "start with 'nth-'";
    private static final CharMatcher CSS_WHITESPACE = CharMatcher.anyOf(" \t\r\n\f");

    private final MutatingVisitController visitController;
    private final ErrorManager errorManager;
    private final boolean simplifyCss;

    public ProcessRefiners(MutatingVisitController visitController, ErrorManager errorManager,
            boolean simplifyCss) {
        this.visitController = visitController;
        this.errorManager = errorManager;
        this.simplifyCss = simplifyCss;
    }

    @Override
    public boolean enterPseudoClass(CssPseudoClassNode refiner) {
        FunctionType functionType = refiner.getFunctionType();
        switch (functionType) {
        case NONE:
            return true;
        case LANG:
            return handleLang(refiner);
        case NTH:
            return handleNth(refiner);
        case NOT:
            return handleNot(refiner);
        }
        return true;
    }

    private static String trim(String input) {
        return CSS_WHITESPACE.trimFrom(input);
    }

    private boolean handleLang(CssPseudoClassNode refiner) {
        if (!refiner.getRefinerName().equals("lang(")) {
            errorManager.report(new GssError(NOT_LANG_ERROR_MESSAGE, refiner.getSourceCodeLocation()));
            return false;
        }
        return true;
    }

    private boolean handleNot(CssPseudoClassNode refiner) {
        if (refiner.getNotSelector() == null) {
            errorManager.report(new GssError(INVALID_NOT_SELECTOR_ERROR_MESSAGE, refiner.getSourceCodeLocation()));
            return false;
        }
        CssRefinerListNode refinerList = refiner.getNotSelector().getRefiners();
        if (refinerList.numChildren() == 0) {
            return true;
        } else if (refinerList.numChildren() > 1) {
            // should not be possible due to the grammar
            errorManager.report(new GssError(INVALID_NOT_SELECTOR_ERROR_MESSAGE, refiner.getSourceCodeLocation()));
            return false;
        }
        CssRefinerNode nestedRefiner = refinerList.getChildAt(0);
        // a pseudo-element is not allowed inside a :not
        if (nestedRefiner instanceof CssPseudoElementNode) {
            errorManager.report(new GssError(INVALID_NOT_SELECTOR_ERROR_MESSAGE, refiner.getSourceCodeLocation()));
            return false;
        }
        // the negation pseudo-class is not allowed inside a :not
        if (nestedRefiner instanceof CssPseudoClassNode) {
            CssPseudoClassNode pseudoClass = (CssPseudoClassNode) nestedRefiner;
            if (pseudoClass.getFunctionType() == FunctionType.NOT) {
                errorManager
                        .report(new GssError(INVALID_NOT_SELECTOR_ERROR_MESSAGE, refiner.getSourceCodeLocation()));
                return false;
            }
        }
        return true;
    }

    private boolean handleNth(CssPseudoClassNode refiner) {
        String argument = trim(refiner.getArgument());
        if (argument.contains(".")) {
            errorManager.report(new GssError(INVALID_NTH_ERROR_MESSAGE, refiner.getSourceCodeLocation()));
            return false;
        }
        // general pattern: an+b
        int a, b;
        if (argument.equals("even")) {
            // 2n
            a = 2;
            b = 0;
        } else if (argument.equals("odd")) {
            // 2n+1
            a = 2;
            b = 1;
        } else {
            try {
                int indexOfN = argument.indexOf('n');
                a = parseA(argument, indexOfN);
                b = parseB(argument, indexOfN);
            } catch (NumberFormatException e) {
                errorManager.report(new GssError(INVALID_NTH_ERROR_MESSAGE, refiner.getSourceCodeLocation()));
                return false;
            }
        }
        if (simplifyCss) {
            refiner.setArgument(compactRepresentation(a, b));
        }
        return true;
    }

    private String compactRepresentation(int a, int b) {
        if (a == 2 && b == 1) {
            // 2n+1 -> odd
            return "odd";
        }
        if (a == 0 && b == 0) {
            return "0";
        }
        StringBuilder compact = new StringBuilder();
        if (a != 0) {
            if (a != 1 || b == 0 /* for WebKit */) {
                compact.append(Integer.toString(a));
            }
            compact.append("n");
        }
        if (b > 0 && a != 0) {
            compact.append("+");
        }
        if (b != 0) {
            compact.append(Integer.toString(b));
        }
        return compact.toString();
    }

    @VisibleForTesting
    int parseA(String argument, int indexOfN) {
        if (indexOfN == -1) {
            // b
            return 0;
        } else {
            if (indexOfN > 0) {
                String aStr = trim(argument.substring(0, indexOfN));
                if (aStr.equals("+")) {
                    // +n+b
                    return 1;
                } else if (aStr.equals("-")) {
                    // -n+b
                    return -1;
                } else {
                    // an+b
                    aStr = aStr.replace("+", "");
                    return Integer.parseInt(aStr);
                }
            } else {
                // n+b
                return 1;
            }
        }
    }

    @VisibleForTesting
    int parseB(String argument, int indexOfN) {
        if (indexOfN == -1) {
            // b
            argument = trim(argument.replace("+", ""));
            return Integer.parseInt(argument);
        } else {
            if (indexOfN + 1 < argument.length()) {
                // an+b
                String bStr = argument.substring(indexOfN + 1, argument.length());
                bStr = trim(bStr);
                bStr = bStr.replace("+", "");
                return Integer.parseInt(bStr);
            } else {
                // an
                return 0;
            }
        }
    }

    @Override
    public void runPass() {
        visitController.startVisit(this);
    }
}