Adds support for the URL_ATTRIBUTE_ENTIRE parse context to HtmlTemplateParser.
Review at http://gwt-code-reviews.appspot.com/1396803
Review by: jlabanca@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9945 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java b/user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java
index 316a524..54bcd6a 100644
--- a/user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java
+++ b/user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java
@@ -32,7 +32,7 @@
*
* <p>
* This parser parses templates consisting of HTML markup, with template
- * variables of the form {@code "{n}"}. For example, a template might look like,
+ * variables of the form {@code {n}}. For example, a template might look like,
*
* <pre> {@code
* <span style="{0}"><a href="{1}/{2}">{3}</a></span>
@@ -70,10 +70,13 @@
* <dt>{@link HtmlContext.Type#TEXT}
* <dd>This context corresponds to basic inner text. In the above example,
* parameter #3 would be tagged with this context.
- * <dt>{@link HtmlContext.Type#URL_START}
+ * <dt>{@link HtmlContext.Type#URL_ATTRIBUTE_START}
* <dd>This context corresponds to a parameter that appears at the very start of
* a URL-valued HTML attribute's value; in the above example this applies to
* parameter #1.
+ * <dt>{@link HtmlContext.Type#URL_ATTRIBUTE_ENTIRE}
+ * <dd>This context corresponds to a parameter that comprises an entire
+ * URL-valued attribute, for example in {@code <img src='{0}'/>}.
* <dt>{@link HtmlContext.Type#CSS_ATTRIBUTE_START}
* <dd>This context corresponds to a parameter that appears at the very
* beginning of a {@code style} attribute's value; in the above example this
@@ -135,6 +138,18 @@
private int parsePosition;
/**
+ * The character preceding a template parameter, at the time a template
+ * parameter is being parsed.
+ */
+ private char lookBehind;
+
+ /**
+ * The character succeeding a template parameter, at the time a template
+ * parameter is being parsed.
+ */
+ private char lookAhead;
+
+ /**
* Creates a {@link HtmlTemplateParser}.
*
* @param logger the {@link TreeLogger} to log to
@@ -163,7 +178,9 @@
// @VisibleForTesting
void parseTemplate(String template) throws UnableToCompleteException {
this.template = template;
- this.parsePosition = 0;
+ parsePosition = 0;
+ lookBehind = 0;
+ lookAhead = 0;
Matcher match = TEMPLATE_PARAM_PATTERN.matcher(template);
int endOfPreviousMatch = 0;
@@ -174,10 +191,16 @@
parseAndAppendTemplateSegment(
template.substring(endOfPreviousMatch, match.start()));
parsePosition = match.start();
+ lookBehind = template.charAt(parsePosition - 1);
}
int paramIndex = Integer.parseInt(match.group(1));
parsePosition = match.end();
+ if (parsePosition < template.length()) {
+ lookAhead = template.charAt(parsePosition);
+ } else {
+ lookAhead = 0;
+ }
parsedTemplate.addParameter(
new ParameterChunk(getHtmlContextFromParseState(), paramIndex));
@@ -255,8 +278,29 @@
+ getTemplateParsedSoFar());
throw new UnableToCompleteException();
}
+ if ("meta".equals(tag) && "content".equals(attribute)) {
+ logger.log(TreeLogger.ERROR,
+ "Template variables in content attribute of meta tag are not supported: "
+ + getTemplateParsedSoFar());
+ throw new UnableToCompleteException();
+ }
if (streamHtmlParser.isUrlStart()) {
- return new HtmlContext(HtmlContext.Type.URL_START, tag, attribute);
+ // Note that we have established above that the attribute is quoted.
+ // Furthermore, we have ruled out template variables in the content
+ // attribute of a meta tag, which is the only case where isUrlStart()
+ // is true and the URL does not appear at the very beginning of the
+ // attribute.
+ Preconditions.checkState(lookBehind == '"' || lookBehind == '\'',
+ "At the start of a quoted attribute, lookBehind should be a quote character; at %s",
+ getTemplateParsedSoFar());
+ // If the the character immediately succeeding the template parameter is
+ // a quote that matches the one that started the attribute, we know
+ // that the parameter comprises the entire attribute.
+ if (lookAhead == lookBehind) {
+ return new HtmlContext(HtmlContext.Type.URL_ATTRIBUTE_ENTIRE, tag, attribute);
+ } else {
+ return new HtmlContext(HtmlContext.Type.URL_ATTRIBUTE_START, tag, attribute);
+ }
} else if (streamHtmlParser.inCss()) {
if (streamHtmlParser.getValueIndex() == 0) {
return new HtmlContext(HtmlContext.Type.CSS_ATTRIBUTE_START, tag, attribute);
diff --git a/user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java b/user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java
index dd44da7..7567c16 100644
--- a/user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java
+++ b/user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java
@@ -56,7 +56,11 @@
/**
* At the very start of a URL-valued attribute.
*/
- URL_START,
+ URL_ATTRIBUTE_START,
+ /**
+ * A template parameter that comprises an entire URL-valued attribute.
+ */
+ URL_ATTRIBUTE_ENTIRE,
/**
* CSS (style) context.
*/
diff --git a/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java b/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
index 479dac5..871ef53 100644
--- a/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
+++ b/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
@@ -144,7 +144,8 @@
expression = "String.valueOf(" + expression + ")";
}
- if ((htmlContext.getType() == HtmlContext.Type.URL_START)) {
+ if ((htmlContext.getType() == HtmlContext.Type.URL_ATTRIBUTE_START) ||
+ (htmlContext.getType() == HtmlContext.Type.URL_ATTRIBUTE_ENTIRE)) {
expression = URI_UTILS_FQCN + ".sanitizeUri(" + expression + ")";
}
@@ -253,7 +254,8 @@
emitAttributeContextParameterExpression(logger, htmlContext,
formalParameterName, parameterType);
break;
- case URL_START:
+ case URL_ATTRIBUTE_START:
+ case URL_ATTRIBUTE_ENTIRE:
case ATTRIBUTE_VALUE:
emitAttributeContextParameterExpression(logger, htmlContext,
formalParameterName, parameterType);
diff --git a/user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java b/user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java
index 8ef9f53..7e6e058 100644
--- a/user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java
+++ b/user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java
@@ -111,15 +111,20 @@
+ "L(</span>)]"),
"<span>foo&bar<b>{1}</b><![CDATA[foo-cdata <baz>]]>{0}</span>");
- // Check correct handling of ATTRIBUTE_VALUE vs URL_START context.
- assertParseTemplateResult(("[L(<a href=\"), P((URL_START,a,href),0), "
+ // Check correct handling of ATTRIBUTE_VALUE vs URL_ATTRIBUTE_START and
+ // URL_ATTRIBUTE_ENTIRE context.
+ assertParseTemplateResult(("[L(<a href=\"), P((URL_ATTRIBUTE_ENTIRE,a,href),0), "
+ "L(\">), P((TEXT,null,null),1), L(</a>)]"),
"<a href=\"{0}\">{1}</a>");
+ // Single quotes work too:
+ assertParseTemplateResult(("[L(<a href='), P((URL_ATTRIBUTE_ENTIRE,a,href),0), "
+ + "L('>), P((TEXT,null,null),1), L(</a>)]"),
+ "<a href='{0}'>{1}</a>");
assertParseTemplateResult(
("[L(<a href=\"http://), P((ATTRIBUTE_VALUE,a,href),0), "
+ "L(\">), P((TEXT,null,null),1), L(</a>)]"),
"<a href=\"http://{0}\">{1}</a>");
- assertParseTemplateResult(("[L(<a href=\"), P((URL_START,a,href),0), "
+ assertParseTemplateResult(("[L(<a href=\"), P((URL_ATTRIBUTE_START,a,href),0), "
+ "L(/), P((ATTRIBUTE_VALUE,a,href),1), "
+ "L(\">), P((TEXT,null,null),2), L(</a>)]"),
"<a href=\"{0}/{1}\">{2}</a>");
@@ -173,6 +178,12 @@
"<div class=\"{0}\" foo=bar>{1}<a");
assertParsingTemplateEndingInNonInnerHtmlContextFails(
"<div class=\"{0}\" foo=bar>{1}<a href=");
+
+ // Check that parseTemplate doesn't walk off the end of the string when
+ // extracting lookAhead: We should be getting an error that the template
+ // ends in non-inner-HTML context, and not an IndexOutOfBoundsException.
+ assertParsingTemplateEndingInNonInnerHtmlContextFails("<a href='{0}'");
+ assertParsingTemplateEndingInNonInnerHtmlContextFails("<a href='{0}");
}
private void assertTemplateVariableInUnquotedAttributeFails(
@@ -247,6 +258,11 @@
"<div style=\"{0}\" foo{1}=\"{2}\">", "<div style=\"{0}\" foo{1}");
}
+ public void testParseTemplate_templateVariableInMetaContentFails() {
+ assertParseFails("Template variables in content attribute of meta tag are not supported: ",
+ "<meta http-equiv=\"{0}\" content=\"{1}\">", "<meta http-equiv=\"{0}\" content=\"{1}");
+ }
+
private void assertParseFails(
String expectedError, final String template, final String failAtPrefix) {
UnitTestTreeLogger.Builder loggerBuilder = new UnitTestTreeLogger.Builder();
diff --git a/user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java b/user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java
index d5d1334..dd8d4ce 100644
--- a/user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java
+++ b/user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java
@@ -49,7 +49,7 @@
HtmlContext.Type.TEXT), 0));
List<TemplateChunk> chunks = parsed.getChunks();
-
+
ParameterChunk chunk = (ParameterChunk) chunks.get(0);
assertEquals(TemplateChunk.Kind.PARAMETER, chunk.getKind());
assertEquals(HtmlContext.Type.TEXT, chunk.getContext().getType());
@@ -108,8 +108,8 @@
/**
* Tests that calling addParameter(), addLiteral(), addLiteral(),
* addParameter() in sequence results in the expected ParsedHtmlTemplate.
- *
- * <p>In particular, two calls to addLiteral() in sequence should result in
+ *
+ * <p>In particular, two calls to addLiteral() in sequence should result in
* only a single LiteralChunk.
*/
public void testAddParameterAddLiteralSequence() {
@@ -120,7 +120,7 @@
parsed.addLiteral("<a");
parsed.addLiteral(" href=\"");
parsed.addParameter(new ParameterChunk(new HtmlContext(
- HtmlContext.Type.URL_START, "a", "href"), 1));
+ HtmlContext.Type.URL_ATTRIBUTE_START, "a", "href"), 1));
List<TemplateChunk> chunks = parsed.getChunks();
assertEquals(3, chunks.size());
@@ -145,15 +145,15 @@
paramChunk = (ParameterChunk) it.next();
assertEquals(TemplateChunk.Kind.PARAMETER, paramChunk.getKind());
assertEquals(
- HtmlContext.Type.URL_START, paramChunk.getContext().getType());
+ HtmlContext.Type.URL_ATTRIBUTE_START, paramChunk.getContext().getType());
assertEquals("a", paramChunk.getContext().getTag());
assertEquals("href", paramChunk.getContext().getAttribute());
assertEquals(1, paramChunk.getParameterIndex());
- assertEquals("P((URL_START,a,href),1)", paramChunk.toString());
+ assertEquals("P((URL_ATTRIBUTE_START,a,href),1)", paramChunk.toString());
assertEquals(
"[P((TEXT,null,null),0), L(<a href=\"), "
- + "P((URL_START,a,href),1)]",
+ + "P((URL_ATTRIBUTE_START,a,href),1)]",
parsed.toString());
}
}