/* * 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.css.compiler.ast.CssCompilerPass; import com.google.common.css.compiler.ast.CssKeyNode; import com.google.common.css.compiler.ast.CssKeyframesNode; 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.VisitController; /** * Compiler pass which ensures that @keyframes rules are only allowed if * they are enabled. In addition this pass checks if the keys are between * 0% and 100%. If CSS simplification is enabled, "from" is replaced by "0%" * and "100%" is replaced by "to". * * @author fbenz@google.com (Florian Benz) */ public class ProcessKeyframes extends DefaultTreeVisitor implements CssCompilerPass { @VisibleForTesting static final String KEYFRAMES_NOT_ALLOWED_ERROR_MESSAGE = "a @keyframes rule occured but the option for it is disabled"; @VisibleForTesting static final String WRONG_KEY_VALUE_ERROR_MESSAGE = "the value of the key is not between 0% and 100%"; static final String INVALID_NUMBER_ERROR_MESSAGE = "the value of the key is invalid (not 'from', 'to', or 'XXX.XXX%')"; private final VisitController visitController; private final ErrorManager errorManager; private final boolean keyframesAllowed; private final boolean simplifyCss; public ProcessKeyframes(VisitController visitController, ErrorManager errorManager, boolean keyframesAllowed, boolean simplifyCss) { this.visitController = visitController; this.errorManager = errorManager; this.keyframesAllowed = keyframesAllowed; this.simplifyCss = simplifyCss; } @Override public boolean enterKeyframesRule(CssKeyframesNode node) { if (!keyframesAllowed) { errorManager.report(new GssError(KEYFRAMES_NOT_ALLOWED_ERROR_MESSAGE, node.getSourceCodeLocation())); } return keyframesAllowed; } @Override public boolean enterKey(CssKeyNode node) { if (!keyframesAllowed) { return false; } String value = node.getKeyValue(); float percentage = -1; if (value.contains("%")) { try { // parse to a float by excluding '%' percentage = Float.parseFloat(value.substring(0, value.length() - 1)); } catch (NumberFormatException e) { // should not happen if the generated parser works correctly errorManager.report(new GssError(INVALID_NUMBER_ERROR_MESSAGE, node.getSourceCodeLocation())); return false; } if (!checkRangeOfPercentage(node, percentage)) { return false; } } else { if (!value.equals("from") && !value.equals("to")) { errorManager.report(new GssError(INVALID_NUMBER_ERROR_MESSAGE, node.getSourceCodeLocation())); return false; } } if (simplifyCss) { compactRepresentation(node, percentage); } return true; } /** * Checks if the percentage is between 0% and 100% inclusive. * * @param node The {@link CssKeyNode} to get the location in case of an error * @param percentage The value represented as a float * @return Returns true if there is no error */ private boolean checkRangeOfPercentage(CssKeyNode node, float percentage) { // check whether the percentage is between 0% and 100% if (percentage < 0 || percentage > 100) { errorManager.report(new GssError(WRONG_KEY_VALUE_ERROR_MESSAGE, node.getSourceCodeLocation())); return false; } return true; } /** * Shortens the representation of the key. * * @param node The {@link CssKeyNode} where the percentage belongs to. * @param percentage The value represented as a float */ @VisibleForTesting void compactRepresentation(CssKeyNode node, float percentage) { if (node.getKeyValue().equals("from")) { node.setKeyValue("0%"); } else if (percentage == 100) { node.setKeyValue("to"); } else if (percentage != -1) { String percentageStr = Float.toString(percentage); if (0 < percentage && percentage < 1) { // eliminate an unnecessary leading 0 percentageStr = percentageStr.substring(1, percentageStr.length()); } // eliminate a trailing zero like in 0.0 percentageStr = percentageStr.replaceAll("0+$", ""); if (percentageStr.endsWith(".")) { // if the number ends with '.' then eliminate that too percentageStr = percentageStr.substring(0, percentageStr.length() - 1); } node.setKeyValue(percentageStr + "%"); } } @Override public void runPass() { visitController.startVisit(this); } }