/* * 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.ast; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.css.SourceCodeLocation; import com.google.common.css.compiler.ast.CssSelectorNode.Specificity; /** * A {@link CssRefinerNode} implementation that represents a pseudo-class. * For example: {@code :visited}, {@code :nth-child(2n)} * * @author fbenz@google.com (Florian Benz) */ public class CssPseudoClassNode extends CssRefinerNode { private static final ImmutableSet<String> COMPATIBILITY_PSEUDO_ELEMENTS = ImmutableSet.of("first-line", "first-letter", "before", "after"); /** Indicates if the refiner is a function and if so which one. */ private FunctionType functionType; /** Argument of the function if the refiner is a function else empty */ private String argument; /** Selector for the ':not' function */ private CssSelectorNode notSelector; /** * Determines if the pseudo-class is a function and if so which one. */ public enum FunctionType { NONE, LANG, // TODO(fbenz): make the arguments for nth-functions real nodes NTH, NOT; // No support for 'any' at the moment because it is relatively new and // subject to changes (e.g. introduced in Gecko 2). } /** * Constructor for non-function pseudo-classes like {@code :link} or * {@code :visited} */ public CssPseudoClassNode(String name, SourceCodeLocation sourceCodeLocation) { this(FunctionType.NONE, name, null /* argument */, null /* notSelector */, sourceCodeLocation); } /** * Constructor for function pseudo-classes except the negation function like * {@code :lang(en)} or {@code :nth-child(2n)}. */ public CssPseudoClassNode(FunctionType functionType, String name, String argument, SourceCodeLocation sourceCodeLocation) { this(functionType, name, argument, null /* notSelector */, sourceCodeLocation); Preconditions.checkArgument( functionType != FunctionType.NOT && functionType != FunctionType.NONE); } /** * Constructor for the negation function pseudo-class. */ public CssPseudoClassNode(String name, CssSelectorNode notSelector, SourceCodeLocation sourceCodeLocation) { this(FunctionType.NOT, name, null /* argument */, notSelector, sourceCodeLocation); } public CssPseudoClassNode(CssPseudoClassNode node) { this(node.functionType, node.refinerName, node.argument, node.notSelector, node.getSourceCodeLocation()); } private CssPseudoClassNode(FunctionType functionType, String name, String argument, CssSelectorNode notSelector, SourceCodeLocation sourceCodeLocation) { super(Refiner.PSEUDO_CLASS, name, sourceCodeLocation); this.functionType = functionType; this.argument = argument; this.notSelector = notSelector; } @Override public CssPseudoClassNode deepCopy() { return new CssPseudoClassNode(this); } public FunctionType getFunctionType() { return functionType; } public String getArgument() { return argument; } public void setArgument(String argument) { this.argument = argument; } public CssSelectorNode getNotSelector() { return notSelector; } /** * Returns the specificity of this pseudo-class or an old pseudo-element * starting with a single colon. * * <p>The :: notation for pseudo-elements is introduced in CSS level 3 in * order to establish a discrimination between pseudo-classes and * pseudo-elements. * "For compatibility with existing style sheets, user agents must also * accept the previous one-colon notation for pseudo-elements introduced * in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and * :after). This compatibility is not allowed for the new pseudo-elements * introduced in this specification." * http://www.w3.org/TR/css3-selectors/#target-pseudo * * <p>The negation pseudo-class itself does not influence the specificity. * However, selectors inside the negation pseudo-class are counted like * any other. Thus, {@code red.level} has the same specificity as * {@code *:not(red.level)}. */ @Override public Specificity getSpecificity() { if (COMPATIBILITY_PSEUDO_ELEMENTS.contains(refinerName)) { // d++ (d = the number of type selectors and pseudo-elements in // the selector) return new Specificity(0, 0, 1); } else { if (functionType == FunctionType.NOT) { return notSelector.getSpecificity(); } else { // c++ (c = the number of class selectors, attributes selectors, // and pseudo-classes in the selector) return new Specificity(0, 1, 0); } } } @Override public String toString() { // TODO(fbenz): toString should not be used to print a node. However, // some tests rely on it. This should be fixed. StringBuilder sb = new StringBuilder(); sb.append(refinerType.getPrefix()); sb.append(refinerName); switch (functionType) { case NONE: sb.append(refinerType.getSuffix()); break; case LANG: sb.append(argument); break; case NTH: sb.append(argument); break; case NOT: sb.append(notSelector.toString()); break; } if (functionType != FunctionType.NONE) { sb.append(")"); } return sb.toString(); } }