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

com.google.common.css.compiler.passes.PrettyPrinter 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 static com.google.common.base.Preconditions.checkArgument;

import com.google.common.css.compiler.ast.CssAtRuleNode.Type;
import com.google.common.css.compiler.ast.CssAttributeSelectorNode;
import com.google.common.css.compiler.ast.CssBlockNode;
import com.google.common.css.compiler.ast.CssClassSelectorNode;
import com.google.common.css.compiler.ast.CssCombinatorNode;
import com.google.common.css.compiler.ast.CssCommentNode;
import com.google.common.css.compiler.ast.CssCompilerPass;
import com.google.common.css.compiler.ast.CssComponentNode;
import com.google.common.css.compiler.ast.CssCompositeValueNode;
import com.google.common.css.compiler.ast.CssConditionalBlockNode;
import com.google.common.css.compiler.ast.CssConditionalRuleNode;
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.CssKeyframeRulesetNode;
import com.google.common.css.compiler.ast.CssKeyframesNode;
import com.google.common.css.compiler.ast.CssMediaRuleNode;
import com.google.common.css.compiler.ast.CssMixinDefinitionNode;
import com.google.common.css.compiler.ast.CssMixinNode;
import com.google.common.css.compiler.ast.CssNode;
import com.google.common.css.compiler.ast.CssPageRuleNode;
import com.google.common.css.compiler.ast.CssPageSelectorNode;
import com.google.common.css.compiler.ast.CssPropertyValueNode;
import com.google.common.css.compiler.ast.CssProvideNode;
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.CssRequireNode;
import com.google.common.css.compiler.ast.CssRootNode;
import com.google.common.css.compiler.ast.CssRulesetNode;
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 com.google.common.css.compiler.ast.VisitController;

import javax.annotation.Nullable;

/**
 * A pretty-printer for {@link CssTree} instances. This is work in progress.
 * Look at PrettyPrinterTest to see what's supported.
 *
 * @author [email protected] (Martin Kretzschmar)
 * @author [email protected] (Oana Florescu)
 * @author [email protected] (Florian Benz)
 */
public class PrettyPrinter extends CodePrinter implements CssCompilerPass {
  private String prettyPrintedString = null;
  private String indent = "";
  private boolean stripQuotes = false;
  private boolean preserveComments = false;

  public PrettyPrinter(VisitController visitController,
      @Nullable CodeBuffer buffer,
      @Nullable GssSourceMapGenerator generator) {
    super(visitController, buffer, generator);
  }

  public PrettyPrinter(VisitController visitController,
      GssSourceMapGenerator generator) {
    this(visitController, null /* buffer */, generator);
  }

  public PrettyPrinter(VisitController visitController) {
    this(visitController, null /* buffer */, null /* generator */);
  }

  /**
   * Whether to strip quotes from certain values. This facilitates
   * tests that want to compare trees.
   */
  public void setStripQuotes(boolean stripQuotes) {
    this.stripQuotes = stripQuotes;
  }

  /**
   * Whether comments in the CSS nodes are preserved in the pretty printed
   * output.
   * 

Note: Comments layout is not guaranteed, since detailed position * information in the input files is not preserved by the parser. Line breaks * are added after every comment with current identation as best effort.

*/ public PrettyPrinter setPreserveComments(boolean preserve) { this.preserveComments = preserve; return this; } @Override public boolean enterImportRule(CssImportRuleNode node) { maybeAppendComments(node); buffer.append(node.getType().toString()); for (CssValueNode param : node.getParameters()) { buffer.append(' '); // TODO(user): teach visit controllers to explore this subtree // rather than leaving it to each pass to figure things out. if (param instanceof CssStringNode) { CssStringNode n = (CssStringNode) param; buffer.append(n.toString(CssStringNode.SHORT_ESCAPER)); } else { buffer.append(param.getValue()); } } return true; } @Override public void leaveImportRule(CssImportRuleNode node) { buffer.append(';').startNewLine(); } @Override public boolean enterMediaRule(CssMediaRuleNode node) { maybeAppendComments(node); buffer.append(node.getType().toString()); if (node.getParameters().size() > 0 || (node.getType().hasBlock() && node.getBlock() != null)) { buffer.append(' '); } return true; } @Override public void leaveMediaRule(CssMediaRuleNode node) {} @Override public boolean enterPageRule(CssPageRuleNode node) { maybeAppendComments(node); buffer.append(node.getType().toString()); buffer.append(' '); for (CssValueNode param : node.getParameters()) { buffer.append(param.getValue()); } if (node.getParametersCount() > 0) { buffer.append(' '); } return true; } @Override public boolean enterPageSelector(CssPageSelectorNode node) { maybeAppendComments(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) { maybeAppendComments(node); buffer.append(node.getType().toString()); return true; } @Override public boolean enterDefinition(CssDefinitionNode node) { maybeAppendComments(node); buffer.append(indent); buffer.append(node.getType()); buffer.append(' '); buffer.append(node.getName()); // Add a space to separate it from next value. buffer.append(' '); return true; } @Override public void leaveDefinition(CssDefinitionNode node) { // Remove trailing space after last value. buffer.deleteLastCharIfCharIs(' '); buffer.append(';').startNewLine(); } @Override public boolean enterRuleset(CssRulesetNode ruleset) { maybeAppendComments(ruleset); buffer.append(indent); return true; } @Override public boolean enterKeyframeRuleset(CssKeyframeRulesetNode ruleset) { maybeAppendComments(ruleset); buffer.append(indent); return true; } // TODO(mkretzschmar): make DeclarationBlock subclass of Block and eliminate // this. @Override public boolean enterDeclarationBlock(CssDeclarationBlockNode block) { maybeAppendComments(block); buffer.deleteLastCharIfCharIs(' '); buffer.append(" {").startNewLine(); indent += " "; return true; } @Override public void leaveDeclarationBlock(CssDeclarationBlockNode block) { indent = indent.substring(0, indent.length() - 2); buffer.append(indent); buffer.append('}').startNewLine(); } @Override public boolean enterBlock(CssBlockNode block) { maybeAppendComments(block); if (block.getParent() instanceof CssUnknownAtRuleNode || block.getParent() instanceof CssMediaRuleNode) { buffer.append('{').startNewLine(); indent += " "; } return true; } @Override public void leaveBlock(CssBlockNode block) { if (block.getParent() instanceof CssMediaRuleNode) { buffer.append('}').startNewLine(); indent = indent.substring(0, indent.length() - 2); } } @Override public boolean enterDeclaration(CssDeclarationNode declaration) { maybeAppendComments(declaration); buffer.append(indent); if (declaration.hasStarHack()) { buffer.append('*'); } buffer.append(declaration.getPropertyName().getValue()); buffer.append(": "); return true; } @Override public void leaveDeclaration(CssDeclarationNode declaration) { buffer.deleteLastCharIfCharIs(' '); buffer.append(';').startNewLine(); } @Override public boolean enterValueNode(CssValueNode node) { maybeAppendComments(node); checkArgument(!(node instanceof CssCompositeValueNode)); String v = node.toString(); if (stripQuotes && node.getParent() instanceof CssDefinitionNode) { v = maybeStripQuotes(v); } buffer.append(v); // NOTE(flan): When visiting function arguments, we don't want to add extra // spaces because they are already in the arguments list if they are // required. Yes, this sucks. if (!node.inFunArgs()) { buffer.append(' '); } return true; } @Override public boolean enterCompositeValueNodeOperator(CssCompositeValueNode parent) { maybeAppendComments(parent); buffer.append(parent.getOperator().getOperatorName()); if (!parent.inFunArgs()) { buffer.append(' '); } return true; } @Override public boolean enterFunctionNode(CssFunctionNode node) { maybeAppendComments(node); buffer.append(node.getFunctionName()); buffer.append('('); return true; } @Override public void leaveFunctionNode(CssFunctionNode node) { buffer.deleteLastCharIfCharIs(' '); buffer.append(") "); } @Override public boolean enterArgumentNode(CssValueNode node) { maybeAppendComments(node); String v = node.toString(); if (stripQuotes && node.getParent().getParent() instanceof CssFunctionNode && ((CssFunctionNode) node.getParent().getParent()).getFunctionName() .equals("url")) { v = maybeStripQuotes(v); } buffer.append(v); return !(node instanceof CssCompositeValueNode); } @Override public boolean enterSelector(CssSelectorNode selector) { maybeAppendComments(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) { maybeAppendComments(node); appendRefiner(node); return true; } @Override public boolean enterIdSelector(CssIdSelectorNode node) { maybeAppendComments(node); appendRefiner(node); return true; } @Override public boolean enterPseudoClass(CssPseudoClassNode node) { maybeAppendComments(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.deleteEndingIfEndingIs(", "); buffer.append(')'); } } @Override public boolean enterPseudoElement(CssPseudoElementNode node) { maybeAppendComments(node); appendRefiner(node); return true; } @Override public boolean enterAttributeSelector(CssAttributeSelectorNode node) { maybeAppendComments(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) { maybeAppendComments(combinator); buffer.append(combinator.getCombinatorType().getCanonicalName()); } return true; } @Override public void leaveCombinator(CssCombinatorNode combinator) { buffer.deleteEndingIfEndingIs(", "); } @Override public void leaveSelectorBlock(CssSelectorListNode node) { buffer.deleteEndingIfEndingIs(", "); } @Override public void leaveConditionalBlock(CssConditionalBlockNode block) { buffer.startNewLine(); } @Override public boolean enterConditionalRule(CssConditionalRuleNode node) { maybeAppendComments(node); if (node.getType() != Type.IF) { buffer.append(' '); } else { buffer.append(indent); } buffer.append(node.getType()); if (node.getParametersCount() > 0) { buffer.append(' '); boolean firstParameter = true; for (CssValueNode value : node.getParameters()) { if (!firstParameter) { buffer.append(' '); } firstParameter = false; buffer.append(value.toString()); } } buffer.append(" {").startNewLine(); indent += " "; return true; } @Override public void leaveConditionalRule(CssConditionalRuleNode node) { indent = indent.substring(0, indent.length() - 2); buffer.append(indent); buffer.append('}'); } @Override public boolean enterUnknownAtRule(CssUnknownAtRuleNode node) { maybeAppendComments(node); buffer.append(indent); buffer.append('@').append(node.getName().toString()); if (node.getParameters().size() > 0 || (node.getType().hasBlock() && node.getBlock() != null)) { buffer.append(' '); } return true; } @Override public void leaveUnknownAtRule(CssUnknownAtRuleNode node) { if (node.getType().hasBlock()) { if (!(node.getBlock() instanceof CssDeclarationBlockNode)) { indent = indent.substring(0, indent.length() - 2); buffer.append(indent); buffer.append('}').startNewLine(); } } else { buffer.deleteLastCharIfCharIs(' '); buffer.append(';').startNewLine(); } } @Override public boolean enterKeyframesRule(CssKeyframesNode node) { maybeAppendComments(node); buffer.append(indent); buffer.append('@').append(node.getName().toString()); for (CssValueNode param : node.getParameters()) { buffer.append(' '); buffer.append(param.getValue()); } if (node.getType().hasBlock()) { buffer.append(" {").startNewLine(); indent += " "; } return true; } @Override public void leaveKeyframesRule(CssKeyframesNode node) { if (node.getType().hasBlock()) { indent = indent.substring(0, indent.length() - 2); buffer.append(indent); buffer.append('}').startNewLine(); } else { buffer.append(';').startNewLine(); } } @Override public boolean enterKey(CssKeyNode key) { maybeAppendComments(key); String value = key.getKeyValue(); if (value != null) { buffer.append(value); } return true; } @Override public void leaveKey(CssKeyNode key) { buffer.append(", "); } @Override public void leaveKeyBlock(CssKeyListNode node) { buffer.deleteEndingIfEndingIs(", "); } @Override public boolean enterProvideNode(CssProvideNode node) { maybeAppendComments(node); return true; } @Override public boolean enterRequireNode(CssRequireNode node) { maybeAppendComments(node); return true; } @Override public boolean enterComponent(CssComponentNode node) { maybeAppendComments(node); return true; } @Override public boolean enterMixin(CssMixinNode node) { maybeAppendComments(node); return true; } @Override public boolean enterConditionalBlock(CssConditionalBlockNode block) { maybeAppendComments(block); return true; } @Override public boolean enterMixinDefinition(CssMixinDefinitionNode node) { maybeAppendComments(node); return true; } @Override public boolean enterCompositeValueNode(CssCompositeValueNode value) { maybeAppendComments(value); return true; } @Override public boolean enterPropertyValue(CssPropertyValueNode propertyValue) { maybeAppendComments(propertyValue); return true; } @Override public boolean enterTree(CssRootNode root) { maybeAppendComments(root); return true; } private void maybeAppendComments(CssNode node) { if (preserveComments && !node.getComments().isEmpty()) { for (CssCommentNode c : node.getComments()) { buffer.append(indent); buffer.append(c.getValue()); buffer.startNewLine(); } } } public String getPrettyPrintedString() { return prettyPrintedString; } @Override public void runPass() { resetBuffer(); visitController.startVisit(this); prettyPrintedString = getOutputBuffer(); } private String maybeStripQuotes(String v) { if (v.startsWith("'") || v.startsWith("\"")) { assert(v.endsWith(v.substring(0, 1))); v = v.substring(1, v.length() - 1); } return v; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy