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

com.google.common.css.compiler.passes.CompactPrinter 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.collect.ImmutableSet;
import com.google.common.css.compiler.ast.CssAttributeSelectorNode;
import com.google.common.css.compiler.ast.CssBlockNode;
import com.google.common.css.compiler.ast.CssBooleanExpressionNode;
import com.google.common.css.compiler.ast.CssClassSelectorNode;
import com.google.common.css.compiler.ast.CssCombinatorNode;
import com.google.common.css.compiler.ast.CssCompilerPass;
import com.google.common.css.compiler.ast.CssCompositeValueNode;
import com.google.common.css.compiler.ast.CssConditionalBlockNode;
import com.google.common.css.compiler.ast.CssDeclarationBlockNode;
import com.google.common.css.compiler.ast.CssDeclarationNode;
import com.google.common.css.compiler.ast.CssDefinitionNode;
import com.google.common.css.compiler.ast.CssFontFaceNode;
import com.google.common.css.compiler.ast.CssFunctionNode;
import com.google.common.css.compiler.ast.CssIdSelectorNode;
import com.google.common.css.compiler.ast.CssImportRuleNode;
import com.google.common.css.compiler.ast.CssKeyListNode;
import com.google.common.css.compiler.ast.CssKeyNode;
import com.google.common.css.compiler.ast.CssKeyframesNode;
import com.google.common.css.compiler.ast.CssMediaRuleNode;
import com.google.common.css.compiler.ast.CssNode;
import com.google.common.css.compiler.ast.CssNodesListNode;
import com.google.common.css.compiler.ast.CssNumericNode;
import com.google.common.css.compiler.ast.CssPageRuleNode;
import com.google.common.css.compiler.ast.CssPageSelectorNode;
import com.google.common.css.compiler.ast.CssPriorityNode;
import com.google.common.css.compiler.ast.CssPropertyValueNode;
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.CssRefinerNode;
import com.google.common.css.compiler.ast.CssSelectorListNode;
import com.google.common.css.compiler.ast.CssSelectorNode;
import com.google.common.css.compiler.ast.CssStringNode;
import com.google.common.css.compiler.ast.CssTree;
import com.google.common.css.compiler.ast.CssUnknownAtRuleNode;
import com.google.common.css.compiler.ast.CssValueNode;

import java.util.logging.Logger;

import javax.annotation.Nullable;

/**
 * A compact-printer for {@link CssTree} instances.
 * TODO(oana): Change this pass to stop visiting when definitions are
 * encountered. The same goes for its test.
 *
 * @author [email protected] (Oana Florescu)
 * @author [email protected] (Florian Benz)
 */
public class CompactPrinter extends CodePrinter implements CssCompilerPass {

  private String compactedPrintedString = null;
  private static final Logger logger = Logger.getLogger(
      CompactPrinter.class.getName());

  public CompactPrinter(CssNode subtree,
      @Nullable CodeBuffer buffer,
      @Nullable GssSourceMapGenerator generator) {
    super(subtree, buffer, generator);
  }

  public CompactPrinter(CssNode subtree, @Nullable CodeBuffer buffer) {
    this(subtree, buffer, null /* generator */);
  }

  public CompactPrinter(CssNode subtree) {
    this(subtree, null /* buffer */);
  }

  public CompactPrinter(CssTree tree,
      @Nullable CodeBuffer buffer,
      @Nullable GssSourceMapGenerator generator) {
    super(tree, buffer, generator);
  }

  public CompactPrinter(CssTree tree, CodeBuffer buffer) {
    this(tree, buffer, null /* generator */);
  }

  public CompactPrinter(CssTree tree, GssSourceMapGenerator generator) {
    this(tree, null /* buffer */, generator);
  }

  public CompactPrinter(CssTree tree) {
    this(tree, null /* buffer */, null /* generator */);
  }

  @Override
  public boolean enterDefinition(CssDefinitionNode node) {
    return false;
  }

  @Override
  public boolean enterImportRule(CssImportRuleNode node) {
    buffer.append(node.getType().toString());
    for (CssValueNode param : node.getParameters()) {
      buffer.append(' ');
      // TODO(user): teach visit controllers to explore
      // CssImportRuleNode parameters rather than leaving it to each
      // pass to figure things out.
      if (param instanceof CssStringNode) {
        buffer.append(param.toString());
      } else {
        buffer.append(param.getValue());
      }
    }
    return true;
  }

  @Override
  public void leaveImportRule(CssImportRuleNode node) {
    buffer.append(';');
  }

  @Override
  public boolean enterMediaRule(CssMediaRuleNode node) {
    buffer.append(node.getType().toString());
    if (node.getParameters().size() > 0) {
      buffer.append(' ');
    }
    return true;
  }

  /**
   * This is necessary because the parser transform '(' ident ')' into a
   * boolean expression node and only stores the identifier itself.
   * For example: {@code @media all and (color)}
   */
  private void appendMediaParameterWithParentheses(CssValueNode node) {
    // TODO(fbenz): Try to avoid the special handling of this case.
    buffer.append('(');
    buffer.append(node.getValue());
    buffer.append(')');
  }

  @Override
  public void leaveMediaRule(CssMediaRuleNode node) {
    buffer.append('}');
  }

  @Override
  public boolean enterPageRule(CssPageRuleNode node) {
    buffer.append(node.getType().toString());
    buffer.append(' ');
    // TODO(fbenz): There are only two parameters possible ('bla:left') that
    // come with no whitespace in between. So it would be better to have a
    // single node (maybe a selector).
    for (CssValueNode param : node.getParameters()) {
      buffer.append(param.getValue());
    }
    buffer.deleteLastCharIfCharIs(' ');
    return true;
  }

  @Override
  public boolean enterPageSelector(CssPageSelectorNode node) {
    buffer.append(node.getType().toString());
    for (CssValueNode param : node.getParameters()) {
      buffer.append(' ');
      buffer.append(param.getValue());
    }
    return true;
  }

  @Override
  public boolean enterFontFace(CssFontFaceNode node) {
    buffer.append(node.getType().toString());
    return true;
  }

  @Override
  public boolean enterSelector(CssSelectorNode selector) {
    String name = selector.getSelectorName();
    if (name != null) {
      buffer.append(name);
    }
    return true;
  }

  @Override
  public void leaveSelector(CssSelectorNode selector) {
    buffer.append(',');
  }

  @Override
  public boolean enterClassSelector(CssClassSelectorNode node) {
    appendRefiner(node);
    return true;
  }

  @Override
  public boolean enterIdSelector(CssIdSelectorNode node) {
    appendRefiner(node);
    return true;
  }

  @Override
  public boolean enterPseudoClass(CssPseudoClassNode node) {
    buffer.append(node.getPrefix());
    buffer.append(node.getRefinerName());
    switch (node.getFunctionType()) {
      case NTH:
        buffer.append(node.getArgument().replace(" ", ""));
        buffer.append(')');
        break;
      case LANG:
        buffer.append(node.getArgument());
        buffer.append(')');
        break;
    }
    return true;
  }

  @Override
  public void leavePseudoClass(CssPseudoClassNode node) {
    if (node.getFunctionType() == FunctionType.NOT) {
      buffer.deleteLastCharIfCharIs(',');
      buffer.append(')');
    }
  }

  @Override
  public boolean enterPseudoElement(CssPseudoElementNode node) {
    appendRefiner(node);
    return true;
  }

  @Override
  public boolean enterAttributeSelector(CssAttributeSelectorNode node) {
    buffer.append(node.getPrefix());
    buffer.append(node.getAttributeName());
    buffer.append(node.getMatchSymbol());
    buffer.append(node.getValue());
    buffer.append(node.getSuffix());
    return true;
  }

  /**
   * Appends the representation of a class selector, an id selector,
   * or a pseudo-element.
   */
  private void appendRefiner(CssRefinerNode node) {
    buffer.append(node.getPrefix());
    buffer.append(node.getRefinerName());
  }

  @Override
  public boolean enterCombinator(CssCombinatorNode combinator) {
    if (combinator != null) {
      buffer.append(combinator.getCombinatorType().getCanonicalName());
    }
    return true;
  }

  @Override
  public void leaveCombinator(CssCombinatorNode combinator) {
    buffer.deleteLastCharIfCharIs(',');
  }

  @Override
  public void leaveSelectorBlock(CssSelectorListNode node) {
    buffer.deleteLastCharIfCharIs(',');
  }

  @Override
  public boolean enterDeclarationBlock(CssDeclarationBlockNode block) {
    buffer.append('{');
    return true;
  }

  @Override
  public void leaveDeclarationBlock(CssDeclarationBlockNode block) {
    buffer.deleteLastCharIfCharIs(';');
    buffer.append('}');
  }

  @Override
  public boolean enterBlock(CssBlockNode block) {
    if (block.getParent() instanceof CssUnknownAtRuleNode
        || block.getParent() instanceof CssMediaRuleNode) {
      buffer.append('{');
    }
    return true;
  }

  @Override
  public boolean enterDeclaration(CssDeclarationNode declaration) {
    if (declaration.hasStarHack()) {
      buffer.append('*');
    }
    buffer.append(declaration.getPropertyName().getValue());
    buffer.append(':');
    return true;
  }

  @Override
  public void leaveDeclaration(CssDeclarationNode declaration) {
    buffer.deleteLastCharIfCharIs(' ');
    buffer.append(';');
  }

  @Override
  public void leaveCompositeValueNode(CssCompositeValueNode node) {
    buffer.deleteLastCharIfCharIs(' ');
    if (node.getParent() instanceof CssPropertyValueNode) {
      buffer.append(' ');
    }
  }

  @Override
  public boolean enterValueNode(CssValueNode node) {
    if (node instanceof CssPriorityNode) {
      buffer.deleteLastCharIfCharIs(' ');
    }
    appendValueNode(node);
    return true;
  }

  @Override
  public void leaveValueNode(CssValueNode node) {
    if (node.getParent() instanceof CssPropertyValueNode) {
      buffer.append(' ');
    }
  }

  @Override
  public boolean enterCompositeValueNodeOperator(CssCompositeValueNode parent) {
    buffer.deleteLastCharIfCharIs(' ');
    buffer.append(parent.getOperator().getOperatorName());
    return true;
  }

  @Override
  public boolean enterFunctionNode(CssFunctionNode node) {
    buffer.append(node.getFunctionName());
    buffer.append('(');
    return true;
  }

  @Override
  public void leaveFunctionNode(CssFunctionNode node) {
    buffer.deleteLastCharIfCharIs(' ');
    buffer.append(") ");
  }

  // We need to handle both standard function calls separated by
  // commas, and IE-specific calls, with = as a separator as in
  // alpha(opacity=70) or even with spaces as separators as in
  // rect(0 0 0 0). In all cases, the separators each appear explicitly
  // as arguments.
  private static final ImmutableSet ARGUMENT_SEPARATORS =
      ImmutableSet.of(",", "=", " ");

  @Override
  public boolean enterArgumentNode(CssValueNode node) {
    if (ARGUMENT_SEPARATORS.contains(node.toString())) {
      // If the previous argument was a function node, then it has a
      // trailing space that needs to be removed.
      buffer.deleteLastCharIfCharIs(' ');
    }
    appendValueNode(node);
    return true;
  }

  @Override
  public boolean enterConditionalBlock(CssConditionalBlockNode node) {
    visitController.stopVisit();
    // TODO(oana): Collect these messages with a proper message collector.
    logger.warning("Conditional block should not be "
        + "present: " + node.toString()
        + ((node.getSourceCodeLocation() != null) ?
            "@" + node.getSourceCodeLocation().getLineNumber() : ""));
    return true;
  }

  @Override
  public boolean enterUnknownAtRule(CssUnknownAtRuleNode node) {
    buffer.append('@').append(node.getName().toString());
    if (node.getParameters().size() > 0) {
      buffer.append(' ');
    }
    return true;
  }

  @Override
  public boolean enterMediaTypeListDelimiter(
      CssNodesListNode node) {
    buffer.append(' ');
    return true;
  }

  @Override
  public void leaveUnknownAtRule(CssUnknownAtRuleNode node) {
    if (node.getType().hasBlock()) {
      if (!(node.getBlock() instanceof CssDeclarationBlockNode)) {
        buffer.append('}');
      }
    } else {
      buffer.append(';');
    }
  }

  @Override
  public boolean enterKeyframesRule(CssKeyframesNode node) {
    buffer.append('@').append(node.getName().toString());
    for (CssValueNode param : node.getParameters()) {
      buffer.append(' ');
      buffer.append(param.getValue());
    }
    if (node.getType().hasBlock()) {
      buffer.append('{');
    }
    return true;
  }

  @Override
  public void leaveKeyframesRule(CssKeyframesNode node) {
    if (node.getType().hasBlock()) {
      buffer.append('}');
    } else {
      buffer.append(';');
    }
  }

  @Override
  public boolean enterKey(CssKeyNode node) {
    String value = node.getKeyValue();
    if (value != null) {
      buffer.append(value);
    }
    return true;
  }

  @Override
  public void leaveKey(CssKeyNode key) {
    buffer.append(',');
  }

  @Override
  public void leaveKeyBlock(CssKeyListNode block) {
    buffer.deleteLastCharIfCharIs(',');
  }

  /**
   * Returns the CSS compacted printed output.
   */
  public String getCompactPrintedString() {
    return compactedPrintedString;
  }

  @Override
  public void runPass() {
    resetBuffer();
    visitController.startVisit(this);
    compactedPrintedString = getOutputBuffer();
  }

  /**
   * Appends the given value node to the buffer.
   *
   * 

Subclasses can modify this to provide a different * serialization for particular types of value nodes. * * @param node the node to append */ protected void appendValueNode(CssValueNode node) { if (node instanceof CssCompositeValueNode) { return; } if (node instanceof CssBooleanExpressionNode && node.getParent() instanceof CssMediaRuleNode) { appendMediaParameterWithParentheses(node); return; } if (node instanceof CssStringNode) { CssStringNode s = (CssStringNode) node; buffer.append(s.toString(CssStringNode.HTML_ESCAPER)); return; } if (node instanceof CssNumericNode) { // TODO(user): Consider introducing // void CssNode.appendTo(Appendable sb) // or // CharSequence CssNode.asCharSequence CssNumericNode n = (CssNumericNode) node; buffer.append(n.getNumericPart()); buffer.append(n.getUnit()); return; } buffer.append(node.toString()); } public static String printCompactly(CssNode n) { CompactPrinter p = new CompactPrinter(n); p.runPass(); return p.getCompactPrintedString().trim(); } public static String printCompactly(CssTree t) { return printCompactly(t.getRoot()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy