| /* |
| * Copyright 2008 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.gwt.resources.css; |
| |
| import com.google.gwt.dev.util.TextOutput; |
| import com.google.gwt.resources.css.ast.Context; |
| import com.google.gwt.resources.css.ast.CssDef; |
| import com.google.gwt.resources.css.ast.CssEval; |
| import com.google.gwt.resources.css.ast.CssExternalSelectors; |
| import com.google.gwt.resources.css.ast.CssFontFace; |
| import com.google.gwt.resources.css.ast.CssIf; |
| import com.google.gwt.resources.css.ast.CssMediaRule; |
| import com.google.gwt.resources.css.ast.CssNoFlip; |
| import com.google.gwt.resources.css.ast.CssNode; |
| import com.google.gwt.resources.css.ast.CssPageRule; |
| import com.google.gwt.resources.css.ast.CssProperty; |
| import com.google.gwt.resources.css.ast.CssRule; |
| import com.google.gwt.resources.css.ast.CssSelector; |
| import com.google.gwt.resources.css.ast.CssSprite; |
| import com.google.gwt.resources.css.ast.CssSubstitution; |
| import com.google.gwt.resources.css.ast.CssUnknownAtRule; |
| import com.google.gwt.resources.css.ast.CssUrl; |
| import com.google.gwt.resources.css.ast.CssVisitor; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.SortedMap; |
| import java.util.TreeMap; |
| |
| /** |
| * Generates a static CSS template string and provides information on where to |
| * inject dynamic expressions. |
| */ |
| public class CssGenerationVisitor extends CssVisitor { |
| private boolean needsOpenBrace; |
| private boolean needsComma; |
| |
| private final TextOutput out; |
| |
| private final boolean substituteDots; |
| private final SortedMap<Integer, List<CssSubstitution>> substitutionPositions = new TreeMap<Integer, List<CssSubstitution>>(); |
| |
| /** |
| * Constructor. |
| * |
| * @param out the output hondler |
| */ |
| public CssGenerationVisitor(TextOutput out) { |
| this(out, false); |
| } |
| |
| /** |
| * Constructor for producing an abbreviated form of the template for use with |
| * {@link CssNode#toString()}. |
| * |
| * @param out the output handler |
| * @param substituteDots if <code>true</code> locations in the text output |
| * where expression substitutions would normally occur are replaced |
| * with a textual placeholder |
| */ |
| public CssGenerationVisitor(TextOutput out, boolean substituteDots) { |
| this.out = out; |
| this.substituteDots = substituteDots; |
| } |
| |
| @Override |
| public void endVisit(CssFontFace x, Context ctx) { |
| closeBrace(); |
| } |
| |
| @Override |
| public void endVisit(CssIf x, Context ctx) { |
| // Match up an explanatory comment |
| out.indentOut(); |
| out.printOpt("/* } */"); |
| out.newlineOpt(); |
| } |
| |
| @Override |
| public void endVisit(CssMediaRule x, Context ctx) { |
| out.indentOut(); |
| out.print("}"); |
| out.newlineOpt(); |
| } |
| |
| @Override |
| public void endVisit(CssNoFlip x, Context ctx) { |
| out.printOpt("/*} @noflip */"); |
| out.newlineOpt(); |
| } |
| |
| @Override |
| public void endVisit(CssPageRule x, Context ctx) { |
| out.indentOut(); |
| out.print("}"); |
| out.newlineOpt(); |
| } |
| |
| @Override |
| public void endVisit(CssRule x, Context ctx) { |
| if (!x.getProperties().isEmpty()) { |
| // Don't print empty rule blocks |
| closeBrace(); |
| } |
| } |
| |
| @Override |
| public void endVisit(CssUnknownAtRule x, Context ctx) { |
| out.printOpt("/* Unknown at-rule */\n"); |
| out.print(x.getRule()); |
| } |
| |
| public SortedMap<Integer, List<CssSubstitution>> getSubstitutionPositions() { |
| return substitutionPositions; |
| } |
| |
| @Override |
| public boolean visit(CssFontFace x, Context ctx) { |
| out.print("@font-face"); |
| openBrace(); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(CssDef x, Context ctx) { |
| // These are not valid CSS |
| out.printOpt("/* CssDef */"); |
| out.newlineOpt(); |
| return false; |
| } |
| |
| @Override |
| public boolean visit(CssEval x, Context ctx) { |
| // These are not valid CSS |
| out.printOpt("/* CssEval */"); |
| out.newlineOpt(); |
| return false; |
| } |
| |
| @Override |
| public boolean visit(CssExternalSelectors x, Context ctx) { |
| // These are not valid CSS |
| out.printOpt("/* @external"); |
| for (String className : x.getClasses()) { |
| out.printOpt(" "); |
| out.printOpt(className); |
| } |
| out.printOpt("; */"); |
| out.newlineOpt(); |
| return false; |
| } |
| |
| @Override |
| public boolean visit(CssIf x, Context ctx) { |
| // Record where the contents of the if block should be inserted |
| StringBuilder expr = new StringBuilder("/* @if "); |
| if (x.getExpression() != null) { |
| expr.append(x.getExpression()).append(" "); |
| } else { |
| expr.append(x.getPropertyName()).append(" "); |
| for (String v : x.getPropertyValues()) { |
| expr.append(v).append(" "); |
| } |
| } |
| expr.append("{ */"); |
| out.printOpt(expr.toString()); |
| out.newlineOpt(); |
| out.indentIn(); |
| addSubstitition(x); |
| return false; |
| } |
| |
| @Override |
| public boolean visit(CssMediaRule x, Context ctx) { |
| out.print("@media"); |
| boolean isFirst = true; |
| for (String m : x.getMedias()) { |
| if (isFirst) { |
| out.print(" "); |
| isFirst = false; |
| } else { |
| comma(); |
| } |
| out.print(m); |
| } |
| spaceOpt(); |
| out.print("{"); |
| out.newlineOpt(); |
| out.indentIn(); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(CssNoFlip x, Context ctx) { |
| out.printOpt("/*@noflip { */"); |
| out.newlineOpt(); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(CssPageRule x, Context ctx) { |
| out.print("@page"); |
| if (x.getPseudoPage() != null) { |
| out.print(" :"); |
| out.print(x.getPseudoPage()); |
| } |
| spaceOpt(); |
| out.print("{"); |
| out.newlineOpt(); |
| out.indentIn(); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(CssProperty x, Context ctx) { |
| if (needsOpenBrace) { |
| openBrace(); |
| needsOpenBrace = false; |
| } |
| |
| out.print(x.getName()); |
| colon(); |
| addSubstitition(x); |
| |
| if (x.isImportant()) { |
| important(); |
| } |
| |
| semi(); |
| |
| return true; |
| } |
| |
| @Override |
| public boolean visit(CssRule x, Context ctx) { |
| if (x.getProperties().isEmpty()) { |
| // Don't print empty rule blocks |
| return false; |
| } |
| |
| needsOpenBrace = true; |
| needsComma = false; |
| return true; |
| } |
| |
| @Override |
| public boolean visit(CssSelector x, Context ctx) { |
| if (needsComma) { |
| comma(); |
| } |
| needsComma = true; |
| out.print(x.getSelector()); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(CssSprite x, Context ctx) { |
| // These are not valid CSS |
| out.printOpt("/* CssSprite */"); |
| out.newlineOpt(); |
| addSubstitition(x); |
| return false; |
| } |
| |
| @Override |
| public boolean visit(CssUrl x, Context ctx) { |
| // These are not valid CSS |
| out.printOpt("/* CssUrl */"); |
| out.newlineOpt(); |
| return false; |
| } |
| |
| private <T extends CssNode & CssSubstitution> void addSubstitition(T node) { |
| if (substituteDots) { |
| out.printOpt("....."); |
| out.newlineOpt(); |
| } else { |
| int position = out.toString().length(); |
| if (substitutionPositions.containsKey(position)) { |
| substitutionPositions.get(position).add(node); |
| } else { |
| List<CssSubstitution> nodes = new ArrayList<CssSubstitution>(); |
| nodes.add(node); |
| substitutionPositions.put(position, nodes); |
| } |
| } |
| } |
| |
| private void closeBrace() { |
| out.indentOut(); |
| out.print('}'); |
| out.newlineOpt(); |
| } |
| |
| private void colon() { |
| spaceOpt(); |
| out.print(':'); |
| spaceOpt(); |
| } |
| |
| private void comma() { |
| out.print(','); |
| spaceOpt(); |
| } |
| |
| private void important() { |
| out.print(" !important"); |
| } |
| |
| private void openBrace() { |
| spaceOpt(); |
| out.print('{'); |
| out.newlineOpt(); |
| out.indentIn(); |
| } |
| |
| private void semi() { |
| out.print(';'); |
| out.newlineOpt(); |
| } |
| |
| private void spaceOpt() { |
| out.printOpt(' '); |
| } |
| } |