/* * 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.common.css.compiler.ast; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import java.util.List; import javax.annotation.Nullable; /** * A node corresponding to an at-rule such as {@code @if} or {@code @media}. * */ public abstract class CssAtRuleNode extends CssNodesListNode<CssValueNode> { private final Type type; private CssLiteralNode name; private CssAbstractBlockNode block; /** * The Types of at-rules we (should/could) know about. */ // TODO(user): Add support for all of these. Support generally means // overriding this class and adding constraints, both during parsing and // during tree construction. We can also add methods and types in order // to have richer information on specific rules. Until a specific rule is // supported, it can be represented using CssUnknownAtRuleNode. public enum Type { CHARSET("charset", false /* hasBlock */), IMPORT("import", false /* hasBlock */), NAMESPACE("namespace", false /* hasBlock */), MEDIA("media"), PAGE("page"), FONT_FACE("font-face"), // The recognized page margin box types in CSS 3. // See http://www.w3.org/TR/css3-page/#margin-boxes TOP_LEFT_CORNER("top-left-corner"), TOP_LEFT("top-left"), TOP_CENTER("top-center"), TOP_RIGHT("top-right"), TOP_RIGHT_CORNER("top-right-corner"), LEFT_TOP("left-top"), LEFT_MIDDLE("left-middle"), LEFT_BOTTOM("left-bottom"), RIGHT_TOP("right-top"), RIGHT_MIDDLE("right-middle"), RIGHT_BOTTOM("right-bottom"), BOTTOM_LEFT_CORNER("bottom-left-corner"), BOTTOM_LEFT("bottom-left"), BOTTOM_CENTER("bottom-center"), BOTTOM_RIGHT("bottom-right"), BOTTOM_RIGHT_CORNER("bottom-right-corner"), // Non-standard extension. // See http://disruptive-innovations.com/zoo/cssvariables/ VARIABLES("variables"), // The Google extensions to CSS, referred to as GSS. DEF("def", false /* hasBlock */), IF("if"), ELSEIF("elseif"), ELSE("else"), // Loops extension. FOR("for"), // GSS components. ABSTRACT_COMPONENT("abstract_component"), COMPONENT("component"), // GSS mixins DEFMIXIN("defmixin"), MIXIN("mixin", false /* hasBlock */), // GSS dependency management PROVIDE("provide", false /* hasBlock */), REQUIRE("require", false /* hasBlock */), // An at rule of which we know nothing about. We can safely assume its // canonical name is its "name" literal converted to lower case and with no // escapes. UNKNOWN(null, false /* hasBlock */), // Same as above, but we allow it to have a block node. UNKNOWN_BLOCK(null, true /* hasBlock */); private final String canonicalName; private final boolean hasBlock; /** * Most at rules require a block. */ private Type(@Nullable String canonicalName) { this(canonicalName, true /* hasBlock */); } private Type(@Nullable String canonicalName, boolean hasBlock) { this.canonicalName = canonicalName; this.hasBlock = hasBlock; } public String getCanonicalName() { return canonicalName; } public boolean hasBlock() { return hasBlock; } public boolean isConditional() { return this == IF || this == ELSEIF || this == ELSE; } /** * For debugging only. */ @Override public String toString() { return "@" + getCanonicalName(); } } /** * Constructor for an AtRule node. * * @param type Type of rule * @param name Name of rule * @param block The block child of this rule * @param comments The comments associated to this rule */ CssAtRuleNode(Type type, CssLiteralNode name, @Nullable CssAbstractBlockNode block, @Nullable List<CssCommentNode> comments) { super(false, comments); Preconditions.checkNotNull(type); Preconditions.checkNotNull(name); Preconditions.checkArgument(type.hasBlock() || block == null); this.type = type; this.name = name; this.block = block; // TODO(user): We could check that the canonicalized name matches the // type that was passed. The same for setName(). becomeParentForNode(this.name); becomeParentForNode(this.block); } /** * Constructor for an AtRule node. * * @param type Type of rule * @param name Name of rule * @param block The block child of this rule */ CssAtRuleNode(Type type, CssLiteralNode name, @Nullable CssAbstractBlockNode block) { this(type, name, block, null); } /** * Constructor for an AtRule node. * * @param type Type of rule * @param name Name of rule */ CssAtRuleNode(Type type, CssLiteralNode name) { this(type, name, type.hasBlock() ? new CssBlockNode() : null); } /** * Constructor for an AtRule node. * * @param type Type of rule * @param name Name of rule * @param comments The comments associated to this rule */ CssAtRuleNode(Type type, CssLiteralNode name, List<CssCommentNode> comments) { this(type, name, type.hasBlock() ? new CssBlockNode() : null, comments); } CssAtRuleNode(CssAtRuleNode node) { this(node.getType(), node.getName().deepCopy(), node.getBlock() != null ? node.getBlock().deepCopy() : null, copyNodes(node.getComments())); setParameters(copyNodes(node.getParameters())); setSourceCodeLocation(node.getSourceCodeLocation()); } public static <N extends CssNode> List<N> copyNodes(List<N> nodes) { List<N> list = Lists.newArrayList(); for (N node : nodes) { @SuppressWarnings("unchecked") N copy = (N) node.deepCopy(); list.add(copy); } return list; } public Type getType() { return type; } public CssLiteralNode getName() { return name; } void setName(CssLiteralNode name) { Preconditions.checkNotNull(name); removeAsParentOfNode(this.name); this.name = name; becomeParentForNode(this.name); } public List<CssValueNode> getParameters() { return this.children; } public int getParametersCount() { return numChildren(); } public void setParameters(List<CssValueNode> parameters) { setChildren(parameters); } /** * Subclasses should override {@code getBlock} to return a more specific * subclass of {@link CssAbstractBlockNode}. */ protected CssAbstractBlockNode getBlock() { return block; } void setBlock(@Nullable CssAbstractBlockNode block) { Preconditions.checkArgument(block.isEnclosedWithBraces(), "Only blocks that are enclosed with braces are valid for @-rules."); removeAsParentOfNode(this.block); this.block = block; becomeParentForNode(this.block); } /** * For debugging only. */ @Override public String toString() { String output = ""; if (!getComments().isEmpty()) { output = getComments().toString(); } output += getType().toString() + getParameters().toString(); if (getBlock() != null) { output += "{" + getBlock().toString() + "}"; } return output; } }