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

com.google.common.css.compiler.passes.CompactPrintingVisitor 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: 1.8.0
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.*;
import com.google.common.css.compiler.ast.CssPseudoClassNode.FunctionType;

import java.util.List;
import java.util.logging.Logger;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * 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 CompactPrintingVisitor extends DefaultTreeVisitor {

    private static final Logger logger = Logger.getLogger(CompactPrintingVisitor.class.getName());

    protected final VisitController visitController;
    protected final CodeBuffer buffer;

    private final String compactedPrintedString = null;

    public CompactPrintingVisitor(VisitController visitController, CodeBuffer buffer) {
        this.visitController = checkNotNull(visitController);
        this.buffer = checkNotNull(buffer);
    }

    @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().isEmpty()) {
            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 enterCharSet(CssCharSetNode node) {
        buffer.append(node.getType().toString());
        List parameters = node.getParameters();
        if (!parameters.isEmpty()) {
            buffer.append(parameters.get(0));
        }
        return true;
    }

    @Override
    public void leaveCharSet(CssCharSetNode node) {
        buffer.append(';');
    }

    @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());
        if (node.getFunctionType() == FunctionType.NTH) {
            buffer.append(node.getArgument().replace(" ", ""));
            buffer.append(')');
        } else if (node.getFunctionType() == FunctionType.LANG) {
            buffer.append(node.getArgument());
            buffer.append(')');
        }
        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 boolean enterCompositeValueNode(CssCompositeValueNode node) {
        if (node.hasParenthesis()) {
            buffer.append('(');
        }
        return true;
    }

    @Override
    public void leaveCompositeValueNode(CssCompositeValueNode node) {
        buffer.deleteLastCharIfCharIs(' ');
        if (node.getParent() instanceof CssPropertyValueNode) {
            buffer.append(' ');
        }
        if (node.hasParenthesis()) {
            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().isEmpty()) {
            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;
    }

    /**
     * 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()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy