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 {
* @Template("<span class=\"{3}\">{0}: <a
href=\"{1}\">{2}</a></span>")
@@ -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 @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&y\"></img></span>",
+ "<span><img src=\"" + GOOD_URL_ESCAPED + "/x&y\"/></span>",
templates.templateWithTwoPartUriAttribute(
GOOD_URL, "x&y").asString());
Assert.assertEquals(
- "<span><img src=\"#/x&y\"></img></span>",
+ "<span><img src=\"#/x&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&bar<b>), P((TEXT,null,null),1), "
- + "L(</b>foo-cdata <baz>), P((TEXT,null,null),0), "
- + "L(</span>)]",
+ assertParseTemplateResult(
+ ("[L(<span>foo&bar<b>), P((TEXT,null,null),1), "
+ + "L(</b><![CDATA[foo-cdata <baz>]]>), P((TEXT,null,null),0), "
+ + "L(</span>)]"),
"<span>foo&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://...&), "
- + "P((ATTRIBUTE,a,href),0), "
- + "L(=), P((ATTRIBUTE,a,href),1), "
- + "L(\">), P((TEXT,null,null),2), L(</a>)]",
+ assertParseTemplateResult(
+ ("[L(<a href=\"http://...&), "
+ "P((ATTRIBUTE_VALUE,a,href),0), "
+ + "L(=), P((ATTRIBUTE_VALUE,a,href),1), "
+ + "L(\">), P((TEXT,null,null),2), L(</a>)]"),
"<a href=\"http://...&{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