/*
* Copyright 2012 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 static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Functions;
import junit.framework.TestCase;
/**
* Unit tests for {@code CssStringNode}
*
*/
public class CssStringNodeTest extends TestCase {
public void testCssValueNodeRoundtrip() throws Exception {
String v = "ordinary";
for (CssStringNode.Type t : CssStringNode.Type.values()) {
CssStringNode n = new CssStringNode(t, v);
n.setValue(v);
assertEquals(v, n.getValue());
}
}
public void testCssValueNodeFixedPoint() throws Exception {
// This test doesn't care if setValue/getValue work in terms of
// CSS or abstract values, but we just want to make sure that
// eventually what we set is what we get.
// Here's a value that must be escaped in CSS. It ensures that
// we exercise whatever codec is in operation behind these
// accessors.
String v = "\\\\\"\"";
CssStringNode n = new CssStringNode(
CssStringNode.Type.DOUBLE_QUOTED_STRING, v);
String v2 = n.getValue();
n.setValue(v2);
assertEquals(v2, n.getValue());
}
public void testConcreteRoundtrip() throws Exception {
// This value is safe for verbatim inclusion in either kind of
// string literal, and for each kind it includes:
// (a) an escape sequence that denotes a character that must
// be escaped in literals of that kind.
// (b) a character (possibly as part of an escape sequence)
// that must be escaped in literals of that kind
// Also, it includes weird escape sequences that are unlikely
// to occur together in sane usage, which helps demonstrate
// normalization.
String v = "\\'\\\"\\041zA\\0000411\\41 1";
// ^single quote
// ^double quote
// ^weird A
// ^z
// ^A
// ^weird A, second variation
// ^1
// ^A with escape code delimiter
// ^1
for (CssStringNode.Type t : CssStringNode.Type.values()) {
CssStringNode n = new CssStringNode(t, v);
n.setConcreteValue(v);
assertEquals(v, n.getConcreteValue());
}
}
public void testShortEscaper() throws Exception {
for (String[] io : new String[][] {
{"", ""},
{"a", "a"},
{"¤", "\\a4"},
{"ξ", "\\3be"},
{"ξe", "\\3be e"},
{"ξx", "\\3bex"},
{"唐", "\\5510"},
{"𠍱", "\\20371"},
{new String(
new byte[] {(byte) 0xf0, (byte) 0xa0, (byte) 0x8d, (byte) 0xb1},
UTF_8),
"\\20371"}}) {
assertEquals(io[1], CssStringNode.SHORT_ESCAPER.apply(io[0]));
}
// Six-hexadecimal-digit codepoints aren't allowed
// leading-zeros-padding. This is also an interesting case because
// Java chars and Strings are defined in terms of UTF-16, which
// represents codepoints in this range as surrogate pairs. Let's
// use UTF-8 to specify the input because it's simpler:
byte[] puabUtf8 = {(byte) 0xf4, (byte) 0x80, (byte) 0x80, (byte) 0x80};
assertEquals("\\100000",
CssStringNode.SHORT_ESCAPER.apply(
new String(puabUtf8, UTF_8)));
assertEquals("\\100000a",
CssStringNode.SHORT_ESCAPER.apply(
String.format("%sa",
new String(puabUtf8, UTF_8))));
}
public void testInsertsIgnoredWhitespaceAfterEscape() throws Exception {
// When parsing, we always discard zero or one whitespace after an
// escape sequence.
// See http://www.w3.org/TR/CSS2/syndata.html#characters
// and http://www.w3.org/TR/css3-syntax/#consume-an-escaped-code-point
String stringTemplate = "%s following (%s)";
String cssTemplate = "%s following (%s)";
for (CssStringNode.Type type : CssStringNode.Type.values()) {
// We produce escape sequences in three cases:
// (1) newline
assertEquals(
String.format(cssTemplate, "\\00000a", type.getClass().getName()),
CssStringNode.escape(
type,
CssStringNode.HTML_ESCAPER,
String.format(stringTemplate, "\n", type.getClass().getName())));
// (2) no CSS literal representation exists
assertEquals(
String.format(cssTemplate, "\\a4", type.getClass().getName()),
CssStringNode.escape(
type,
CssStringNode.SHORT_ESCAPER,
String.format(stringTemplate, "¤", type.getClass().getName())));
// (3) HTML/SGML special character when using the HTML_ESCAPER
assertEquals(
String.format(cssTemplate, "\\00003c", type.getClass().getName()),
CssStringNode.escape(
type,
CssStringNode.HTML_ESCAPER,
String.format(stringTemplate, "<", type.getClass().getName())));
}
}
public void testHtmlEscaper() throws Exception {
for (String[] io : new String[][] {
{"", ""},
{"a", "a"},
{"¤", "\\0000a4"},
{"ξ", "\\0003be"},
{"ξe", "\\0003bee"},
{"ξx", "\\0003bex"},
{"唐", "\\005510"},
{"𠍱", "\\020371"},
{new String(
new byte[] {(byte) 0xf0, (byte) 0xa0, (byte) 0x8d, (byte) 0xb1},
UTF_8),
"\\020371"}}) {
assertEquals(io[1], CssStringNode.HTML_ESCAPER.apply(io[0]));
}
assertEquals("\\000026", CssStringNode.HTML_ESCAPER.apply("&"));
assertEquals("\\00003c", CssStringNode.HTML_ESCAPER.apply("<"));
assertEquals("\\00003e", CssStringNode.HTML_ESCAPER.apply(">"));
assertEquals("\\000022", CssStringNode.HTML_ESCAPER.apply("\""));
assertEquals("\\000027", CssStringNode.HTML_ESCAPER.apply("'"));
}
public void testEscape() throws Exception {
for (String[] io : new String[][] {
{"", ""},
{"a", "a"},
{"\\", "\\\\"},
{"19\\3=6", "19\\\\3=6"},
{"say \"hello\"", "say \\\"hello\\\""},
{"say 'goodbye'", "say 'goodbye'"}}) {
assertEquals(
io[1],
CssStringNode.escape(
CssStringNode.Type.DOUBLE_QUOTED_STRING,
Functions.<String>identity(), io[0]));
}
assertEquals(
"say \\'goodbye\\'",
CssStringNode.escape(
CssStringNode.Type.SINGLE_QUOTED_STRING,
Functions.<String>identity(), "say 'goodbye'"));
}
public void testUnescape() throws Exception {
for (String[] io : new String[][] {
{"", ""},
{"a", "a"},
{"\\\\", "\\"},
{"\\\"", "\""},
{"\\41", "A"},
{"\\41 ", "A"},
{"\\41 ", "A "},
// The spec requires us to discard a whitespace following an
// escape sequence, even when the escape sequence is as long
// as possible and hence there is no ambiguity about where it
// ends.
{"abc\\000041 ", "abcA"},
{"abc\\000041 ", "abcA "},
{"\\41x", "Ax"},
{"\\0000a4", "¤"},
{"\\0003be", "ξ"},
{"\\0003bee", "ξe"},
{"\\3be e", "ξe"},
{"\\0003be", "ξ"},
{"\\3be z", "ξz"},
{"\\005510", "唐"},
{"\\020371", "𠍱"},
{"\\020371", new String(
new byte[] {(byte) 0xf0, (byte) 0xa0, (byte) 0x8d, (byte) 0xb1},
UTF_8)}}) {
assertEquals(io[1], CssStringNode.unescape(io[0]));
}
// Now let's look at a character that requires a max-length escape
// code in CSS and use of surrogate pairs in the JVM
byte[] puabUtf8 = {(byte) 0xf4, (byte) 0x80, (byte) 0x80, (byte) 0x80};
assertEquals(new String(puabUtf8, UTF_8),
CssStringNode.unescape("\\100000"));
assertEquals(new String(puabUtf8, UTF_8) + "a",
CssStringNode.unescape("\\100000a"));
// Here's an escape sequence denoting a code point beyond the Java
// char/String repertoire. According to CSS 2.1, we can replace the
// escape.
int bigCodePoint = Character.MAX_CODE_POINT + 1;
assertEquals(
// replacement character
"\ufffd",
CssStringNode.unescape(
String.format("\\%x", bigCodePoint)));
}
public void testCopyCtor() {
CssStringNode a = new CssStringNode(
CssStringNode.Type.DOUBLE_QUOTED_STRING, "foo");
a.setConcreteValue("\\0066oobar");
CssStringNode b = new CssStringNode(a);
assertEquals("\\0066oobar", a.getConcreteValue());
assertEquals(a.getConcreteValue(), b.getConcreteValue());
assertEquals(a.getValue(), b.getValue());
}
public void testStringCannotDirectlyContainNewline() {
// See http://www.w3.org/TR/CSS2/syndata.html#strings
CssStringNode a = new CssStringNode(
CssStringNode.Type.SINGLE_QUOTED_STRING, "line1\nline2");
assertTrue(
"We should support the Java String representation of newlines.",
a.getValue().contains("\n"));
assertFalse(
"If we set a Java newline, it should be escaped in the"
+ " generated concrete value.",
a.getConcreteValue().contains("\n"));
assertFalse(
"If we ask for CSS markup, we should escape newlines per the"
+ " CSS spec.",
a.toString(CssStringNode.HTML_ESCAPER).contains("\n"));
assertTrue(
"Escaping a new line shouldn't affect the left hand side",
a.toString(CssStringNode.HTML_ESCAPER).startsWith("'line1"));
assertTrue(
"Escaping a new line shouldn't affect the right-hand side",
a.toString(CssStringNode.HTML_ESCAPER).endsWith("line2'"));
}
}