We were double escaping the contents of placeholders inside HTML
messages. E.g.,
<p><ui:msg>I would 'like' a <span ui:field='foo'>'single'</span> quote</ui
rendered as:
I would 'like' a ''single'' quote
There was already a mechanism in place for this kind of thing, written for t
widgets-in-HTML-messages case (WidgetPlaceholderInterpreter), but I forgot
to use it in HtmlPlaceholderInterpreter.
Added tests and touched up docs while there, hoping to regain my
bearings more quickly next time.
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6100 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java
index 48b4003..759e009 100644
--- a/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java
@@ -32,14 +32,14 @@
final UiBinderWriter writer) throws UnableToCompleteException {
String customTag =
UiBinderWriter.escapeTextForJavaStringLiteral(elem.consumeAttribute("tag"));
-
+
/*
* Gathers up elements that indicate nested widgets (but only those
* that are not inside msg elements).
*/
WidgetInterpreter widgetInterpreter = new WidgetInterpreter(fieldName, writer);
- /*
+ /*
* Handles non-widget elements like msg, and dom elements with ui:field
* attributes. There may be widgets inside a msg, which is why
* the construction in makeHtmlInterpreter is so complicated.
@@ -65,7 +65,7 @@
/**
* Creates an HtmlInterpreter with our specialized placeholder interpreter,
- * which will allow widget instances to be declared inside of m:msg elements.
+ * which will allow widget instances to be declared inside of ui:msg elements.
*/
private HtmlInterpreter makeHtmlInterpreter(final String fieldName,
final UiBinderWriter uiWriter) {
diff --git a/user/src/com/google/gwt/uibinder/parsers/HtmlMessageInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/HtmlMessageInterpreter.java
index 4c9d48d..b81fca5 100644
--- a/user/src/com/google/gwt/uibinder/parsers/HtmlMessageInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/parsers/HtmlMessageInterpreter.java
@@ -23,7 +23,7 @@
import com.google.gwt.uibinder.rebind.messages.PlaceholderInterpreter;
/**
- * Processes <m:msg> elements inside HTML values, which themselves
+ * Processes <ui:msg> elements inside HTML values, which themselves
* are allowed to contain HTML. That HTML may hold elements with
* ui:field attributes and computed attributes, which must be
* replaced by placeholders in the generated message.
@@ -52,7 +52,7 @@
/**
* Build a normally configured HtmlMessageInterpreter, able to handle
- * put placeholders around dom elements with m:ph attributes and computed
+ * put placeholders around dom elements with ui:ph attributes and computed
* attributes.
*/
public HtmlMessageInterpreter(final UiBinderWriter uiWriter,
diff --git a/user/src/com/google/gwt/uibinder/parsers/HtmlPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/HtmlPlaceholderInterpreter.java
index 81d2f15..9e08199 100644
--- a/user/src/com/google/gwt/uibinder/parsers/HtmlPlaceholderInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/parsers/HtmlPlaceholderInterpreter.java
@@ -60,7 +60,11 @@
nextPlaceholder(name + "Begin", stripTokens(openTag),
uiWriter.detokenate(openTag));
- String body = elem.consumeInnerHtml(this);
+ /*
+ * This recursive innerHtml call has already been escaped. Hide it in a
+ * token to avoid double escaping
+ */
+ String body = tokenator.nextToken(elem.consumeInnerHtml(this));
String closeTag = elem.getClosingTag();
String closePlaceholder =
@@ -79,7 +83,13 @@
}
/**
- * @return true if it has an m:ph attribute, or has a token in any attribute
+ * An element will require a placeholder if the user has called it out with a
+ * ui:ph attribute, or if it will require run time swizzling (e.g. has a
+ * ui:field). These latter we can identify easily because they'll have an
+ * attribute that holds a tokenator token that was vended by
+ * {@link UiBinderWriter}, typically in id.
+ *
+ * @return true if it has an ui:ph attribute, or has a token in any attribute
*/
private boolean isDomPlaceholder(XMLElement elem) {
MessagesWriter mw = uiWriter.getMessages();
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderInterpreter.java
index 69ea07b..5578f74 100644
--- a/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderInterpreter.java
@@ -40,15 +40,14 @@
public String interpretElement(XMLElement elem)
throws UnableToCompleteException {
-
if (isPlaceholderElement(elem)) {
/*
- * The innerHTML or innerText of the <m:ph> will be provided as the value
+ * The innerHTML or innerText of the <ui:ph> will be provided as the value
* of the appropriate parameter when the Messages method is called.
*
* E.g.
*
- * <m:msg>Able <m:ph name="foo" example"baz">baker</m:ph> charlie</m:msg>
+ * <ui:msg>Able <ui:ph name="foo" example"baz">baker</ui:ph> charlie</ui:msg>
*
* becomes
*
@@ -78,11 +77,10 @@
}
/*
- * Again, if there are TemplateWriter tokens in the value string, we need
- * it to have it replace them with the real expresions.
+ * Likewise, if there are tokens from the UiWriter in the value string, we
+ * need it to replace them with the real expresions.
*/
value = uiWriter.detokenate(value);
-
return nextPlaceholder(name, example, value);
}
@@ -94,9 +92,10 @@
/**
* Called by various {@link XMLElement} consumeInner*() methods after all
- * elements have been handed to {@link #interpretElement}. Performs escaping
- * on the consumed text to make it safe for use as a Messages {@literal @}Default
- * value.
+ * elements have been handed to {@link #interpretElement}.
+ * <p>
+ * Performs escaping on the consumed text to make it safe for use as a
+ * Messages {@literal @}Default value
*/
public String postProcess(String consumed) throws UnableToCompleteException {
return tokenator.detokenate(MessageWriter.escapeMessageFormat(consumed));
@@ -105,10 +104,29 @@
protected abstract String consumePlaceholderInnards(XMLElement elem)
throws UnableToCompleteException;
+ /**
+ * To be called from {@link #interpretElement(XMLElement)}. Creates the next
+ * placeholder in the {@link MessageWriter} we're building, and returns the
+ * text to stand in its place.
+ *
+ * @param name
+ * @param example
+ * @param value
+ * @return
+ */
protected String nextPlaceholder(String name, String example, String value) {
message.addPlaceholder(new PlaceholderWriter(name, example, value));
- return tokenator.nextToken(String.format("{%d}",
- message.getPlaceholderCount() - 1));
+
+ /*
+ * We can't just return the {0} placeholder text, because it will be
+ * clobbered by the escaping performed in postProcess. We use a tokenator to
+ * hide the placeholder from the escaping step, and postProcess resolves the
+ * tokens when the escaping is done.
+ */
+ String placeholder = String.format("{%d}",
+ message.getPlaceholderCount() - 1);
+
+ return tokenator.nextToken(placeholder);
}
protected String stripTokens(String value) {
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
index b32ae43..1fde1b3 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
@@ -74,7 +74,9 @@
@UiField ClickyLink funnyCharsMessageAttributeWidget;
@UiField ParagraphElement funnyCharsMessageDomAttributeParagraph;
@UiField ParagraphElement funnyCharsMessageParagraph;
+ @UiField SpanElement funnyCharsMessageChildSpan;
@UiField ParagraphElement funnyCharsParagraph;
+ @UiField ParagraphElement funnyCharsProtectedMessageParagraph;
@UiField Label gwtFieldLabel;
@UiField ParagraphElement main;
@UiField Button myButton;
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
index d4d99db..0d2db28 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
@@ -99,7 +99,7 @@
</gwt:Dock>
<gwt:Dock direction='CENTER'>
<gwt:HTMLPanel>
- <p><ui:msg>This is a demonstration and test bed of GWT's UiBinder
+ <p><ui:msg>This is a demonstration and test bed of GWT's shiny UiBinder
package. At the moment it works mainly as described in
<a href="http://code.google.com/p/google-web-toolkit-incubator/wiki/DeclarativeUi"
ui:ph="oldBlogLink">
@@ -279,6 +279,8 @@
untranslated paragraph.</p>
<p ui:field="funnyCharsMessageParagraph"><ui:msg>They might show up
in body text that has been <b>marked for translation</b>: funny characters " " ' ' & < > > { }</ui:msg></p>
+ <p><ui:msg>Or perhaps in a subelement with a ui:field: <span ui:field='funnyCharsMessageChildSpan'>funny characters " " ' ' & < > > { }</span></ui:msg></p>
+ <p ui:field="funnyCharsProtectedMessageParagraph"><ui:msg>Don't forget about protected untranslatable blocks: <ui:ph name='francine'>funny characters " " ' ' & < > > { }</ui:ph></ui:msg></p>
<p ui:field="funnyCharsMessageDomAttributeParagraph" title="funny characters " ' ' & < > > { }">
<ui:attribute name="title"/>
Attributes of dom elements can be translated too, like the
diff --git a/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java b/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java
index 90bf5a4..82a842b 100644
--- a/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java
+++ b/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java
@@ -166,6 +166,17 @@
t);
}
+ public void testProtectedDomTextMessageWithFunnyChars() {
+ String t = widgetUi.funnyCharsProtectedMessageParagraph.getInnerText();
+ assertEquals("Don't forget about protected untranslatable blocks: "
+ + "funny characters \" \" ' ' & < > > { }", t);
+ }
+
+ public void testDomTextInNamedElementMessageWithFunnyChars() {
+ String t = widgetUi.funnyCharsMessageChildSpan.getInnerText();
+ assertEquals("funny characters \" \" ' ' & < > > { }", t);
+ }
+
public void suppressedForSafari3Fail_testDomTextNoMessageWithFunnyChars() {
ParagraphElement p = widgetUi.funnyCharsParagraph;
// WebKit does \n replace thing, so let's do it everywhere