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

com.google.common.css.compiler.passes.ResolveCustomFunctionNodes 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 2009 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.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.css.compiler.ast.CssCompilerPass;
import com.google.common.css.compiler.ast.CssCustomFunctionNode;
import com.google.common.css.compiler.ast.CssFunctionNode;
import com.google.common.css.compiler.ast.CssLiteralNode;
import com.google.common.css.compiler.ast.CssValueNode;
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.GssFunction;
import com.google.common.css.compiler.ast.GssFunctionException;
import com.google.common.css.compiler.ast.MutatingVisitController;
import com.google.common.css.compiler.ast.Proxiable;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This compiler pass replaces {@link CssCustomFunctionNode} instances with the
 * list of nodes returned by the GssFunction.
 *
 */
public class ResolveCustomFunctionNodes extends DefaultTreeVisitor
    implements CssCompilerPass {

  private final MutatingVisitController visitController;
  protected Map functionMap;
  private final ErrorManager errorManager;
  private final boolean allowUnknownFunctions;
  private final Set allowedNonStandardFunctions;

  /**
   * Constructs the pass.
   *
   * @param visitController The visit controller
   * @param errorManager The error manager
   * @param functionMap The map from function names to resolve to GSS functions
   */
  public ResolveCustomFunctionNodes(MutatingVisitController visitController,
                                    ErrorManager errorManager,
                                    Map functionMap) {
    this(visitController, errorManager, functionMap,
        false /* allowUnknownFunctions */);
  }

  /**
   * Constructs the pass.
   *
   * @param visitController The visit controller
   * @param errorManager The error manager
   * @param functionMap The map from function names to resolve to GSS functions
   * @param allowUnknownFunctions Whether to allow unknown function calls,
   *     leaving them as is, instead of reporting an error
   */
  public ResolveCustomFunctionNodes(MutatingVisitController visitController,
                                    ErrorManager errorManager,
                                    Map functionMap,
                                    boolean allowUnknownFunctions) {
    this(visitController, errorManager, functionMap, allowUnknownFunctions,
        ImmutableSet.of() /* allowedNonStandardFunctions */);
  }

  /**
   * Constructs the pass.
   *
   * @param visitController The visit controller
   * @param errorManager The error manager
   * @param functionMap The map from function names to resolve to GSS functions
   * @param allowUnknownFunctions Whether to allow unknown function calls,
   *     leaving them as is, instead of reporting an error
   * @param allowedNonStandardFunctions functions that should not yield a
   *     warning if they appear in a stylesheet
   */
  public ResolveCustomFunctionNodes(MutatingVisitController visitController,
                                    ErrorManager errorManager,
                                    Map functionMap,
                                    boolean allowUnknownFunctions,
                                    Set allowedNonStandardFunctions) {
    Preconditions.checkNotNull(functionMap);
    this.visitController = visitController;
    this.errorManager = errorManager;
    this.functionMap = functionMap;
    this.allowUnknownFunctions = allowUnknownFunctions;
    this.allowedNonStandardFunctions = ImmutableSet.copyOf(
        allowedNonStandardFunctions);
  }

  @Override
  public void leaveFunctionNode(CssFunctionNode functionNode) {
    if (!(functionNode instanceof Proxiable)) {
      return;
    }

    CssCustomFunctionNode node = (CssCustomFunctionNode) functionNode;

    List functionResult = node.getResult();
    if (functionResult == null) {
      // Look up the function's name in the map.
      String functionName = node.getFunctionName();
      GssFunction function = functionMap.get(functionName);
      if (function == null) {
        if (!allowUnknownFunctions && !allowedNonStandardFunctions.contains(
            functionName)) {
          errorManager.report(new GssError(
              String.format("Unknown function \"%s\"", functionName),
              node.getSourceCodeLocation()));
          visitController.removeCurrentNode();
        }
        return;
      }

      // Removes the commas from the arguments and groups arguments separated
      // by commas, e.g.:
      // verticalGradient(#fff, #f2f2f2, url(foo) left top)
      // has 7 initial arguments (2 commas, 5 words).
      // After the cleanup we end up with the 3 arguments separated by commas.
      // TODO(user): Clean this up once we get rid of the comma-as-argument hack.
      List arguments = Lists.newArrayList();
      CssValueNode lastArg = null;
      for (CssValueNode value : node.getArguments().childIterable()) {
        if (value.getValue() != null && value.getValue().equals(",")) {
          lastArg = null;
        } else {
          if (lastArg == null) {
            arguments.add(value);
            lastArg = value;
          } else {
            if (value instanceof CssLiteralNode
                && " ".equals(value.getValue())) {
              // Here we are building a parse subtree that, when
              // printed by most output passes, looks like the subtree
              // we got as input, but actually consists of just a
              // CssLiteralNode. This requires us to add some
              // whitespace to the printed representations of the
              // nodes we are merging, corresponding to the whitespace
              // eaten by the parser between lexemes and generated by
              // printing passes when flattening a tree back to a
              // string. But functions are special: the parse tree
              // includes CssLiteralNode(" ") to explicitly represent
              // function argument delimiters. We need to avoid adding
              // extra space here, just as printing passes do.
            } else if (lastArg.getValue() == null) {
              arguments.remove(lastArg);
              lastArg = new CssLiteralNode(lastArg.toString() + " "
                  + value.toString());
              arguments.add(lastArg);
            } else {
              lastArg.setValue(lastArg.getValue() + " " + value.toString());
            }
          }
        }
      }

      Integer expArgNumber = function.getNumExpectedArguments();
      int argNumber = arguments.size();
      if (expArgNumber != null && expArgNumber.intValue() != argNumber) {
        errorManager.report(new GssError("Function expects " + expArgNumber
            + " arguments but has " + argNumber,
            node.getSourceCodeLocation()));
        visitController.removeCurrentNode();
        return;
      }

      try {
        functionResult = evaluateFunction(node, function, arguments, errorManager);
      } catch (GssFunctionException e) {
        visitController.removeCurrentNode();
        return;
      } catch (RuntimeException e) {
        errorManager.report(
            new GssError(
                Throwables.getStackTraceAsString(e),
                node.getSourceCodeLocation()));
        visitController.removeCurrentNode();
        return;
      }
    }
    visitController.replaceCurrentBlockChildWith(functionResult, false);
  }

  /**
   * Evaluates the given function node.
   *
   * 

Subclasses may sublcass this method to change the evaluation * process in some circumstances. * * @param node the function node to evaluate * @param function the GSS function matching this node * @param arguments the arguments of this node * @param errorManager the error manager passed into the GSS function call * @return the result of the evaluation as a list of value nodes * @throws GssFunctionException if the function call is invalid * @throws RuntimeException if the function call fails to complete */ protected List evaluateFunction( CssCustomFunctionNode node, GssFunction function, List arguments, ErrorManager errorManager) throws GssFunctionException { List functionResult = function.getCallResultNodes(arguments, errorManager); node.setResult(functionResult); return functionResult; } @Override public void runPass() { visitController.startVisit(this); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy