Package com.google.caja.plugin

Source Code of com.google.caja.plugin.CssValidatorTest

// Copyright (C) 2006 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.caja.plugin;

import com.google.caja.lang.css.CssSchema;
import com.google.caja.lang.html.HtmlSchema;
import com.google.caja.parser.AncestorChain;
import com.google.caja.parser.MutableParseTreeNode;
import com.google.caja.parser.ParseTreeNode;
import com.google.caja.parser.css.CssTree;
import com.google.caja.reporting.Message;
import com.google.caja.reporting.MessageContext;
import com.google.caja.reporting.MessageLevel;
import com.google.caja.reporting.MessageQueue;
import com.google.caja.reporting.SimpleMessageQueue;
import com.google.caja.util.CajaTestCase;
import com.google.caja.util.Lists;
import com.google.caja.util.MoreAsserts;
import com.google.caja.util.SyntheticAttributeKey;

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;

/**
* @author mikesamuel@gmail.com (Mike Samuel)
*/
public final class CssValidatorTest extends CajaTestCase {
  public final void testValidateColor() throws Exception {
    runTest("a { color: blue }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : a\n"
            + "    PropertyDeclaration\n"
            + "      Property : color\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=color::color\n"
            + "          IdentLiteral : blue");
    runTest("a { COLOR: Blue }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : a\n"
            + "    PropertyDeclaration\n"
            + "      Property : color\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=color::color\n"
            + "          IdentLiteral : Blue");
    runTest("a { color: #00f }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : a\n"
            + "    PropertyDeclaration\n"
            + "      Property : color\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=COLOR"
                        + " ; cssPropertyPart=color::color\n"
            + "          HashLiteral : #00f");
    runTest("a { color: #0000ff }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : a\n"
            + "    PropertyDeclaration\n"
            + "      Property : color\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=COLOR"
                        + " ; cssPropertyPart=color::color\n"
            + "          HashLiteral : #0000ff");
    runTest("a { color: rgb(0, 0, 255) }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : a\n"
            + "    PropertyDeclaration\n"
            + "      Property : color\n"
            + "      Expr\n"
            + "        Term\n"
            + "          FunctionCall : rgb\n"
            + "            Expr\n"
            + "              Term ; cssPropertyPartType=INTEGER"
                              + " ; cssPropertyPart=color::color::red\n"
            + "                QuantityLiteral : 0\n"
            + "              Operation : COMMA\n"
            + "              Term ; cssPropertyPartType=INTEGER"
                              + " ; cssPropertyPart=color::color::green\n"
            + "                QuantityLiteral : 0\n"
            + "              Operation : COMMA\n"
            + "              Term ; cssPropertyPartType=INTEGER"
                              + " ; cssPropertyPart=color::color::blue\n"
            + "                QuantityLiteral : 255");
    runTest("a { color: rgb(0%, 0%, 100%) }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : a\n"
            + "    PropertyDeclaration\n"
            + "      Property : color\n"
            + "      Expr\n"
            + "        Term\n"
            + "          FunctionCall : rgb\n"
            + "            Expr\n"
            + "              Term ; cssPropertyPartType=PERCENTAGE"
                              + " ; cssPropertyPart=color::color::red\n"
            + "                QuantityLiteral : 0%\n"
            + "              Operation : COMMA\n"
            + "              Term ; cssPropertyPartType=PERCENTAGE"
                              + " ; cssPropertyPart=color::color::green\n"
            + "                QuantityLiteral : 0%\n"
            + "              Operation : COMMA\n"
            + "              Term ; cssPropertyPartType=PERCENTAGE"
                              + " ; cssPropertyPart=color::color::blue\n"
            + "                QuantityLiteral : 100%");
    runTest("a { color: rgba(0, 0, 255, 50%) }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : a\n"
            + "    PropertyDeclaration\n"
            + "      Property : color\n"
            + "      Expr\n"
            + "        Term\n"
            + "          FunctionCall : rgba\n"
            + "            Expr\n"
            + "              Term ; cssPropertyPartType=INTEGER"
                              + " ; cssPropertyPart=color::color::red\n"
            + "                QuantityLiteral : 0\n"
            + "              Operation : COMMA\n"
            + "              Term ; cssPropertyPartType=INTEGER"
                              + " ; cssPropertyPart=color::color::green\n"
            + "                QuantityLiteral : 0\n"
            + "              Operation : COMMA\n"
            + "              Term ; cssPropertyPartType=INTEGER"
                              + " ; cssPropertyPart=color::color::blue\n"
            + "                QuantityLiteral : 255\n"
            + "              Operation : COMMA\n"
            + "              Term ; cssPropertyPartType=PERCENTAGE"
                              + " ; cssPropertyPart=color::color::alpha\n"
            + "                QuantityLiteral : 50%");
    runTest("a { color: rgba(0, 0, 255, 0.5) }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : a\n"
            + "    PropertyDeclaration\n"
            + "      Property : color\n"
            + "      Expr\n"
            + "        Term\n"
            + "          FunctionCall : rgba\n"
            + "            Expr\n"
            + "              Term ; cssPropertyPartType=INTEGER"
                              + " ; cssPropertyPart=color::color::red\n"
            + "                QuantityLiteral : 0\n"
            + "              Operation : COMMA\n"
            + "              Term ; cssPropertyPartType=INTEGER"
                              + " ; cssPropertyPart=color::color::green\n"
            + "                QuantityLiteral : 0\n"
            + "              Operation : COMMA\n"
            + "              Term ; cssPropertyPartType=INTEGER"
                              + " ; cssPropertyPart=color::color::blue\n"
            + "                QuantityLiteral : 255\n"
            + "              Operation : COMMA\n"
            + "              Term ; cssPropertyPartType=NUMBER"
                              + " ; cssPropertyPart=color::color::alpha\n"
            + "                QuantityLiteral : 0.5");
  }

  public final void testValidateFont() throws Exception {
    // special names
    runTest("p, dl { font: caption; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : font\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font\n"
            + "          IdentLiteral : caption\n"
            + "    EmptyDeclaration");
    fails("bogus, dl { font: caption; }");
    fails("p, bogus { font: caption; }");
    fails("p[bogus] { font: caption; }");
    runTest("p { font: waption; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : font-family\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : waption\n"
            + "    EmptyDeclaration",
            // This message is misleading.  There is not likely a font named
            // waption, but we don't make a change without saying anything.
            "WARNING: specialized CSS property font to font-family");
    runTest("p, dl { font: status-bar; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : font\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font\n"
            + "          IdentLiteral : status-bar\n"
            + "    EmptyDeclaration");
    runTest("p, dl { font: status-bar caption; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    EmptyDeclaration",
            "WARNING: css property font has bad value: "
            + "status-bar  ==><==  caption");

    // size and family
    runTest("p, dl { font: 12pt Arial; }"// absolute
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : font\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=font-size\n"
            + "          QuantityLiteral : 12pt\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Arial\n"
            + "    EmptyDeclaration");
    warns("p, dl { font: -12pt Arial; }");
    runTest("p, dl { font: -12pt url(Arial); }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    EmptyDeclaration",
            "WARNING: css property font has bad value:"
            + " -12pt  ==>url('Arial')<==");
    runTest("p, dl { font: twelve Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : font-family\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : twelve\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Arial\n"
            + "    EmptyDeclaration",
            // A similarly misleading but correct message
            "WARNING: specialized CSS property font to font-family");
    runTest("p, dl { font: 150% Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : font\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=PERCENTAGE"
                        + " ; cssPropertyPart=font-size\n"
            + "          QuantityLiteral : 150%\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Arial\n"
            + "    EmptyDeclaration");
    runTest("p, dl { font: 150Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    EmptyDeclaration",
            "WARNING: css property font has bad value:"
            + " ==>150Arial<==");
    runTest("p, dl { font: 150/Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    EmptyDeclaration",
            "WARNING: css property font has bad value:"
            + " 150 /  ==>Arial<==");
    runTest("p, dl { font: medium Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : font\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-size::absolute-size\n"
            + "          IdentLiteral : medium\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Arial\n"
            + "    EmptyDeclaration");
    runTest("p, dl { font: medium; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : font-size\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-size::absolute-size\n"
            + "          IdentLiteral : medium\n"
            + "    EmptyDeclaration",
            "WARNING: specialized CSS property font to font-size");

    // style weight size family
    runTest("p, dl { font: italic bolder 150% Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : font\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-style\n"
            + "          IdentLiteral : italic\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-weight\n"
            + "          IdentLiteral : bolder\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=PERCENTAGE"
                        + " ; cssPropertyPart=font-size\n"
            + "          QuantityLiteral : 150%\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Arial\n"
            + "    EmptyDeclaration");
    runTest("p, dl { font: italic bolderer 150% Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    EmptyDeclaration",
            "WARNING: css property font has bad value:"
            + " italic  ==>bolderer<==  150% Arial");
    runTest("p, dl { font: italix bolder 150% Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    EmptyDeclaration",
            "WARNING: css property font has bad value:"
            + " ==>italix<==  bolder 150% Arial");

    // font-size also matches by previous terms
    runTest("p, dl { font: inherit \"Arial\"; }"// special
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : font\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-size\n"
            + "          IdentLiteral : inherit\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=STRING"
                        + " ; cssPropertyPart=font-family::family-name\n"
            + "          StringLiteral : Arial\n"
            + "    EmptyDeclaration");
    runTest("p, dl { font: inherit; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    EmptyDeclaration",
            "WARNING: css property font has bad value: inherit");

    // weight size family
    runTest("p, dl { font: 800 150% Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : font\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-weight\n"
            + "          QuantityLiteral : 800\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=PERCENTAGE"
                        + " ; cssPropertyPart=font-size\n"
            + "          QuantityLiteral : 150%\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Arial\n"
            + "    EmptyDeclaration");
    runTest("p, dl { font: 800; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : font-size\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=font-size\n"
            + "          QuantityLiteral : 800\n"
            + "    EmptyDeclaration",
            "WARNING: specialized CSS property font to font-size");

    // variant weight family
    runTest("p, dl { font: normal 800 150% Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : font\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-style\n"
            + "          IdentLiteral : normal\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-weight\n"
            + "          QuantityLiteral : 800\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=PERCENTAGE"
                        + " ; cssPropertyPart=font-size\n"
            + "          QuantityLiteral : 150%\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Arial\n"
            + "    EmptyDeclaration");
    runTest("p, dl { font: abnormal 150% Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    EmptyDeclaration",
            "WARNING: css property font has bad value:"
            + " ==>abnormal<==  150% Arial");

    // with line-height following /
    runTest("p, dl { font: normal 800 150%/175% Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : font\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-style\n"
            + "          IdentLiteral : normal\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-weight\n"
            + "          QuantityLiteral : 800\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=PERCENTAGE"
                        + " ; cssPropertyPart=font-size\n"
            + "          QuantityLiteral : 150%\n"
            + "        Operation : DIV\n"
            + "        Term ; cssPropertyPartType=PERCENTAGE"
                        + " ; cssPropertyPart=line-height\n"
            + "          QuantityLiteral : 175%\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Arial\n"
            + "    EmptyDeclaration");
    runTest("p, dl { font: abnormal 150%/175% Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    EmptyDeclaration",
            "WARNING: css property font has bad value:"
            + " ==>abnormal<==  150% / 175% Arial");
    runTest("p, dl { font: normal 800 150%/ Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    EmptyDeclaration",
            "WARNING: css property font has bad value:"
            + " normal 800 150% /  ==>Arial<==");
    runTest("p, dl { font: normal 800 150%/17.5 Arial; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : font\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-style\n"
            + "          IdentLiteral : normal\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-weight\n"
            + "          QuantityLiteral : 800\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=PERCENTAGE"
                        + " ; cssPropertyPart=font-size\n"
            + "          QuantityLiteral : 150%\n"
            + "        Operation : DIV\n"
            + "        Term ; cssPropertyPartType=NUMBER"
                        + " ; cssPropertyPart=line-height\n"
            + "          QuantityLiteral : 17.5\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Arial\n"
            + "    EmptyDeclaration");
    warns("p, dl { font: normal 800 150%/-175% Arial; }");
    warns("p, dl { font: normal 800 150%/-17.5 Arial; }");

    // make sure the first three inherits match different parts
    runTest("p { font: inherit inherit inherit Arial; }",
            "StyleSheet\n" +
            "  RuleSet\n" +
            "    Selector\n" +
            "      SimpleSelector\n" +
            "        IdentLiteral : p\n" +
            "    PropertyDeclaration\n" +
            "      Property : font\n" +
            "      Expr\n" +
            "        Term ; cssPropertyPartType=IDENT"
                      + " ; cssPropertyPart=font-style\n" +
            "          IdentLiteral : inherit\n" +
            "        Operation : NONE\n" +
            "        Term ; cssPropertyPartType=IDENT"
                      + " ; cssPropertyPart=font-variant\n" +
            "          IdentLiteral : inherit\n" +
            "        Operation : NONE\n" +
            "        Term ; cssPropertyPartType=IDENT"
                      + " ; cssPropertyPart=font-size\n" +
            "          IdentLiteral : inherit\n" +
            "        Operation : NONE\n" +
            "        Term ; cssPropertyPartType=LOOSE_WORD"
                      + " ; cssPropertyPart=font-family::family-name"
                                         + "::loose-quotable-words\n" +
            "          IdentLiteral : Arial\n" +
            "    EmptyDeclaration");
  }

  public final void testValidateUnquotedFamilyNames() throws Exception {
    runTest("p { font-family: Arial Black }",
            "StyleSheet\n" +
            "  RuleSet\n" +
            "    Selector\n" +
            "      SimpleSelector\n" +
            "        IdentLiteral : p\n" +
            "    PropertyDeclaration\n" +
            "      Property : font-family\n" +
            "      Expr\n" +
            "        Term ; cssPropertyPartType=LOOSE_WORD"
                      + " ; cssPropertyPart=font-family::family-name"
                                         + "::loose-quotable-words\n" +
            "          IdentLiteral : Arial\n" +
            "        Operation : NONE\n" +
            "        Term ; cssPropertyPartType=LOOSE_WORD"
                      + " ; cssPropertyPart=font-family::family-name"
                                         + "::loose-quotable-words\n" +
            "          IdentLiteral : Black"
            );
  }

  public final void testValidateBorder() throws Exception {
    runTest("p, dl { border: inherit; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : dl\n"
            + "    PropertyDeclaration\n"
            + "      Property : border\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=border\n"
            + "          IdentLiteral : inherit\n"
            + "    EmptyDeclaration");
    runTest("p { border: 2px }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : border\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=border::border-width\n"
            + "          QuantityLiteral : 2px");
    runTest("p { border: 2px solid black}",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : border\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=border::border-width\n"
            + "          QuantityLiteral : 2px\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=border::border-style\n"
            + "          IdentLiteral : solid\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=border::color\n"
            + "          IdentLiteral : black");
    runTest("p {border: solid black; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : border\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=border::border-style\n"
            + "          IdentLiteral : solid\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=border::color\n"
            + "          IdentLiteral : black\n"
            + "    EmptyDeclaration");
    runTest("p {border-top: solid black; }",
        "StyleSheet\n"
        + "  RuleSet\n"
        + "    Selector\n"
        + "      SimpleSelector\n"
        + "        IdentLiteral : p\n"
        + "    PropertyDeclaration\n"
        + "      Property : border-top\n"
        + "      Expr\n"
        + "        Term ; cssPropertyPartType=IDENT"
                    + " ; cssPropertyPart=border-top::border-style\n"
        + "          IdentLiteral : solid\n"
        + "        Operation : NONE\n"
        + "        Term ; cssPropertyPartType=IDENT"
                    + " ; cssPropertyPart=border-top-color::color\n"
        + "          IdentLiteral : black\n"
        + "    EmptyDeclaration");
    runTest("p { border:solid black 1em}",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : border\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=border::border-style\n"
            + "          IdentLiteral : solid\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=border::color\n"
            + "          IdentLiteral : black\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=border::border-width\n"
            + "          QuantityLiteral : 1em");
    runTest("p { border: 14px transparent }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : border\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=border::border-width\n"
            + "          QuantityLiteral : 14px\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=border\n"
            + "          IdentLiteral : transparent");
  }

  public final void testClip() throws Exception {
    runTest("p { clip: rect(10px, 10px, 10px, auto) }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : clip\n"
            + "      Expr\n"
            + "        Term\n"
            + "          FunctionCall : rect\n"
            + "            Expr\n"
            + "              Term ; cssPropertyPartType=LENGTH"
                              + " ; cssPropertyPart=clip::shape::top\n"
            + "                QuantityLiteral : 10px\n"
            + "              Operation : COMMA\n"
            + "              Term ; cssPropertyPartType=LENGTH"
                              + " ; cssPropertyPart=clip::shape::right\n"
            + "                QuantityLiteral : 10px\n"
            + "              Operation : COMMA\n"
            + "              Term ; cssPropertyPartType=LENGTH"
                              + " ; cssPropertyPart=clip::shape::bottom\n"
            + "                QuantityLiteral : 10px\n"
            + "              Operation : COMMA\n"
            + "              Term ; cssPropertyPartType=IDENT"
                              + " ; cssPropertyPart=clip::shape::left\n"
            + "                IdentLiteral : auto");
  }

  public final void testContent() throws Exception {
    // Tests a string that is not a URL.
    runTest(""
            + "#body:before { content: ' ' }"
            + "#body:after { content: '.' }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdLiteral : #body\n"
            + "        Pseudo\n"
            + "          IdentLiteral : before\n"
            + "    PropertyDeclaration\n"
            + "      Property : content\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=STRING"
                        + " ; cssPropertyPart=content\n"
            + "          StringLiteral :  \n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdLiteral : #body\n"
            + "        Pseudo\n"
            + "          IdentLiteral : after\n"
            + "    PropertyDeclaration\n"
            + "      Property : content\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=STRING"
                        + " ; cssPropertyPart=content\n"
            + "          StringLiteral : .\n");
  }

  public final void testBackground() throws Exception {
    runTest("p { background: url( /images/smiley-face.jpg ) no-repeat }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : background\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=URI"
                        + " ; cssPropertyPart=background::bg-image::image\n"
            + "          UriLiteral : /images/smiley-face.jpg\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background::repeat-style\n"
            + "          IdentLiteral : no-repeat");
    runTest("p { background: url( /images/smiley-face.jpg ) no-repeat }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : background\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=URI"
                        + " ; cssPropertyPart=background::bg-image::image\n"
            + "          UriLiteral : /images/smiley-face.jpg\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background::repeat-style\n"
            + "          IdentLiteral : no-repeat");
    runTest("p { background-image: '/images/smiley-face.jpg' }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : background-image\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=URI"
                        + " ; cssPropertyPart=background-image::bg-image::image\n"
            + "          StringLiteral : /images/smiley-face.jpg");
    runTest("p { background:#F7F7F7 url(/images/foo.gif) no-repeat scroll"
            + " left top; }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : background\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=COLOR"
                        + " ; cssPropertyPart=background-color::color\n"
            + "          HashLiteral : #F7F7F7\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=URI"
                        + " ; cssPropertyPart=background::bg-image::image\n"
            + "          UriLiteral : /images/foo.gif\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background::repeat-style\n"
            + "          IdentLiteral : no-repeat\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background::attachment\n"
            + "          IdentLiteral : scroll\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background::bg-position\n"
            + "          IdentLiteral : left\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background::bg-position\n"
            + "          IdentLiteral : top\n"
            + "    EmptyDeclaration\n"
            );
    runTest("p { background:#FFEBE8 none repeat scroll 0% }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : background\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=COLOR"
                        + " ; cssPropertyPart=background-color::color\n"
            + "          HashLiteral : #FFEBE8\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background::bg-image\n"
            + "          IdentLiteral : none\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background::repeat-style\n"
            + "          IdentLiteral : repeat\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background::attachment\n"
            + "          IdentLiteral : scroll\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=PERCENTAGE"
                        + " ; cssPropertyPart=background::bg-position\n"
            + "          QuantityLiteral : 0%\n"
            );
    runTest("p { background: transparent url(/foo.gif) no-repeat top right }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : background\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background-color\n"
            + "          IdentLiteral : transparent\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=URI"
                        + " ; cssPropertyPart=background::bg-image::image\n"
            + "          UriLiteral : /foo.gif\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background::repeat-style\n"
            + "          IdentLiteral : no-repeat\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background::bg-position\n"
            + "          IdentLiteral : top\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background::bg-position\n"
            + "          IdentLiteral : right\n"
            );
    runTest(
        "p { background: url( /images/smiley-face.jpg ) no-repeat, blue }",
        "StyleSheet\n"
        + "  RuleSet\n"
        + "    Selector\n"
        + "      SimpleSelector\n"
        + "        IdentLiteral : p\n"
        + "    PropertyDeclaration\n"
        + "      Property : background\n"
        + "      Expr\n"
        + "        Term ; cssPropertyPartType=URI"
                    + " ; cssPropertyPart=background::bg-image::image\n"
        + "          UriLiteral : /images/smiley-face.jpg\n"
        + "        Operation : NONE\n"
        + "        Term ; cssPropertyPartType=IDENT"
                    + " ; cssPropertyPart=background::repeat-style\n"
        + "          IdentLiteral : no-repeat\n"
        + "        Operation : COMMA\n"
        + "        Term ; cssPropertyPartType=IDENT"
                    + " ; cssPropertyPart=background-color::color\n"
        + "          IdentLiteral : blue"
        );
  }

  public final void testBackgroundPosition() throws Exception {
    // TODO(mikesamuel): We could break the position rule into multiple
    // subrules so that the part for "right" becomes background-position::x-pos,
    // and the part for "top" becomes background-position::y-pos.
    runTest("p { background-position: right top }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : background-position\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background-position::bg-position\n"
            + "          IdentLiteral : right\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background-position::bg-position\n"
            + "          IdentLiteral : top\n"
            );
    runTest("p { background-position: top center }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : background-position\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background-position::bg-position\n"
            + "          IdentLiteral : top\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background-position::bg-position\n"
            + "          IdentLiteral : center\n"
            );
    runTest("p { background-position: center }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : background-position\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background-position::bg-position\n"
            + "          IdentLiteral : center\n"
            );
    runTest("p { background-position: bottom }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : background-position\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=background-position::bg-position\n"
            + "          IdentLiteral : bottom\n"
            );
  }

  public final void testPositionSubstitution() throws Exception {
    runTest("p { left: ${3}px }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : left\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=left\n"
            + "          Substitution : ${3}px");
  }

  public final void testColorSubstitution() throws Exception {
    runTest("p { background: ${shade << 16 | shade << 8 | shade} }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : background\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=COLOR"
                        + " ; cssPropertyPart=background-color::color\n"
            + "          Substitution : ${shade << 16 | shade << 8 | shade}");
  }

  public final void testUriSubstitution() throws Exception {
    runTest("p { background: ${imageName + '.png'}uri }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : background\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=URI"
                        + " ; cssPropertyPart=background::bg-image::image\n"
            + "          Substitution : ${imageName + '.png'}uri");
    runTest("p { background-image: ${imageName + '.png'} }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : background-image\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=URI"
                        + " ; cssPropertyPart=background-image::bg-image::image\n"
            + "          Substitution : ${imageName + '.png'}");
  }

  public final void testFontFamily() throws Exception {
    runTest("a { font: 12pt Times New Roman, Times, 'Times Old Roman', serif }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : a\n"
            + "    PropertyDeclaration\n"
            + "      Property : font\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=font-size\n"
            + "          QuantityLiteral : 12pt\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Times\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : New\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Roman\n"
            + "        Operation : COMMA\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Times\n"
            + "        Operation : COMMA\n"
            + "        Term ; cssPropertyPartType=STRING"
                        + " ; cssPropertyPart=font-family::family-name\n"
            + "          StringLiteral : Times Old Roman\n"
            + "        Operation : COMMA\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-family::generic-family\n"
            + "          IdentLiteral : serif\n"
            );
    runTest("p { font-family: Georgia, \"Times New Roman\", Times, serif }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : font-family\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Georgia\n"
            + "        Operation : COMMA\n"
            + "        Term ; cssPropertyPartType=STRING"
                        + " ; cssPropertyPart=font-family::family-name\n"
            + "          StringLiteral : Times New Roman\n"
            + "        Operation : COMMA\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Times\n"
            + "        Operation : COMMA\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-family::generic-family\n"
            + "          IdentLiteral : serif\n"
            );
    runTest("p { font-family: Times New Roman }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : font-family\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Times\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : New\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Roman\n"
            );
    runTest("p { font-family: Heisi  Minco W3, serif }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : font-family\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Heisi\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Minco\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : W3\n"
            + "        Operation : COMMA\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-family::generic-family\n"
            + "          IdentLiteral : serif\n"
            );
    runTest(("p { font-family: 'Helvetica Neue Light', 'HelveticaNeue-Light',"
             + "  'Helvetica Neue', Calibri, Helvetica, Arial }"),
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : font-family\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=STRING"
                        + " ; cssPropertyPart=font-family::family-name\n"
            + "          StringLiteral : Helvetica Neue Light\n"
            + "        Operation : COMMA\n"
            + "        Term ; cssPropertyPartType=STRING"
                        + " ; cssPropertyPart=font-family::family-name\n"
            + "          StringLiteral : HelveticaNeue-Light\n"
            + "        Operation : COMMA\n"
            + "        Term ; cssPropertyPartType=STRING"
                        + " ; cssPropertyPart=font-family::family-name\n"
            + "          StringLiteral : Helvetica Neue\n"
            + "        Operation : COMMA\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Calibri\n"
            + "        Operation : COMMA\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Helvetica\n"
            + "        Operation : COMMA\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                                           + "::loose-quotable-words\n"
            + "          IdentLiteral : Arial\n"
            );
  }

  public final void testUnitlessLengths() throws Exception {
    runTest("p { padding: 4 10 0 10 }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : padding\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=padding::padding-width\n"
            + "          QuantityLiteral : 4\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=padding::padding-width\n"
            + "          QuantityLiteral : 10\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=padding::padding-width\n"
            + "          QuantityLiteral : 0\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=padding::padding-width\n"
            + "          QuantityLiteral : 10\n"
            );
    runTest("p { border: .125in 6 }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : border\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=border::border-width\n"
            + "          QuantityLiteral : .125in\n"
            + "        Operation : NONE\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=border::border-width\n"
            + "          QuantityLiteral : 6\n"
            );
  }

  public final void testNegativeSpacing() throws Exception {
    runTest("p { letter-spacing: -4px; word-spacing: -2px }",
        "StyleSheet\n"
        + "  RuleSet\n"
        + "    Selector\n"
        + "      SimpleSelector\n"
        + "        IdentLiteral : p\n"
        + "    PropertyDeclaration\n"
        + "      Property : letter-spacing\n"
        + "      Expr\n"
        + "        Term : NEGATION ; cssPropertyPartType=LENGTH"
                    + " ; cssPropertyPart=letter-spacing\n"
        + "          QuantityLiteral : 4px\n"
        + "    PropertyDeclaration\n"
        + "      Property : word-spacing\n"
        + "      Expr\n"
        + "        Term : NEGATION ; cssPropertyPartType=LENGTH"
                    + " ; cssPropertyPart=word-spacing\n"
        + "          QuantityLiteral : 2px\n"
        );
  }

  public final void testOpacity() throws Exception {
    runTest("img {\n"
            + "  opacity: 0.5;\n"
            + "  filter:alpha(opacity=50)\n"
            + "         progid:DXImageTransform.Microsoft.Alpha(opacity=50) }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : img\n"
            + "    PropertyDeclaration\n"
            + "      Property : opacity\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=NUMBER"
                        + " ; cssPropertyPart=opacity::alphavalue\n"
            + "          QuantityLiteral : 0.5\n"
            + "    PropertyDeclaration\n"
            + "      Property : filter\n"
            + "      Expr\n"
            + "        Term\n"
            + "          FunctionCall : alpha\n"
            + "            Expr\n"
            + "              Term ; cssPropertyPartType=IDENT"
                              + " ; cssPropertyPart=filter::ie-filter-opacity\n"
            + "                IdentLiteral : opacity\n"
            + "              Operation : EQUAL\n"
            + "              Term ; cssPropertyPartType=NUMBER"
                              + " ; cssPropertyPart=filter::ie-filter-opacity\n"
            + "                QuantityLiteral : 50\n"
            + "        Operation : NONE\n"
            + "        Term\n"
            + "          ProgId : dximagetransform.microsoft.alpha\n"
            + "            ProgIdAttribute : opacity\n"
            + "              Term ; cssPropertyPartType=NUMBER"
                              + " ; cssPropertyPart=filter::prog-id"
                                   + "::prog-id-alpha::filter-opacity\n"
            + "                QuantityLiteral : 50\n"
            );
  }

  public final void testProgId() throws Exception {
    runTest(
        "img {\n"
        + "  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader("
        + "      src='howdy', sizingMethod='scale') }",
        "StyleSheet\n"
        + "  RuleSet\n"
        + "    Selector\n"
        + "      SimpleSelector\n"
        + "        IdentLiteral : img\n"
        + "    PropertyDeclaration\n"
        + "      Property : filter\n"
        + "      Expr\n"
        + "        Term\n"
        + "          ProgId : dximagetransform.microsoft.alphaimageloader\n"
        + "            ProgIdAttribute : src\n"
        + "              Term ; cssPropertyPartType=URI"
                          + " ; cssPropertyPart=filter::prog-id"
                              + "::prog-id-alpha-image-loader::page-url\n"
        + "                StringLiteral : howdy\n"
        + "            ProgIdAttribute : sizingmethod\n"
        + "              Term ; cssPropertyPartType=STRING"
                          + " ; cssPropertyPart=filter::prog-id"
                              + "::prog-id-alpha-image-loader::sizing-method\n"
        + "                StringLiteral : scale\n"
        );
    runTest("p { filter: progid:foo.bar() }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n",
            "WARNING: css property filter has bad value:"
            + " ==>progid:foo.bar()<==");
    runTest("p { filter: progid:dximagetransform.microsoft.alpha(opaquity=50) }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n",
            "WARNING: css property filter has bad value:"
            + " ==>progid:dximagetransform.microsoft.alpha(opaquity=50)<==");
  }

  public final void testStarHack() throws Exception {
    runTest("p {\n"
            + "  color: blue;\n"
            + "  *color: red }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : color\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=color::color\n"
            + "          IdentLiteral : blue\n"
            + "    UserAgentHack : [IE6, IE7]\n"
            + "      PropertyDeclaration\n"
            + "        Property : color\n"
            + "        Expr\n"
            + "          Term ; cssPropertyPartType=IDENT"
                          + " ; cssPropertyPart=color::color\n"
            + "            IdentLiteral : red\n"
            );
    runTest("p { *color: yelow }",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n",
            "WARNING: css property color has bad value: ==>yelow<==");
  }

  public final void testHtmlStarHack() throws Exception {
    fails("* html p { color: blue }");
    fails("* html { color: blue }");
    fails("* html > p { color: blue }");
    fails("* html object { color: blue }");
    fails("* html#hiya p { color: blue }");
  }

  public final void testFontSpecialization() throws Exception {
    runTest("a {font:12px} b {font:x-small} i {font:caption} p {font:arial}",
            "StyleSheet\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : a\n"
            + "    PropertyDeclaration\n"
            + "      Property : font-size\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LENGTH"
                        + " ; cssPropertyPart=font-size\n"
            + "          QuantityLiteral : 12px\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : b\n"
            + "    PropertyDeclaration\n"
            + "      Property : font-size\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font-size::absolute-size\n"
            + "          IdentLiteral : x-small\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : i\n"
            + "    PropertyDeclaration\n"
            + "      Property : font\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=IDENT"
                        + " ; cssPropertyPart=font\n"
            + "          IdentLiteral : caption\n"
            + "  RuleSet\n"
            + "    Selector\n"
            + "      SimpleSelector\n"
            + "        IdentLiteral : p\n"
            + "    PropertyDeclaration\n"
            + "      Property : font-family\n"
            + "      Expr\n"
            + "        Term ; cssPropertyPartType=LOOSE_WORD"
                        + " ; cssPropertyPart=font-family::family-name"
                           + "::loose-quotable-words\n"
            + "          IdentLiteral : arial\n",

            "WARNING: specialized CSS property font to font-size",
            "WARNING: specialized CSS property font to font-size",
            // caption is a legal font value and should not be specialized to
            // font-family.
            "WARNING: specialized CSS property font to font-family"
            );
  }

  public final void testAttrSelectorNoTag() throws Exception {
    // we do not allow a selector without a tag name to
    // have attribute selectors
    fails("[type] { font-weight: bold }");
    fails("*[type] { font-weight: bold }");
    fails("*[type='radio'] { font-weight: bold }");
    fails("input [type='radio'] { font-weight: bold }");
    fails("#zork[type] { font-weight: bold }");
    fails(".zork[type] { font-weight: bold }");
  }

  public final void testAttrSelectorBadTag() throws Exception {
    // invalid tag names should be marked invalid even though they have
    // attribute selectors (defensive test cases)
    // first try a tag name that is not in the HTML schema
    fails("zork[type] { font-weight: bold }");
    fails("zork[type='radio'] { font-weight: bold }");
    fails("zork[type~='radio'] { font-weight: bold }");
    fails("zork[type|='radio'] { font-weight: bold }");
    // now try tags in the schema, but which we disallow
    fails("link[type] { font-weight: bold }");
    fails("object[type] { font-weight: bold }");
    fails("script[type] { font-weight: bold }");
  }

  public final void testSimpleAttrSelectorNoValue() throws Exception {
    // various forms of attribute selector without a value match
    runTest("input[type] { font-weight: bold }", null);
    fails("input[zork] { font-weight: bold }");
  }

  public final void testSimpleAttrSelectorEqual() throws Exception {
    // various forms of attribute selector with an 'equals' comparator
    runTest("input[type='radio'] { font-weight: bold }", null);
    runTest("input[type=radio] { font-weight: bold }", null);
    fails("input[zork='radio'] { font-weight: bold }");
    fails("input[type='atyourservice'] { font-weight: bold }");
    fails("input[type=atyourservice] { font-weight: bold }");
    fails("input[zork='atyourservice'] { font-weight: bold }");
  }

  public final void testSimpleAttrSelectorIncludes() throws Exception {
    // various forms of attribute selector with an 'includes' comparator
    runTest("input[type~='radio'] { font-weight: bold }", null);
    runTest("input[type~=radio] { font-weight: bold }", null);
    runTest("input[type~='radio button'] { font-weight: bold }", null);
    runTest("input[type~=' radio \t button \t '] { font-weight: bold }", null);
    fails("input[zork~='radio'] { font-weight: bold }");
    fails("input[zork~='radio atyourservice'] { font-weight: bold }");
    fails("input[type~='atyourservice'] { font-weight: bold }");
    fails("input[type~=atyourservice] { font-weight: bold }");
    fails("input[type~='radio atyourservice'] { font-weight: bold }");
  }

  public final void testSimpleAttrSelectorDashMatch() throws Exception {
    // we don't know how to whitelist the "|=" form so rejected
    fails("input[type|='button'] { font-weight: bold }");
  }

  public final void testAttrSelectorNesting() throws Exception {
    // attribute selectors on nested node type; ensure that whitelisting
    // is done on the basis of the innermost tag
    //   - the TR tag has attribute VALIGN with valid value TOP
    //   - the TABLE tag (enclosing in the rule) has no attribute VALIGN
    // first poke valid attributes of the enclosed TR ensuring that they
    // are whitelisted (or rejected) correctly based on the TR schema
    runTest("table tr[valign='top'] { font-weight: bold }", null);
    fails("table tr[valign='atyourservice'] { font-weight: bold }");
    fails("table tr[zork='top'] { font-weight: bold }");
    // then, just to be sure, poke a valid attribute and value of the
    // enclosing TABLE tag to make sure that the TABLE schema is not being
    // erroneously appplied. The TABLE tag has an attribute RULES, with a
    // valid value GROUPS, which is not applicable to the TR tag
    fails("table tr[rules='groups'] { font-weight: bold }");
  }

  public final void testDisallowedAttrs() throws Exception {
    // ID-like attributes disallowed because the cajoler rewrites them, and
    // we don't yet implement logic to reconstruct the rewritten values
    fails("input[id] { font-weight: bold }");
    fails("input[id='foo'] { font-weight: bold }");
    fails("input[id~='foo'] { font-weight: bold }");
    fails("td[headers] { font-weight: bold }");
    fails("label[for] { font-weight: bold }");
    // the STYLE attribute could be used to embed stylesheet content
    // recursively in a stylesheet; probably harmless but does not make
    // sense and is useless anyway so why risk it?
    fails("input[style] { font-weight: bold }");
    fails("input[style='foo'] { font-weight: bold }");
    fails("input[style~='foo'] { font-weight: bold }");
    // any URI-valued attribute is disallowed because the cajoler rewrites it,
    // and we don't yet implement logic to reconstruct the rewritten values.
    // we first verify that tag BLOCKQUOTE is allowed with valid attribute TITLE
    runTest("blockquote[title] { font-weight: bold }", null);
    // we then ensure it fails with URI-valued attribute CITE
    fails("blockquote[cite] { font-weight: bold }");
  }

  private void fails(String css) throws Exception {
    CssTree t = css(fromString(css), true);
    mq.getMessages().clear();
    CssValidator v = makeCssValidator(mq);
    assertTrue(css, !v.validateCss(ac(t)));
    MessageLevel maxLevel = MessageLevel.values()[0];
    for (Message msg : mq.getMessages()) {
      MessageLevel level = msg.getMessageLevel();
      if (level.compareTo(maxLevel) > 0) { maxLevel = level; }
    }
    // If there is a failure, there should be an error or greater on the queue.
    assertTrue(maxLevel.name(), MessageLevel.ERROR.compareTo(maxLevel) <= 0);
  }

  private void warns(String css) throws Exception {
    MessageQueue smq = new SimpleMessageQueue();
    CssTree t = css(fromString(css), true);
    CssValidator v = makeCssValidator(smq);
    boolean valid = v.validateCss(ac(t));
    mq.getMessages().addAll(smq.getMessages());
    assertTrue(css, valid);
    assertTrue(css, !mq.getMessages().isEmpty());
  }

  private static void removeInvalidNodes(AncestorChain<? extends CssTree> t) {
    if (t.node.getAttributes().is(CssValidator.INVALID)) {
      ((MutableParseTreeNode) t.parent.node).removeChild(t.node);
      return;
    }

    // Use a mutation to remove invalid nodes so that the sanity checks in
    // childrenChanged sees all removals at once.
    MutableParseTreeNode.Mutation mut = null;
    for (CssTree child : t.node.children()) {
      if (child.getAttributes().is(CssValidator.INVALID)) {
        if (mut == null) { mut = t.node.createMutation(); }
        mut.removeChild(child);
      } else {
        removeInvalidNodes(AncestorChain.instance(t, child));
      }
    }
    if (mut != null) { mut.execute(); }
  }


  private void runTest(String css, String golden, String... warnings)
    throws Exception {
    MessageContext mc = new MessageContext();
    mq.getMessages().clear();
    CssTree cssTree = css(fromString(css), true);
    MessageQueue smq = new SimpleMessageQueue();
    CssValidator v = makeCssValidator(smq);
    boolean valid = v.validateCss(ac(cssTree));
    mq.getMessages().addAll(smq.getMessages());

    // If no warnings are expected, the result should be valid
    if (warnings.length == 0) {
      if (!valid) {
        System.err.println(cssTree.toStringDeep());
      }
      assertTrue(css, valid);
    } else {
      removeInvalidNodes(AncestorChain.instance(cssTree));
    }

    mc.relevantKeys = new LinkedHashSet<SyntheticAttributeKey<?>>(
        Arrays.<SyntheticAttributeKey<?>>asList(
            CssValidator.CSS_PROPERTY_PART_TYPE,
            CssValidator.CSS_PROPERTY_PART));
    StringBuilder sb = new StringBuilder();
    cssTree.format(mc, sb);
    if (golden != null) {
      assertEquals(css, golden.trim(), sb.toString().trim());
    }

    List<String> actualWarnings = Lists.newArrayList();
    for (Message msg : mq.getMessages()) {
      if (MessageLevel.WARNING.compareTo(msg.getMessageLevel()) <= 0) {
        String msgText = msg.format(mc);
        msgText = msgText.substring(msgText.indexOf(": ") + 1);
        actualWarnings.add(msg.getMessageLevel().name() + ":" + msgText);
      }
    }
    mq.getMessages().clear();
    MoreAsserts.assertListsEqual(Arrays.asList(warnings), actualWarnings);
  }

  private static CssValidator makeCssValidator(MessageQueue mq) {
    return new CssValidator(
        CssSchema.getDefaultCss21Schema(mq), HtmlSchema.getDefault(mq), mq);
  }

  private static <T extends ParseTreeNode> AncestorChain<T> ac(T node) {
    return AncestorChain.instance(node);
  }
}
TOP

Related Classes of com.google.caja.plugin.CssValidatorTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.