Revision: 6100 Author: [email protected] Date: Tue Sep 8 22:07:33 2009 Log: 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. http://code.google.com/p/google-web-toolkit/source/detail?r=6100 Modified: /trunk/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java /trunk/user/src/com/google/gwt/uibinder/parsers/HtmlMessageInterpreter.java /trunk/user/src/com/google/gwt/uibinder/parsers/HtmlPlaceholderInterpreter.java /trunk/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderInterpreter.java /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml /trunk/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java ======================================= --- /trunk/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java Wed Aug 5 20:27:52 2009 +++ /trunk/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java Tue Sep 8 22:07:33 2009 @@ -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) { ======================================= --- /trunk/user/src/com/google/gwt/uibinder/parsers/HtmlMessageInterpreter.java Wed Aug 5 20:27:52 2009 +++ /trunk/user/src/com/google/gwt/uibinder/parsers/HtmlMessageInterpreter.java Tue Sep 8 22:07:33 2009 @@ -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, ======================================= --- /trunk/user/src/com/google/gwt/uibinder/parsers/HtmlPlaceholderInterpreter.java Wed Aug 5 20:27:52 2009 +++ /trunk/user/src/com/google/gwt/uibinder/parsers/HtmlPlaceholderInterpreter.java Tue Sep 8 22:07:33 2009 @@ -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(); ======================================= --- /trunk/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderInterpreter.java Tue Sep 1 15:14:29 2009 +++ /trunk/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderInterpreter.java Tue Sep 8 22:07:33 2009 @@ -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) { ======================================= --- /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java Thu Sep 3 16:03:13 2009 +++ /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java Tue Sep 8 22:07:33 2009 @@ -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; ======================================= --- /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml Thu Sep 3 16:03:13 2009 +++ /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml Tue Sep 8 22:07:33 2009 @@ -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 ======================================= --- /trunk/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java Thu Sep 3 16:03:13 2009 +++ /trunk/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java Tue Sep 8 22:07:33 2009 @@ -165,6 +165,17 @@ + "translation: funny characters \" \" ' ' & < > > { }", 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; --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---
