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

com.google.common.css.compiler.passes.ProcessKeyframes 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: 20160212
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.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 [email protected] (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);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy