Reviewers: jlabanca, tbroyer,
Description:
Adds support for the URL_ATTRIBUTE_ENTIRE parse context to
HtmlTemplateParser.
Please review this at http://gwt-code-reviews.appspot.com/1396803/
Affected files:
M user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java
M user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java
M
user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
M user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java
M user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java
Index: user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java
===================================================================
--- user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java
(revision 9914)
+++ user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java
(working copy)
@@ -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));
@@ -208,7 +231,6 @@
* This method checks for certain illegal/unsupported template
constructs,
* such as template variables that occur in an un-quoted attribute (see
this
* class' class documentation for details).
- *
* @throws UnableToCompleteException if an illegal/unuspported template
* construct is encountered
*/
@@ -256,7 +278,18 @@
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.
+ 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);
Index: user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java
===================================================================
--- user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java
(revision 9914)
+++ user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java
(working copy)
@@ -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.
*/
Index:
user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
===================================================================
---
user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
(revision 9914)
+++
user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
(working copy)
@@ -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);
Index: user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java
===================================================================
--- user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java
(revision 9914)
+++ user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java
(working copy)
@@ -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(
Index: user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java
===================================================================
--- user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java
(revision 9914)
+++ user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java
(working copy)
@@ -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());
}
}
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors