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&amp;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

Reply via email to