All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.css.compiler.passes.ProcessRefiners Maven / Gradle / Ivy

Go to download

Closure Stylesheets is an extension to CSS that adds variables, functions, conditionals, and mixins to standard CSS. The tool also supports minification, linting, RTL flipping, and CSS class renaming.

There is a newer version: 1.8.0
Show newest version
/*
 * 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.*;
import com.google.common.css.compiler.ast.CssPseudoClassNode.FunctionType;

/**
 * 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 [email protected] (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(a);
            }
            compact.append("n");
        }
        if (b > 0 && a != 0) {
            compact.append("+");
        }
        if (b != 0) {
            compact.append(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);
                bStr = trim(bStr);
                bStr = bStr.replace("+", "");
                return Integer.parseInt(bStr);
            } else {
                // an
                return 0;
            }
        }
    }

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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy