/* * 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.testing; import com.google.common.css.compiler.ast.CssBooleanExpressionNode; import com.google.common.css.compiler.ast.CssCommentNode; 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.CssImportRuleNode; import com.google.common.css.compiler.ast.CssMediaRuleNode; import com.google.common.css.compiler.ast.CssNode; import com.google.common.css.compiler.ast.CssPropertyValueNode; 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.CssTree; import com.google.common.css.compiler.ast.CssTreeVisitor; import com.google.common.css.compiler.ast.CssValueNode; import com.google.common.css.compiler.ast.VisitController; import com.google.common.css.compiler.passes.CodeBuffer; import com.google.common.css.compiler.passes.CompactPrinter; import com.google.common.css.compiler.passes.CompactPrintingVisitor; /** * A {@link CompactPrinter} extension that adds square brackets to make the * construction of the AST more obvious. * * <p> This printer is useful to ensure that the AST is constructed in the * expected way. Thus, it avoids that simply returning the source code is * regarded as valid. * * @author fbenz@google.com (Florian Benz) */ public class AstPrinter extends CompactPrinter { /** * Uses the AstPrinter to print the tree. */ public static String print(CssTree tree) { AstPrinter astPrinter = new AstPrinter(tree); astPrinter.runPass(); return astPrinter.getCompactPrintedString(); } public AstPrinter(CssTree tree) { super(tree); } @Override protected CssTreeVisitor createVisitor(VisitController visitController, CodeBuffer buffer) { return new AstPrintingVisitor(visitController, buffer); } private static class AstPrintingVisitor extends CompactPrintingVisitor { public AstPrintingVisitor(VisitController visitController, CodeBuffer buffer) { super(visitController, buffer); } @Override public boolean enterTree(CssRootNode root) { buffer.append('['); return super.enterTree(root); } @Override public void leaveTree(CssRootNode root) { super.leaveTree(root); buffer.append(']'); } @Override public boolean enterDeclarationBlock(CssDeclarationBlockNode block) { super.enterDeclarationBlock(block); buffer.append('['); return true; } @Override public void leaveDeclarationBlock(CssDeclarationBlockNode block) { buffer.append(']'); super.leaveDeclarationBlock(block); } @Override public boolean enterSelectorBlock(CssSelectorListNode block) { buffer.append('['); return super.enterSelectorBlock(block); } @Override public void leaveSelectorBlock(CssSelectorListNode block) { super.leaveSelectorBlock(block); buffer.append(']'); } @Override public boolean enterPropertyValue(CssPropertyValueNode propertyValue) { buffer.append('['); return super.enterPropertyValue(propertyValue); } @Override public void leavePropertyValue(CssPropertyValueNode propertyValue) { super.leavePropertyValue(propertyValue); buffer.deleteLastCharIfCharIs(' '); buffer.append(']'); } @Override public boolean enterCompositeValueNode(CssCompositeValueNode value) { buffer.append('['); return super.enterCompositeValueNode(value); } @Override public void leaveCompositeValueNode(CssCompositeValueNode value) { super.leaveCompositeValueNode(value); buffer.deleteLastCharIfCharIs(' '); buffer.append(']'); } @Override public boolean enterValueNode(CssValueNode value) { buffer.append('['); return super.enterValueNode(value); } @Override public void leaveValueNode(CssValueNode value) { super.leaveValueNode(value); buffer.deleteLastCharIfCharIs(' '); buffer.append(']'); } @Override public boolean enterDefinition(CssDefinitionNode node) { buffer.append("@def "); buffer.append(node.getName()); buffer.append(" ["); return true; } @Override public void leaveDefinition(CssDefinitionNode node) { buffer.deleteLastCharIfCharIs(' '); buffer.append("];"); } @Override public boolean enterConditionalBlock(CssConditionalBlockNode node) { return true; } @Override public boolean enterConditionalRule(CssConditionalRuleNode node) { buffer.append(node.getType()); buffer.append('['); for (CssValueNode value : node.getParameters()) { appendValue(value); buffer.append(' '); } buffer.deleteLastCharIfCharIs(' '); buffer.append(']').append('{'); return true; } @Override public void leaveConditionalRule(CssConditionalRuleNode node) { buffer.append('}'); } @Override public boolean enterRuleset(CssRulesetNode node) { appendComments(node); return super.enterRuleset(node); } @Override public boolean enterDeclaration(CssDeclarationNode node) { appendComments(node); return super.enterDeclaration(node); } @Override public boolean enterMediaRule(CssMediaRuleNode node) { appendComments(node); return super.enterMediaRule(node); } @Override public boolean enterImportRule(CssImportRuleNode node) { appendComments(node); return super.enterImportRule(node); } /** * This method appends the representation of a value but does not cover all cases at the moment. */ private void appendValue(CssValueNode node) { if (node instanceof CssBooleanExpressionNode) { appendBooleanExpression((CssBooleanExpressionNode) node); } else { buffer.append(node.getValue()); } } private void appendBooleanExpression(CssBooleanExpressionNode node) { if (!node.getType().isOperator()) { buffer.append(node.getValue()); } else if (node.getType().isBinaryOperator()) { appendBooleanChildExpression(node, node.getLeft()); buffer.append(' '); buffer.append(node.getType().getOperatorString()); buffer.append(' '); appendBooleanChildExpression(node, node.getRight()); } else if (node.getType().isUnaryOperator()) { buffer.append(node.getType().getOperatorString()); appendBooleanChildExpression(node, node.getLeft()); } } private void appendBooleanChildExpression( CssBooleanExpressionNode node, CssBooleanExpressionNode child) { if (child.getType().getPriority() >= node.getType().getPriority()) { appendBooleanExpression(child); } else { buffer.append('('); appendBooleanExpression(child); buffer.append(')'); } } private void appendComments(CssNode node) { for (CssCommentNode c : node.getComments()) { buffer.append('['); buffer.append(c.getValue()); buffer.append(']'); } } } }