Revision: 9396
Author: x...@google.com
Date: Thu Dec  9 08:34:53 2010
Log: Switch SafeHtmlTemplates code generator to use the stream HTML parser.

Review at http://gwt-code-reviews.appspot.com/905801

http://code.google.com/p/google-web-toolkit/source/detail?r=9396

Modified:
 /trunk/user/src/com/google/gwt/safehtml/client/SafeHtmlTemplates.java
 /trunk/user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java
 /trunk/user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java
/trunk/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
 /trunk/user/test/com/google/gwt/cell/client/ImageCellTest.java
 /trunk/user/test/com/google/gwt/safehtml/client/SafeHtmlTemplatesTest.java
 /trunk/user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java
 /trunk/user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java

=======================================
--- /trunk/user/src/com/google/gwt/safehtml/client/SafeHtmlTemplates.java Thu Dec 2 04:57:49 2010 +++ /trunk/user/src/com/google/gwt/safehtml/client/SafeHtmlTemplates.java Thu Dec 9 08:34:53 2010
@@ -25,7 +25,9 @@
* A tag interface that facilitates compile-time binding of HTML templates to
  * generate SafeHtml strings.
  *
- * <p>Example usage:
+ * <p>
+ * Example usage:
+ *
  * <pre>
  *   public interface MyTemplate extends SafeHtmlTemplates {
* &#064;Template("&lt;span class=\"{3}\"&gt;{0}: &lt;a href=\"{1}\"&gt;{2}&lt;/a&gt;&lt;/span&gt;")
@@ -50,19 +52,41 @@
* Instantiating a {...@code SafeHtmlTemplates} interface with {...@code GWT.create()} * returns an instance of an implementation that is generated at compile time.
  * The code generator parses the value of each template method's
- * {...@code &#064;Template} annotation as a (X)HTML template, with template
+ * {...@code @Template} annotation as an HTML template, with template
  * variables denoted by curly-brace placeholders that refer by index to the
  * corresponding template method parameter.
  *
  * <p>
- * <b>Note:</b> The current implementation of the code generator cannot
- * guarantee the {...@code SafeHtml} contract for templates with template variables - * in a CSS or JavaScript context (that is, within a {...@code style} attribute or
- * tag; or within {...@code <script>} tags or {...@code onClick}, {...@code
- * onError}, etc. attributes). Developers are advised to avoid such templates, - * or to review the uses of corresponding template methods very carefully to - * ensure that values passed into the CSS or JavaScript context cannot result in
- * unintended script execution.
+ * The code generator's template parser is lenient, and will accept HTML that is + * not well-formed; the accepted set of HTML is similar to what is typically + * accepted by browsers. However, the following constraints on the HTML template
+ * are enforced:
+ *
+ * <ol>
+ * <li>Template variables may not appear in a JavaScript context (inside a
+ * {...@code <script>} tag, or in an {...@code onClick} etc handler).</li>
+ * <li>Template variables may not appear inside HTML comments.</li>
+ * <li>If a template variable appears inside the value of an attribute, the
+ * value must be enclosed in quotes.</li>
+ * <li>Template variables may not appear in the context of an attribute name,
+ * nor elsewhere inside a tag except within a quoted attribute value.</li>
+ * <li>The template must end in "inner HTML" context, and not inside a tag or
+ * attribute.</li>
+ * </ol>
+ *
+ * <p>
+ * <b>Limitation:</b> For templates with template variables in a CSS (style) + * context, the current implementation of the code generator does not guarantee
+ * that the generated template method produces values that adhere to the
+ * {...@code SafeHtml} type contract. When the code generator encounters a template
+ * with a variable in a style attribute or tag, such as,
+ * {...@code <div style=\"{0}\">}, a warning will be emitted. In this
+ * case, developers are advised to carefully review uses of this template to + * ensure that parameters passed to the template are from a trusted source or
+ * suitably sanitized.
+ *
+ * <p> Future implementations of the code generator may place additional
+ * constraints on template parameters in style contexts.
  */
 public interface SafeHtmlTemplates {

=======================================
--- /trunk/user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java Fri Oct 8 06:15:38 2010 +++ /trunk/user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java Thu Dec 9 08:34:53 2010
@@ -20,219 +20,90 @@
 import com.google.gwt.dev.util.Preconditions;
 import com.google.gwt.safehtml.rebind.ParsedHtmlTemplate.HtmlContext;
 import com.google.gwt.safehtml.rebind.ParsedHtmlTemplate.ParameterChunk;
-import com.google.gwt.safehtml.shared.SafeHtmlUtils;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXNotSupportedException;
-import org.xml.sax.SAXParseException;
-import org.xml.sax.XMLReader;
-import org.xml.sax.helpers.DefaultHandler;
-import org.xml.sax.helpers.XMLReaderFactory;
-
-import java.io.IOException;
-import java.io.Reader;
+import com.google.gwt.thirdparty.streamhtmlparser.HtmlParser;
+import com.google.gwt.thirdparty.streamhtmlparser.HtmlParserFactory;
+import com.google.gwt.thirdparty.streamhtmlparser.ParseException;
+
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;

 /**
  * A HTML context-aware parser for a simple HTML template language.
  *
- * <p>This parser parses templates consisting of well-formed XML or XHTML
- * markup, with template parameters of the form {...@code "{n}"}. For example, a
- * template might look like,
+ * <p>
+ * This parser parses templates consisting of HTML markup, with template
+ * variables of the form {...@code "{n}"}. For example, a template might look like,
+ *
  * <pre>  {...@code
- *   <span class="{0}"><a href="{1}/{2}">{3}</a></span>
+ *   <span style="{0}"><a href="{1}/{2}">{3}</a></span>
  * }</pre>
  *
- * <p>The parser produces a parsed form of the template (returned as a
- * {...@link ParsedHtmlTemplate}) consisting of a sequence of chunks
- * corresponding to the literal strings and parameters of the template. The - * parser is HTML context aware and tags each parameter with its parameter index
- * as well as a {...@link HtmlContext} that corresponds to the HTML context in
- * which the parameter occurs in the template.
+ * <p>
+ * The parser is lenient, and will accept HTML that is not well-formed; the
+ * accepted set of HTML is similar to what is typically accepted by browsers.
+ * However, the following constraints on the HTML template are enforced:
  *
- * <p>The following contexts are recognized and instantiated:
+ * <ol>
+ * <li>Template variables may not appear in a JavaScript context (inside a
+ * {...@code <script>} tag, or in an {...@code onClick} etc handler).</li>
+ * <li>Template variables may not appear inside HTML comments.</li>
+ * <li>If a template variable appears inside the value of an attribute, the
+ * value must be enclosed in quotes.</li>
+ * <li>Template variables may not appear in the context of an attribute name,
+ * nor elsewhere inside a tag except within a quoted attribute value.
+ * </li>
+ * <li>The template must end in "inner HTML" context, and not inside a tag or
+ * attribute.</li>
+ * </ol>
+ *
+ * <p>
+ * The parser produces a parsed form of the template (returned as a
+ * {...@link ParsedHtmlTemplate}) consisting of a sequence of chunks corresponding + * to the literal strings and parameters of the template. The parser is HTML + * context aware and tags each parameter with its parameter index as well as a
+ * {...@link HtmlContext} that corresponds to the HTML context in which the
+ * parameter occurs in the template.
+ *
+ * <p>
+ * The following contexts are recognized and instantiated:
  * <dl>
- *   <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#ATTRIBUTE_START}
- * <dd>This context corresponds to a parameter that appears at the very start
- *       of a HTML attribute's value; in the above example this applies to
- *       parameters #0 and #1.
- *   <dt>{...@link HtmlContext.Type#ATTRIBUTE}
- *   <dd>This context corresponds to a parameter that appears within an
- *       attribute in a position other than at the start of the attribute's
- *       value. In the above example, this applies to parameter #2.
+ * <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}
+ * <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#CSS_ATTRIBUTE}
+ * <dd>This context corresponds to a parameter that appears in the context of a
+ * {...@code style} attribute; in the above example this applies to
+ * parameter #0.
+ * <dt>{...@link HtmlContext.Type#ATTRIBUTE_VALUE}
+ * <dd>This context corresponds to a parameter that appears within an attribute
+ * and is not in one of the more specific in-attribute contexts above. In
+ * the example, this applies to parameter #2.
+ * <dt>{...@link HtmlContext.Type#CSS}
+ * <dd>This context corresponds to a parameter that appears within a
+ * {...@code <style>} tag.
  * </dl>
  *
- * <p>For both attribute contexts, the {...@code tag} and {...@code attribute}
- * properties of the context are set to the name of the enclosing tag and
- * attribute, respectively.
+ * <p>
+ * For attribute contexts, the {...@code tag} and {...@code attribute} 
properties
+ * of the context are set to the name of the enclosing tag and attribute,
+ * respectively.
  *
- * <p>Tag and attribute names are converted to lower-case in {...@link HtmlContext}
- * properties and literal string chunks of the parsed form.
+ * <p>
+ * The implementation is subject to the following limitation:
  *
- * <p>The implementation is subject to the following limitations:
- * <ul>
- *   <li>The input template must be well-formed XML/XHTML. If it is not,
- * an {...@link UnableToCompleteException} is thrown and details regarding - * the source of the parse failure are logged to this parser's logger.
- *   <li>Template parameters can only appear within inner text and within
- *       attributes. In particular, parameters cannot appear within a HTML
- *       tag or attribute name; for example, the following is not a valid
- *       template:
- *       <pre>  {...@code
- *         <span><{0} class="xyz" {1}="..."/></span>
- *       }</pre>
- * <li>The output markup will contain separate closing tags for tags without
- *       content. I.e., an input template
- *       <pre>  {...@code
- *         <img src="..."/>
- *       }</pre>
- *       will result in the corresponding output
- *       <pre>  {...@code
- *         <img src="..."></img>
- *       }</pre>
- * <li>There is no escaping mechanism for the parameter syntax, i.e. it is - * impossible to write a template that results in a literal output chunk
- *       containing a substring of the form "{...@code {0}}".
- * </ul>
+ * <p>
+ * There is no escaping mechanism for the parameter syntax, i.e. it is
+ * impossible to write a template that results in a literal output chunk
+ * containing a substring of the form "{...@code {0}}".
+ *
+ * <p>
+ * This class is not thread safe.
  */
 final class HtmlTemplateParser {
-
-  /**
-   * A SAX parser event handler for parsing HTML templates.
-   */
-  private class HtmlTemplateHandler extends DefaultHandler {
-
-    /*
-     * Overrides for relevant SAX event handler methods.
-     */
-
-    @Override
-    public void characters(char[] ch, int start, int length) {
-      parseTemplateString(
-          new HtmlContext(HtmlContext.Type.TEXT),
-          SafeHtmlUtils.htmlEscape(new String(ch, start, length)));
-    }
-
-    @Override
-    public void endElement(String uri, String localName, String name) {
-      Preconditions.checkArgument(uri.equals(""),
-          "Namespace uri unexpectedly non-empty: %s", uri);
-      appendLiteral("</" + name.toLowerCase() + ">");
-    }
-
-    @Override
-    public void endPrefixMapping(String prefix) throws SAXException {
-      throw unsupportedError("Prefix Mapping");
-    }
-
-    @Override
-    public void error(SAXParseException e) {
-      getLogger().log(TreeLogger.ERROR, "Parser error: " + e);
-    }
-
-    @Override
-    public void fatalError(SAXParseException e) throws SAXException {
-      getLogger().log(TreeLogger.ERROR, "Parser fatal error: " + e);
-      throw e;
-    }
-
-    /*
-     * Throw errors on various irrelevant SAX events that we don't want to
-     * handle, and which should not occur in templates.
-     *
- * It may be reasonable to just silently ignore these events, but failing
-     * explicitly seems more helpful to developers.
-     */
-
-    @Override
-    public void notationDecl(String name, String publicId, String systemId)
-        throws SAXException {
-      throw unsupportedError("Notation Declaration");
-    }
-
-    @Override
-    public void processingInstruction(String target, String data)
-        throws SAXException {
-      throw unsupportedError("Processing Instruction");
-    }
-
-    @Override
-    public InputSource resolveEntity(String publicId, String systemId)
-        throws SAXException {
-      throw unsupportedError("External Entity");
-    }
-
-    @Override
-    public void skippedEntity(String name) throws SAXException {
-      throw unsupportedError("Skipped Entity");
-    }
-
-    @Override
-    public void startElement(String uri, String localName, String name,
-        Attributes attributes) {
-      Preconditions.checkArgument(uri.equals(""),
-          "Namespace uri unexpectedly non-empty: %s", uri);
-
-      name = name.toLowerCase();
-      appendLiteral("<" + name);
-      if (attributes != null) {
-        int len = attributes.getLength();
-        for (int i = 0; i < len; i++) {
-          String attribute = attributes.getQName(i).toLowerCase();
-          appendLiteral(" " + attribute + "=\"");
-          parseTemplateString(
-              new HtmlContext(HtmlContext.Type.ATTRIBUTE, name, attribute),
-              new HtmlContext(HtmlContext.Type.ATTRIBUTE_START,
-                  name, attribute),
-                  SafeHtmlUtils.htmlEscape(attributes.getValue(i)));
-          appendLiteral("\"");
-        }
-      }
-      appendLiteral(">");
-    }
-
-    /*
-     * Error handlers.
-     */
-
-    @Override
-    public void startPrefixMapping(String prefix, String uri)
-        throws SAXException {
-      throw unsupportedError("Prefix Mapping");
-    }
-
-    @Override
-    public void unparsedEntityDecl(String name, String publicId,
-        String systemId, String notationName) throws SAXException {
-      throw unsupportedError("Unparsed Entity Declaration");
-    }
-
-    @Override
-    public void warning(SAXParseException e) {
-      getLogger().log(TreeLogger.WARN, "Parser warning: " + e);
-    }
-
-    /**
-     * Returns exception for unsupported event in SafeHtmlTemplates.
-     *
-     * <p>
-     * Returns an exception indicating that the event in question is not
-     * supported in SafeHtmlTemplates.
-     *
-     * @param what unsupported SAX event that should not occur in templates
-     * @return exception stating that the event is not allowed
-     */
-    private SAXNotSupportedException unsupportedError(String what) {
-      return new SAXNotSupportedException(
-          "Not allowed in SafeHtmlTemplates: " + what);
-    }
-  }

   /**
    * Pattern to find template parameters references.
@@ -244,6 +115,21 @@

   private final ParsedHtmlTemplate parsedTemplate;

+  private final HtmlParser streamHtmlParser;
+
+  /**
+   * The template string being parsed.
+   */
+  private String template;
+
+  /**
+   * The index in the template up to which the template has been parsed.
+   *
+   * <p>
+   * Used for error reporting.
+   */
+  private int parsePosition;
+
   /**
    * Creates a {...@link HtmlTemplateParser}.
    *
@@ -252,13 +138,7 @@
   public HtmlTemplateParser(TreeLogger logger) {
     this.logger = logger;
     this.parsedTemplate = new ParsedHtmlTemplate();
-  }
-
-  /**
-   * Returns this parser's logger.
-   */
-  public TreeLogger getLogger() {
-    return logger;
+    this.streamHtmlParser = HtmlParserFactory.createParser();
   }

   /**
@@ -267,136 +147,154 @@
   public ParsedHtmlTemplate getParsedTemplate() {
     return parsedTemplate;
   }
-
-  /**
-   * Parses a XML/XHTML document.
-   *
- * @param input a {...@link Reader} from which the document to be parsed will be
-   *        read.
- * @throws UnableToCompleteException if the template cannot be parsed. Details - * on the source of the failure will have been logged to this parser's
-   *         logger.
-   */
-  public void parseXHtml(Reader input) throws UnableToCompleteException {
-    HtmlTemplateHandler saxEventHandler = new HtmlTemplateHandler();
-    XMLReader xmlParser;
-    try {
-      xmlParser = XMLReaderFactory.createXMLReader();
-    } catch (SAXException e) {
-      logger.log(TreeLogger.ERROR, "Couldn't instantiate XML parser", e);
-      throw new UnableToCompleteException();
-    }
-
-    xmlParser.setContentHandler(saxEventHandler);
-    xmlParser.setDTDHandler(saxEventHandler);
-    xmlParser.setEntityResolver(saxEventHandler);
-    xmlParser.setErrorHandler(saxEventHandler);
-
-    try {
-      xmlParser.parse(new InputSource(input));
-    } catch (IOException e) {
-      logger.log(TreeLogger.ERROR, "Error during template parsing:", e);
-      throw new UnableToCompleteException();
-    } catch (SAXParseException e) {
-      String logMessage = "Parse Error during template parsing, at line "
-          + e.getLineNumber() + ", column " + e.getColumnNumber();
-      // Attempt to extract (some) of the input to provide a more useful
-      // error message.
-      try {
-        input.reset();
-        char[] buf = new char[200];
-        int len = input.read(buf);
-        if (len > 0) {
-          logMessage += " of input " + new String(buf, 0, len);
-        }
-      } catch (IOException e1) {
- // We tried, but resetting/reading from the input stream failed. Sorry.
-        logMessage += " <failed to read input snippet>";
-      }
-      logger.log(TreeLogger.ERROR, logMessage + ": " + e);
-      throw new UnableToCompleteException();
-    } catch (SAXException e) {
-      logger.log(TreeLogger.ERROR, "Error during template parsing:", e);
-      throw new UnableToCompleteException();
-    }
-  }

   /**
* Parses a {...@link String} that may contain template parameters of the form
    * {...@code {n}} into corresponding literal and parameter
    * {...@link ParsedHtmlTemplate.TemplateChunk}s.
    *
-   * <p>Parameters will be tagged with the {...@link HtmlContext} provided.
-   *
- * <p>If {...@code contextAtStart} is not {...@code null} and the parsed template - * starts with a parameter (i.e., is of the form {...@code "{n}..."}), this first
-   * parameter will be tagged with that context; any other parameters will
-   * be tagged with context {...@code context}.
-   *
- * @param context the context with which to tag parameters occurring in the
-   *        template
- * @param contextAtStart if not {...@code null}, the context with which to tag a
-   *        parameter that occurs at the very beginning of the template
    * @param template the template {...@link String} to parse
+ * @throws UnableToCompleteException if an unrecoverable parse error occurs
    */
   // @VisibleForTesting
-  void parseTemplateString(HtmlContext context,
-      HtmlContext contextAtStart, String template) {
+  void parseTemplate(String template) throws UnableToCompleteException {
+    this.template = template;
+    this.parsePosition = 0;
     Matcher match = TEMPLATE_PARAM_PATTERN.matcher(template);

-    boolean firstMatch = true;
-    int endOfLastMatch = 0;
+    int endOfPreviousMatch = 0;
     while (match.find()) {
-      if (match.start() > endOfLastMatch) {
+      if (match.start() > endOfPreviousMatch) {
         // There is a non-empty string between the previous match and this
         // match; add this as a literal chunk to the parsed representation.
-        appendLiteral(template.substring(endOfLastMatch, match.start()));
+        parseAndAppendTemplateSegment(
+            template.substring(endOfPreviousMatch, match.start()));
+        parsePosition = match.start();
       }

       int paramIndex = Integer.parseInt(match.group(1));
-      if (firstMatch && (match.start() == 0) && (contextAtStart != null)) {
-        parsedTemplate.addParameter(new ParameterChunk(contextAtStart,
-            paramIndex));
-      } else {
- parsedTemplate.addParameter(new ParameterChunk(context, paramIndex));
-      }
-
-      firstMatch = false;
-      endOfLastMatch = match.end();
+      parsePosition = match.end();
+      parsedTemplate.addParameter(
+          new ParameterChunk(getHtmlContextFromParseState(), paramIndex));
+
+      endOfPreviousMatch = match.end();
     }

     // Add a literal chunk for the substring after the last match, if any.
-    if (endOfLastMatch < template.length()) {
-      parsedTemplate.addLiteral(template.substring(endOfLastMatch));
+    if (endOfPreviousMatch < template.length()) {
+ parseAndAppendTemplateSegment(template.substring(endOfPreviousMatch));
+    }
+
+    if (!streamHtmlParser.getState().equals(HtmlParser.STATE_TEXT)) {
+      logger.log(TreeLogger.ERROR,
+          "Template does not end in inner-HTML context: " + template);
+      throw new UnableToCompleteException();
     }
   }

   /**
- * Parses a {...@link String} that may contain template parameters of the form
-   * {...@code {n}} into corresponding literal and parameter
-   * {...@link ParsedHtmlTemplate.TemplateChunk}s.
+   * Determines the {...@link HtmlContext} in the parser's current state.
    *
-   * <p>Parameters will be tagged with the {...@link HtmlContext} provided.
+   * <p>
+   * This method translates from the stream HTML parser's internal state
+ * representation to our HTML context representation, and is intended to be
+   * invoked at the point where a template variable is encountered.
    *
- * @param context the context with which to tag parameters occurring in the
-   *        template
-   * @param template the template to parse
+   * <p>
+ * 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
    */
-  // @VisibleForTesting
-  void parseTemplateString(HtmlContext context, String template) {
-    parseTemplateString(context, null, template);
+  private HtmlContext getHtmlContextFromParseState()
+      throws UnableToCompleteException {
+
+    if (streamHtmlParser.getState().equals(HtmlParser.STATE_ERROR)) {
+      logger.log(TreeLogger.ERROR,
+          "Parsing template resulted in parse error: "
+              + getTemplateParsedSoFar());
+      throw new UnableToCompleteException();
+    }
+
+    if (streamHtmlParser.inJavascript()) {
+      logger.log(TreeLogger.ERROR,
+          "Template variables in javascript context are not supported: "
+              + getTemplateParsedSoFar());
+      throw new UnableToCompleteException();
+    }
+    if (streamHtmlParser.getState().equals(HtmlParser.STATE_COMMENT)) {
+      logger.log(TreeLogger.ERROR,
+          "Template variables inside HTML comments are not supported: "
+              + getTemplateParsedSoFar());
+      throw new UnableToCompleteException();
+    } else if (streamHtmlParser.getState().equals(HtmlParser.STATE_TEXT)
+        && !streamHtmlParser.inCss()) {
+      return new HtmlContext(HtmlContext.Type.TEXT);
+ } else if (streamHtmlParser.getState().equals(HtmlParser.STATE_VALUE)) {
+      final String tag = streamHtmlParser.getTag();
+      final String attribute = streamHtmlParser.getAttribute();
+      Preconditions.checkState(!tag.equals(""),
+          "streamHtmlParser.getTag() should not be empty  while in "
+ + "attribute value context; at %s", getTemplateParsedSoFar());
+      Preconditions.checkState(!attribute.equals(""),
+          "streamHtmlParser.getAttribute() should not be empty while in "
+ + "attribute value context; at %s", getTemplateParsedSoFar());
+      if (!streamHtmlParser.isAttributeQuoted()) {
+        logger.log(TreeLogger.ERROR,
+            "Template variable in unquoted attribute value: "
+                + getTemplateParsedSoFar());
+        throw new UnableToCompleteException();
+      }
+      if (streamHtmlParser.isUrlStart()) {
+        return new HtmlContext(HtmlContext.Type.URL_START, tag, attribute);
+      } else if (streamHtmlParser.inCss()) {
+ return new HtmlContext(HtmlContext.Type.CSS_ATTRIBUTE, tag, attribute);
+      } else {
+        return new HtmlContext(
+            HtmlContext.Type.ATTRIBUTE_VALUE, tag, attribute);
+      }
+    } else if (streamHtmlParser.inCss()) {
+      return new HtmlContext(HtmlContext.Type.CSS);
+    } else if (streamHtmlParser.getState().equals(HtmlParser.STATE_TAG)
+        || streamHtmlParser.inAttribute()) {
+      logger.log(TreeLogger.ERROR,
+ "Template variables in tags or in attribute names are not supported: "
+              + getTemplateParsedSoFar());
+      throw new UnableToCompleteException();
+    }
+
+    logger.log(TreeLogger.ERROR,
+        "unhandeled/illegal parse state" + streamHtmlParser.getState());
+    throw new UnableToCompleteException();
   }

   /**
-   * Appends a literal string to the parsed template representation.
+   * Returns the prefix of the template string that has been parsed so far.
+   */
+  private String getTemplateParsedSoFar() {
+    return template.substring(0, parsePosition);
+  }
+
+  /**
+ * Feeds a literal string to the stream parser and appends it to the parsed
+   * template representation.
    *
- * <p>The {...@code literal} will be appended without processing; any XML/XHTML - * markup as well as template parameters occurring in the {...@code literal} will
-   * not be parsed.
-   *
-   * @param literal the string to append
+   * @param segment the template segment to parse and append to the parsed
+   *          template representation
+ * @throws UnableToCompleteException if an unrecoverable parse error occurs
    */
-  private void appendLiteral(String literal) {
-    parsedTemplate.addLiteral(literal);
+  private void parseAndAppendTemplateSegment(String segment)
+      throws UnableToCompleteException {
+    try {
+      streamHtmlParser.parse(segment);
+    } catch (ParseException cause) {
+      logger.log(TreeLogger.ERROR,
+ "Parse exception when parsing segment '" + segment + "' of template '"
+              + template + "'", cause);
+      throw new UnableToCompleteException();
+    }
+    parsedTemplate.addLiteral(segment);
   }
 }
=======================================
--- /trunk/user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java Fri Aug 27 03:13:26 2010 +++ /trunk/user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java Thu Dec 9 08:34:53 2010
@@ -1,12 +1,12 @@
 /*
  * Copyright 2009 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
@@ -22,7 +22,7 @@
 import java.util.List;

 /**
- * A representation of a parsed (X)HTML template.
+ * A representation of a parsed HTML template.
  *
  * <p>A parsed template is represented as a sequence of template chunks.
  *
@@ -44,25 +44,27 @@
     /**
      * The possible types of HTML context.
      */
-    // TODO(xtof): Possibly add support for other contexts, such as style.
     static enum Type {
       /**
- * The undefined HTML context. Contexts of this type are meant to be used
-       * in cases where a parser is used that is not HTML context-aware.
-       */
-      UNDEFINED,
-      /**
        * Regular inner text HTML context.
        */
       TEXT,
       /**
-       * HTML attribute context.
+       * Value of a HTML attribute.
        */
-      ATTRIBUTE,
+      ATTRIBUTE_VALUE,
       /**
-       * At the very start of an attribute.
+       * At the very start of a URL-valued attribute.
        */
-      ATTRIBUTE_START,
+      URL_START,
+      /**
+       * CSS (style) context.
+       */
+      CSS,
+      /**
+       * CSS (style) attribute context.
+       */
+      CSS_ATTRIBUTE
     }

     private final Type type;
@@ -88,9 +90,6 @@
      *        applicable; null otherwise
      */
     public HtmlContext(Type type, String tag, String attribute) {
-      Preconditions.checkArgument((type != Type.UNDEFINED)
-          || ((tag == null) && (attribute == null)),
-          "tag and attribute must be null for context \"UNDEFINED\"");
       Preconditions.checkArgument((type != Type.TEXT)
           || ((tag == null) && (attribute == null)),
           "tag and attribute must be null for context \"TEXT\"");
@@ -210,7 +209,7 @@

   /**
    * Represents a parsed chunk of a template.
-   *
+   *
* <p>There are two kinds of chunks: Those representing literal strings and
    * those representing template parameters.
    */
@@ -242,7 +241,7 @@

   /**
    * Adds a literal string to the template.
-   *
+   *
* If the currently last chunk of the parsed template is a literal chunk, the * provided string literal will be appended to that chunk. I.e., consecutive
    * literal chunks are automatically coalesced.
@@ -260,7 +259,7 @@

   /**
    * Adds a parameter chunk to the template.
-   *
+   *
    * @param chunk the chunk to be added
    */
   public void addParameter(ParameterChunk chunk) {
@@ -269,7 +268,7 @@

   /**
    * Returns the chunks of this parsed template.
-   *
+   *
    * <p>The returned list is unmodifiable.
    */
   public List<TemplateChunk> getChunks() {
=======================================
--- /trunk/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java Thu Sep 2 08:33:16 2010 +++ /trunk/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java Thu Dec 9 08:34:53 2010
@@ -27,18 +27,13 @@
 import com.google.gwt.safehtml.rebind.ParsedHtmlTemplate.LiteralChunk;
 import com.google.gwt.safehtml.rebind.ParsedHtmlTemplate.ParameterChunk;
 import com.google.gwt.safehtml.rebind.ParsedHtmlTemplate.TemplateChunk;
-import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.safehtml.shared.OnlyToBeUsedInGeneratedCodeStringBlessedAsSafeHtml;
 import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.safehtml.shared.UriUtils;
 import com.google.gwt.user.rebind.AbstractGeneratorClassCreator;
 import com.google.gwt.user.rebind.AbstractMethodCreator;

-import java.io.StringReader;
-import java.util.Arrays;
-import java.util.Set;
-import java.util.TreeSet;
-
 /**
  * Method body code generator for implementations of
  * {...@link com.google.gwt.safehtml.client.SafeHtmlTemplates}.
@@ -71,31 +66,6 @@
    */
   private static final String URI_UTILS_FQCN = UriUtils.class.getName();

-  /**
- * The set of the names of the HTML attributes whose values are interpreted
-   * as URIs.
-   *
- * <p>See <a href="http://www.w3.org/TR/html4/index/attributes.html";>Index
-   * of Attributes</a> for reference.
-   */
-  private static final Set<String> URI_VALUED_ATTRIBUTES;
-
-  static {
-    URI_VALUED_ATTRIBUTES = new TreeSet<String>();
-    URI_VALUED_ATTRIBUTES.addAll(Arrays.asList(
-          "action",
-          "archive",
-          "background",
-          "cite",
-          "classid",
-          "codebase",
-          "data",
-          "dynsrc",
-          "href",
-          "longdesc",
-          "src",
-          "usemap"));
-  }

   public SafeHtmlTemplatesImplMethodCreator(
       AbstractGeneratorClassCreator classCreator) {
@@ -162,9 +132,6 @@
       HtmlContext htmlContext, String formalParameterName,
       JType parameterType) {

- // TODO(xtof): check attr name against a set of attributes we know we can
-    //     handle (i.e., excluding things like onFoo, style, etc).
-
     /*
* Build up the expression from the "inside out", i.e. start with the formal * parameter, convert to string if necessary, then wrap in validators if
@@ -178,10 +145,7 @@
       expression = "String.valueOf(" + expression + ")";
     }

-    boolean isParameterAtStartOfUriAttribute =
-        htmlContext.getType() == HtmlContext.Type.ATTRIBUTE_START
-            && URI_VALUED_ATTRIBUTES.contains(htmlContext.getAttribute());
-    if (isParameterAtStartOfUriAttribute) {
+    if ((htmlContext.getType() == HtmlContext.Type.URL_START)) {
       expression = URI_UTILS_FQCN + ".sanitizeUri(" + expression + ")";
     }

@@ -197,7 +161,7 @@
* Generates code that renders the provided HTML template into an instance
    * of the {...@link SafeHtml} type.
    *
-   * <p>The template is parsed as a (X)HTML template (see
+   * <p>The template is parsed as a HTML template (see
    * {...@link HtmlTemplateParser}).  From the template's parsed form, code is
* generated that, when executed, will emit an instantiation of the template.
    * The generated code appropriately escapes and/or sanitizes template
@@ -221,7 +185,7 @@
     indent();

     HtmlTemplateParser parser = new HtmlTemplateParser(logger);
-    parser.parseXHtml(new StringReader(template));
+    parser.parseTemplate(template);

     for (TemplateChunk chunk : parser.getParsedTemplate().getChunks()) {
       if (chunk.getKind() == TemplateChunk.Kind.LITERAL) {
@@ -253,32 +217,55 @@
   /**
    * Emits an expression corresponding to a template parameter.
    *
- * <p>The expression emitted applies appropriate escaping/sanitization to the
+   * <p>
+ * The expression emitted applies appropriate escaping/sanitization to the * parameter's value, depending on the parameter's HTML context, and the Java
    * type of the corresponding template method parameter.
    *
    * @param logger the logger to log failures to
* @param htmlContext the HTML context in which the corresponding template
-   *        variable occurs in
+   *          variable occurs in
    * @param formalParameterName the name of the template method's formal
-   *        parameter corresponding to the expression being emitted
+   *          parameter corresponding to the expression being emitted
* @param parameterType the Java type of the corresponding template method's
-   *        parameter
+   *          parameter
    */
   private void emitParameterExpression(TreeLogger logger,
       HtmlContext htmlContext, String formalParameterName,
       JType parameterType) {
     print(".append(");
-    if (htmlContext.getType() == HtmlContext.Type.TEXT) {
- emitTextContextParameterExpression(formalParameterName, parameterType);
-    } else if (htmlContext.getType() == HtmlContext.Type.ATTRIBUTE
-        || htmlContext.getType() == HtmlContext.Type.ATTRIBUTE_START) {
-      emitAttributeContextParameterExpression(logger, htmlContext,
-          formalParameterName, parameterType);
-    } else {
-      throw new IllegalStateException(
-          "unknown HTML context for formal template parameter "
-              + formalParameterName + ": " + htmlContext);
+    switch (htmlContext.getType()) {
+      case CSS:
+        // TODO(xtof): Improve support for CSS.
+        // The stream parser does not parse CSS; we could however improve
+        // safety via sub-formats that specify the in-css context.
+ logger.log(TreeLogger.WARN, "Template with variable in CSS context: " + + "The template code generator cannot guarantee HTML-safety of "
+            + "the template -- please inspect manually");
+ emitTextContextParameterExpression(formalParameterName, parameterType);
+        break;
+      case TEXT:
+ emitTextContextParameterExpression(formalParameterName, parameterType);
+        break;
+
+      case CSS_ATTRIBUTE:
+        // TODO(xtof): Improve support for CSS.
+ logger.log(TreeLogger.WARN, "Template with variable in CSS context: " + + "The template code generator cannot guarantee HTML-safety of "
+            + "the template -- please inspect manually");
+        emitAttributeContextParameterExpression(logger, htmlContext,
+            formalParameterName, parameterType);
+        break;
+      case URL_START:
+      case ATTRIBUTE_VALUE:
+        emitAttributeContextParameterExpression(logger, htmlContext,
+            formalParameterName, parameterType);
+        break;
+
+      default:
+          throw new IllegalStateException(
+              "unknown HTML context for formal template parameter "
+                  + formalParameterName + ": " + htmlContext);
     }
     println(")");
   }
=======================================
--- /trunk/user/test/com/google/gwt/cell/client/ImageCellTest.java Thu Sep 2 08:33:16 2010 +++ /trunk/user/test/com/google/gwt/cell/client/ImageCellTest.java Thu Dec 9 08:34:53 2010
@@ -42,7 +42,7 @@

   @Override
   protected String getExpectedInnerHtml() {
-    return "<img src=\"test.png\"></img>";
+    return "<img src=\"test.png\"/>";
   }

   @Override
=======================================
--- /trunk/user/test/com/google/gwt/safehtml/client/SafeHtmlTemplatesTest.java Fri Aug 27 03:13:26 2010 +++ /trunk/user/test/com/google/gwt/safehtml/client/SafeHtmlTemplatesTest.java Thu Dec 9 08:34:53 2010
@@ -99,11 +99,11 @@

   public void testTemplateWithTwoPartUriAttribute() {
     Assert.assertEquals(
- "<span><img src=\"" + GOOD_URL_ESCAPED + "/x&amp;y\"></img></span>",
+        "<span><img src=\"" + GOOD_URL_ESCAPED + "/x&amp;y\"/></span>",
         templates.templateWithTwoPartUriAttribute(
             GOOD_URL, "x&y").asString());
     Assert.assertEquals(
-        "<span><img src=\"#/x&amp;y\"></img></span>",
+        "<span><img src=\"#/x&amp;y\"/></span>",
         templates.templateWithTwoPartUriAttribute(
             BAD_URL, "x&y").asString());
   }
=======================================
--- /trunk/user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java Fri Aug 27 03:13:26 2010 +++ /trunk/user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java Thu Dec 9 08:34:53 2010
@@ -17,17 +17,14 @@

 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.safehtml.rebind.ParsedHtmlTemplate.HtmlContext;
+import com.google.gwt.dev.util.UnitTestTreeLogger;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;

 import junit.framework.TestCase;

-import java.io.StringReader;
-
 /**
  * Tests for {...@link HtmlTemplateParser}.
  */
-//TODO(xtof): unit tests for parse failures
 public final class HtmlTemplateParserTest extends TestCase {

   private TreeLogger logger;
@@ -36,149 +33,230 @@
   public void setUp() {
     logger = new PrintWriterTreeLogger();
   }
-
+
   /*
-   * We use the string representation of ParsedHtmlTemplate to express
- * expected results. The unit test for ParsedHtmlTemplate establishes that
-   * this string representation accurately represents the structure of the
-   * parsed template.
+ * We use the string representation of ParsedHtmlTemplate to express expected + * results. The unit test for ParsedHtmlTemplate establishes that this string + * representation accurately represents the structure of the parsed template.
    */
-  private void assertParseTemplateStringResult(String expected,
-                                               String template) {
+  private void assertParseTemplateResult(String expected, String template)
+      throws UnableToCompleteException {
     HtmlTemplateParser parser = new HtmlTemplateParser(logger);
-    HtmlContext ctx = new HtmlContext(HtmlContext.Type.UNDEFINED);
-    parser.parseTemplateString(ctx, template);
+    parser.parseTemplate(template);
     assertEquals(expected, parser.getParsedTemplate().toString());
   }

-  public void testParseTemplateString() {
-    assertParseTemplateStringResult("[]", "");
-    assertParseTemplateStringResult("[L(foo)]", "foo");
-    assertParseTemplateStringResult(
-        "[L(foo), P((UNDEFINED,null,null),0), L(bar)]",
+ public void testParseTemplate_noMarkup() throws UnableToCompleteException {
+    assertParseTemplateResult("[]", "");
+    assertParseTemplateResult("[L(foo)]", "foo");
+    assertParseTemplateResult(
+        "[L(foo), P((TEXT,null,null),0), L(bar)]",
         "foo{0}bar");
-    assertParseTemplateStringResult(
-        "[L(foo), P((UNDEFINED,null,null),0), "
-            + "P((UNDEFINED,null,null),1), L(bar)]",
+    assertParseTemplateResult(
+        "[L(foo), P((TEXT,null,null),0), "
+            + "P((TEXT,null,null),1), L(bar)]",
         "foo{0}{1}bar");
-    assertParseTemplateStringResult(
-        "[L(foo), P((UNDEFINED,null,null),0), L(b), "
-            + "P((UNDEFINED,null,null),1), L(bar)]",
+    assertParseTemplateResult(
+        "[L(foo), P((TEXT,null,null),0), L(b), "
+            + "P((TEXT,null,null),1), L(bar)]",
         "foo{0}b{1}bar");
-    assertParseTemplateStringResult(
-        "[L(foo), P((UNDEFINED,null,null),0), L(baz), "
-            + "P((UNDEFINED,null,null),1), L(bar)]",
+    assertParseTemplateResult(
+        "[L(foo), P((TEXT,null,null),0), L(baz), "
+            + "P((TEXT,null,null),1), L(bar)]",
         "foo{0}baz{1}bar");
-    assertParseTemplateStringResult(
-        "[P((UNDEFINED,null,null),0), L(foo), P((UNDEFINED,null,null),0), "
-            + "P((UNDEFINED,null,null),1), L(bar)]",
+    assertParseTemplateResult(
+        "[P((TEXT,null,null),0), L(foo), P((TEXT,null,null),0), "
+            + "P((TEXT,null,null),1), L(bar)]",
         "{0}foo{0}{1}bar");
-    assertParseTemplateStringResult(
-        "[P((UNDEFINED,null,null),0), L(foo), P((UNDEFINED,null,null),0), "
-            + "P((UNDEFINED,null,null),1), L(b)]",
+    assertParseTemplateResult(
+        "[P((TEXT,null,null),0), L(foo), P((TEXT,null,null),0), "
+            + "P((TEXT,null,null),1), L(b)]",
         "{0}foo{0}{1}b");
-    assertParseTemplateStringResult(
-        "[L(foo), P((UNDEFINED,null,null),0), P((UNDEFINED,null,null),1), "
-            + "L(bar), P((UNDEFINED,null,null),2)]",
+    assertParseTemplateResult(
+        "[L(foo), P((TEXT,null,null),0), P((TEXT,null,null),1), "
+            + "L(bar), P((TEXT,null,null),2)]",
         "foo{0}{1}bar{2}");
-    assertParseTemplateStringResult(
-        "[L(f), P((UNDEFINED,null,null),2), P((UNDEFINED,null,null),1), "
-            + "L(bar), P((UNDEFINED,null,null),2)]",
+    assertParseTemplateResult(
+        "[L(f), P((TEXT,null,null),2), P((TEXT,null,null),1), "
+            + "L(bar), P((TEXT,null,null),2)]",
         "f{2}{1}bar{2}");
-
+
     // Test degenerate cases with curly braces that don't match a parameter
     // pattern; these are treated as regular string literals.
-    assertParseTemplateStringResult("[L(foo{)]", "foo{");
-    assertParseTemplateStringResult("[L(}foo)]", "}foo");
-    assertParseTemplateStringResult("[L(foo{text})]", "foo{text}");
+    assertParseTemplateResult("[L(foo{)]", "foo{");
+    assertParseTemplateResult("[L(}foo)]", "}foo");
+    assertParseTemplateResult("[L(foo{text})]", "foo{text}");
   }

-  private void assertParseTemplateStringResultWithAtStartContext(
-      String expected, String template) {
-    HtmlTemplateParser parser = new HtmlTemplateParser(logger);
- HtmlContext ctx = new HtmlContext(HtmlContext.Type.ATTRIBUTE, "img", "src");
-    HtmlContext ctxAtStart =
-        new HtmlContext(HtmlContext.Type.ATTRIBUTE_START, "img", "src");
-    parser.parseTemplateString(ctx, ctxAtStart, template);
-    assertEquals(expected, parser.getParsedTemplate().toString());
-  }
-
-  public void testParseTemplateStringWithAtStartContext() {
-    assertParseTemplateStringResultWithAtStartContext(
-        "[P((ATTRIBUTE_START,img,src),0)]",
-        "{0}");
-    assertParseTemplateStringResultWithAtStartContext(
-        "[L(x), P((ATTRIBUTE,img,src),0)]",
-        "x{0}");
-    assertParseTemplateStringResultWithAtStartContext(
-        "[L(boo), P((ATTRIBUTE,img,src),0)]",
-        "boo{0}");
-
-    assertParseTemplateStringResultWithAtStartContext(
-        "[P((ATTRIBUTE_START,img,src),0), L(foo)]",
-        "{0}foo");
-    assertParseTemplateStringResultWithAtStartContext(
-        "[P((ATTRIBUTE_START,img,src),0), P((ATTRIBUTE,img,src),1)]",
-        "{0}{1}");
-    assertParseTemplateStringResultWithAtStartContext(
-        "[P((ATTRIBUTE_START,img,src),0), L(X), P((ATTRIBUTE,img,src),1)]",
-        "{0}X{1}");
-    assertParseTemplateStringResultWithAtStartContext(
-        "[L(X), P((ATTRIBUTE,img,src),0), P((ATTRIBUTE,img,src),1)]",
-        "X{0}{1}");
-   }
-
-  private void assertParseXHtmlResult(String expected, String template)
-      throws UnableToCompleteException {
-    HtmlTemplateParser parser = new HtmlTemplateParser(logger);
-    parser.parseXHtml(new StringReader(template));
-    assertEquals(expected, parser.getParsedTemplate().toString());
-  }
-
-  public void testParseXHtml() throws UnableToCompleteException {
+ public void testParseTemplate_withMarkup() throws UnableToCompleteException {
     // Basic cases.
-    assertParseXHtmlResult("[L(<b>foo</b>)]", "<b>foo</b>");
-    assertParseXHtmlResult(
+    assertParseTemplateResult("[L(<b>foo</b>)]", "<b>foo</b>");
+    assertParseTemplateResult(
         "[L(<span>foo<b>), P((TEXT,null,null),0), L(</b></span>)]",
         "<span>foo<b>{0}</b></span>");
-    assertParseXHtmlResult(
-        "[L(<span>foo<b>), P((TEXT,null,null),1), L(</b>), "
-            + "P((TEXT,null,null),0), L(</span>)]",
+    assertParseTemplateResult(
+        ("[L(<span>foo<b>), P((TEXT,null,null),1), L(</b>), "
+            + "P((TEXT,null,null),0), L(</span>)]"),
         "<span>foo<b>{1}</b>{0}</span>");

- // Check that tags and attributes are lower-cased, but inner text is not.
-    assertParseXHtmlResult("[L(<b id=\"bAr\">fOo</b>)]",
-                           "<B Id=\"bAr\">fOo</B>");
+    // Check that case is not modified.
+    assertParseTemplateResult(
+        "[L(<B Id=\"bAr\">fOo</B>)]", "<B Id=\"bAr\">fOo</B>");

     // Verify correct handling/escaping of HTML metacharacters and
     // CDATA sections.
-    assertParseXHtmlResult(
-        "[L(<span>foo&amp;bar<b>), P((TEXT,null,null),1), "
-            + "L(</b>foo-cdata &lt;baz&gt;), P((TEXT,null,null),0), "
-            + "L(</span>)]",
+    assertParseTemplateResult(
+        ("[L(<span>foo&amp;bar<b>), P((TEXT,null,null),1), "
+            + "L(</b><![CDATA[foo-cdata <baz>]]>), P((TEXT,null,null),0), "
+            + "L(</span>)]"),
"<span>foo&amp;bar<b>{1}</b><![CDATA[foo-cdata <baz>]]>{0}</span>");

-    // Check correct handling of ATTRIBUTE vs ATTRIBUTE_START context.
-    assertParseXHtmlResult(
-        "[L(<a href=\"), P((ATTRIBUTE_START,a,href),0), "
-            + "L(\">), P((TEXT,null,null),1), L(</a>)]",
+    // Check correct handling of ATTRIBUTE_VALUE vs URL_START context.
+    assertParseTemplateResult(("[L(<a href=\"), P((URL_START,a,href),0), "
+        + "L(\">), P((TEXT,null,null),1), L(</a>)]"),
         "<a href=\"{0}\">{1}</a>");
-    assertParseXHtmlResult(
-        "[L(<a href=\"http://), P((ATTRIBUTE,a,href),0), "
-            + "L(\">), P((TEXT,null,null),1), L(</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>");
-    assertParseXHtmlResult(
-        "[L(<a href=\"), P((ATTRIBUTE_START,a,href),0), "
-            + "L(/), P((ATTRIBUTE,a,href),1), "
-            + "L(\">), P((TEXT,null,null),2), L(</a>)]",
+    assertParseTemplateResult(("[L(<a href=\"), P((URL_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>");

     // Verify correct escaping in attributes.
-    assertParseXHtmlResult(
-        "[L(<a href=\"http://...&amp;), "
-            + "P((ATTRIBUTE,a,href),0), "
-            + "L(=), P((ATTRIBUTE,a,href),1), "
-            + "L(\">), P((TEXT,null,null),2), L(</a>)]",
+    assertParseTemplateResult(
+ ("[L(<a href=\"http://...&amp;), " + "P((ATTRIBUTE_VALUE,a,href),0), "
+            + "L(=), P((ATTRIBUTE_VALUE,a,href),1), "
+            + "L(\">), P((TEXT,null,null),2), L(</a>)]"),
         "<a href=\"http://...&amp;{0}={1}\";>{2}</a>");
+
+    // Test correct detection of CSS context.
+    assertParseTemplateResult(
+ "[L(<div class=\"), P((ATTRIBUTE_VALUE,div,class),0), L(\" style=\"), "
+            + "P((CSS_ATTRIBUTE,div,style),2), L(\">Hello ), "
+            + "P((TEXT,null,null),1)]",
+        "<div class=\"{0}\" style=\"{2}\">Hello {1}");
+    assertParseTemplateResult(
+        "[L(<div>), P((TEXT,null,null),0), L(<style>foo ), "
+            + "P((CSS,null,null),1), L(</style>)]",
+        "<div>{0}<style>foo {1}</style>");
+
+    // Test that javascript contexts without variables are allowed
+    assertParseTemplateResult(
+        "[L(<div onClick=alert() class=\"), "
+            + "P((ATTRIBUTE_VALUE,div,class),0), L(\">)]",
+        "<div onClick=alert() class=\"{0}\">");
+  }
+
+  private void assertParsingTemplateEndingInNonInnerHtmlContextFails(
+      String template) {
+    assertParseFails(
+ "Template does not end in inner-HTML context: ", template, template);
+  }
+
+  public void testParseTemplate_endingInNonInnerHtmlContextFails() {
+    assertParsingTemplateEndingInNonInnerHtmlContextFails("<div class=");
+    assertParsingTemplateEndingInNonInnerHtmlContextFails("<div class=\"");
+ assertParsingTemplateEndingInNonInnerHtmlContextFails("<div class=\"{0}"); + assertParsingTemplateEndingInNonInnerHtmlContextFails("<div class=\"{0}\"");
+    assertParsingTemplateEndingInNonInnerHtmlContextFails(
+        "<div class=\"{0}\" foo");
+    assertParsingTemplateEndingInNonInnerHtmlContextFails(
+        "<div class=\"{0}\" foo=bar");
+    assertParsingTemplateEndingInNonInnerHtmlContextFails(
+        "<div class=\"{0}\" foo=bar>{1}<a");
+    assertParsingTemplateEndingInNonInnerHtmlContextFails(
+        "<div class=\"{0}\" foo=bar>{1}<a href=");
+  }
+
+  private void assertTemplateVariableInUnquotedAttributeFails(
+      String template, String failAtPrefix) {
+    assertParseFails("Template variable in unquoted attribute value: ",
+        template, failAtPrefix);
+  }
+
+ public void testParseTemplate_templateVariableInUnquotedAttributeFails() {
+    assertTemplateVariableInUnquotedAttributeFails(
+        "<div class={0}>", "<div class={0}");
+    assertTemplateVariableInUnquotedAttributeFails(
+        "<div style=blah class={0}>", "<div style=blah class={0}");
+    assertTemplateVariableInUnquotedAttributeFails(
+        "<div style=blah class=blah{0}>", "<div style=blah class=blah{0}");
+    assertTemplateVariableInUnquotedAttributeFails(
+        "<div style=blah class=\"{0}\" foo={1}>bar</div><a href={3}>",
+        "<div style=blah class=\"{0}\" foo={1}");
+    assertTemplateVariableInUnquotedAttributeFails(
+        "<div style=blah class=\"{0}\" foo=\"{1}\">bar</div><a href={3}>",
+        "<div style=blah class=\"{0}\" foo=\"{1}\">bar</div><a href={3}");
+    assertTemplateVariableInUnquotedAttributeFails(
+ "<div style=blah class=\"{0}\"foo=\"{1}\">bar</div><a href=http://{3}>", + "<div style=blah class=\"{0}\"foo=\"{1}\">bar</div><a href=http://{3}";);
+  }
+
+  private void assertTemplateVariableInJsContextFails(
+      String template, String failAtPrefix) {
+    assertParseFails(
+        "Template variables in javascript context are not supported: ",
+        template, failAtPrefix);
+  }
+
+  public void testParseTemplate_templateVariableInJsContextFails() {
+    assertTemplateVariableInJsContextFails(
+        "<div onClick=\"{0}\">", "<div onClick=\"{0}");
+    assertTemplateVariableInJsContextFails(
+        "<div onClick=\"alert({0})\">", "<div onClick=\"alert({0}");
+    assertTemplateVariableInJsContextFails(
+        "foo<script language=blah>{0};", "foo<script language=blah>{0}");
+    assertTemplateVariableInJsContextFails(
+        "foo<script language=blah>alert({0});",
+        "foo<script language=blah>alert({0}");
+  }
+
+  private void assertTemplateVariableInCommentContextFails(
+      String template, String failAtPrefix) {
+    assertParseFails(
+        "Template variables inside HTML comments are not supported: ",
+        template, failAtPrefix);
+  }
+
+  public void testParseTemplate_templateVariableInCommentFails() {
+    assertTemplateVariableInCommentContextFails(
+        "<!-- Hello {0}-->", "<!-- Hello {0}");
+    assertTemplateVariableInCommentContextFails(
+        "<!--<script language=blah>alert({0});",
+        "<!--<script language=blah>alert({0}");
+  }
+
+  private void assertTemplateVariableInAttributeNameFails(
+      String template, String failAtPrefix) {
+    assertParseFails(
+ "Template variables in tags or in attribute names are not supported: ",
+        template, failAtPrefix);
+  }
+
+  public void testParseTemplate_templateVariableInAttributeNameFails() {
+    assertTemplateVariableInAttributeNameFails(
+        "<div style=\"{0}\" {1}=\"{2}\">", "<div style=\"{0}\" {1}");
+    assertTemplateVariableInAttributeNameFails(
+        "<div style=\"{0}\" foo{1}=\"{2}\">", "<div style=\"{0}\" foo{1}");
+  }
+
+  private void assertParseFails(
+ String expectedError, final String template, final String failAtPrefix) { + UnitTestTreeLogger.Builder loggerBuilder = new UnitTestTreeLogger.Builder();
+    loggerBuilder.expectError(
+        expectedError
+            + failAtPrefix, null);
+    UnitTestTreeLogger logger = loggerBuilder.createLogger();
+
+    HtmlTemplateParser parser = new HtmlTemplateParser(logger);
+    try {
+      parser.parseTemplate(template);
+      fail("Parsing invalid template did not fail."
+ + " Parsed representation: " + parser.getParsedTemplate().toString());
+    } catch (UnableToCompleteException e) {
+      logger.assertCorrectLogEntries();
+    }
   }
 }
=======================================
--- /trunk/user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java Fri Aug 27 03:13:26 2010 +++ /trunk/user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java Thu Dec 9 08:34:53 2010
@@ -120,7 +120,7 @@
     parsed.addLiteral("<a");
     parsed.addLiteral(" href=\"");
     parsed.addParameter(new ParameterChunk(new HtmlContext(
-        HtmlContext.Type.ATTRIBUTE_START, "a", "href"), 1));
+        HtmlContext.Type.URL_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.ATTRIBUTE_START, paramChunk.getContext().getType());
+        HtmlContext.Type.URL_START, paramChunk.getContext().getType());
     assertEquals("a", paramChunk.getContext().getTag());
     assertEquals("href", paramChunk.getContext().getAttribute());
     assertEquals(1, paramChunk.getParameterIndex());
-    assertEquals("P((ATTRIBUTE_START,a,href),1)", paramChunk.toString());
+    assertEquals("P((URL_START,a,href),1)", paramChunk.toString());

     assertEquals(
         "[P((TEXT,null,null),0), L(<a href=\"), "
-            + "P((ATTRIBUTE_START,a,href),1)]",
+            + "P((URL_START,a,href),1)]",
         parsed.toString());
   }
 }

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to