You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by hn...@apache.org on 2023/01/13 12:22:32 UTC

[myfaces-tobago] branch main updated: feat: message-format for tc:out

This is an automated email from the ASF dual-hosted git repository.

hnoeth pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git


The following commit(s) were added to refs/heads/main by this push:
     new 957ad7c84f feat: message-format for tc:out
957ad7c84f is described below

commit 957ad7c84f9df815a64a195ff04ccd39df18add0
Author: Henning Noeth <hn...@apache.org>
AuthorDate: Fri Jan 13 13:15:10 2023 +0100

    feat: message-format for tc:out
    
    * support for f:param to tc:out
    
    Issue: TOBAGO-2177
---
 .../myfaces/tobago/component/Attributes.java       |  1 +
 .../tobago/internal/component/AbstractUIOut.java   |  2 +
 .../internal/renderkit/renderer/OutRenderer.java   | 62 +++++++++++++++++++++-
 .../taglib/component/OutTagDeclaration.java        | 15 +++++-
 .../internal/config/AbstractTobagoTestBase.java    | 20 ++++---
 .../renderkit/renderer/OutRendererUnitTest.java    | 23 ++++++++
 .../src/test/resources/renderer/out/outFormat.html | 18 +++++++
 .../tobago/example/demo/DemoBundle.properties      |  1 +
 .../tobago/example/demo/DemoBundle_de.properties   |  1 +
 .../webapp/content/020-output/10-out/Out.xhtml     | 14 ++++-
 10 files changed, 141 insertions(+), 16 deletions(-)

diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java
index 0ad982eff0..5b29c108fa 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Attributes.java
@@ -215,6 +215,7 @@ public enum Attributes {
   maxWidth,
   media,
   method,
+  messageFormat,
   min,
   minHeight,
   minWidth,
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIOut.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIOut.java
index 619c508b4b..ea0b1631e0 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIOut.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIOut.java
@@ -49,6 +49,8 @@ public abstract class AbstractUIOut extends UIOutput implements SupportsLabelLay
   @Deprecated
   public abstract boolean isCompact();
 
+  public abstract boolean isMessageFormat();
+
   public abstract SanitizeMode getSanitize();
 
   @Override
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/OutRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/OutRenderer.java
index 0ec33557d8..94a707d02d 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/OutRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/OutRenderer.java
@@ -19,6 +19,9 @@
 
 package org.apache.myfaces.tobago.internal.renderkit.renderer;
 
+import jakarta.faces.component.UIComponent;
+import jakarta.faces.component.UIParameter;
+import jakarta.faces.context.FacesContext;
 import org.apache.myfaces.tobago.config.TobagoConfig;
 import org.apache.myfaces.tobago.context.Markup;
 import org.apache.myfaces.tobago.internal.component.AbstractUIOut;
@@ -30,14 +33,22 @@ import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
 import org.apache.myfaces.tobago.sanitizer.SanitizeMode;
 import org.apache.myfaces.tobago.sanitizer.Sanitizer;
 import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
-
-import jakarta.faces.context.FacesContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.StringTokenizer;
 
 public class OutRenderer<T extends AbstractUIOut> extends MessageLayoutRendererBase<T> {
 
+  private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+
   @Override
   protected boolean isOutputOnly(T component) {
     return true;
@@ -106,6 +117,10 @@ public class OutRenderer<T extends AbstractUIOut> extends MessageLayoutRendererB
       text = "";
     }
 
+    if (out.isMessageFormat()) {
+      text = getOutputFormatText(facesContext, text, out);
+    }
+
     if (escape) {
       if (keepLineBreaks) {
         final StringTokenizer tokenizer = new StringTokenizer(text, "\r\n");
@@ -130,6 +145,49 @@ public class OutRenderer<T extends AbstractUIOut> extends MessageLayoutRendererB
     }
   }
 
+  private String getOutputFormatText(final FacesContext facesContext, final String pattern, final T out) {
+    final Object[] args;
+    if (out.getChildCount() > 0) {
+      final List<UIParameter> validParams = getValidUIParameterChildren(out);
+      if (!validParams.isEmpty()) {
+        List<Object> argsList = new ArrayList<>(validParams.size());
+        for (UIParameter param : validParams) {
+          argsList.add(param.getValue());
+        }
+        args = argsList.toArray(EMPTY_OBJECT_ARRAY);
+      } else {
+        args = EMPTY_OBJECT_ARRAY;
+      }
+    } else {
+      args = EMPTY_OBJECT_ARRAY;
+    }
+
+    final MessageFormat format = new MessageFormat(pattern, facesContext.getViewRoot().getLocale());
+    try {
+      return format.format(args);
+    } catch (Exception e) {
+      LOG.error("Error formatting message of component with clientId='{}'", out.getClientId(facesContext));
+      return pattern;
+    }
+  }
+
+  private List<UIParameter> getValidUIParameterChildren(T out) {
+
+    List<UIParameter> parameters = new ArrayList<>();
+    for (UIComponent child : out.getChildren()) {
+      if (child instanceof UIParameter) {
+        UIParameter param = (UIParameter) child;
+        if (param.isDisable() || !param.isRendered()) {
+          // ignore
+          continue;
+        }
+        parameters.add(param);
+      }
+    }
+
+    return parameters;
+  }
+
   @Override
   public void encodeEndField(final FacesContext facesContext, final T component) throws IOException {
     final TobagoResponseWriter writer = getResponseWriter(facesContext);
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/OutTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/OutTagDeclaration.java
index 86271ec4e0..8813a028b5 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/OutTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/OutTagDeclaration.java
@@ -19,12 +19,14 @@
 
 package org.apache.myfaces.tobago.internal.taglib.component;
 
+import jakarta.faces.component.UIOutput;
 import org.apache.myfaces.tobago.apt.annotation.BodyContentDescription;
 import org.apache.myfaces.tobago.apt.annotation.Tag;
 import org.apache.myfaces.tobago.apt.annotation.TagAttribute;
 import org.apache.myfaces.tobago.apt.annotation.UIComponentTag;
 import org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute;
 import org.apache.myfaces.tobago.component.RendererTypes;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasAutoSpacing;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverter;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasIdBindingAndRendered;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabel;
@@ -32,11 +34,10 @@ import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabelLayout;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasSanitize;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasTip;
 import org.apache.myfaces.tobago.internal.taglib.declaration.HasValue;
-import org.apache.myfaces.tobago.internal.taglib.declaration.HasAutoSpacing;
 import org.apache.myfaces.tobago.internal.taglib.declaration.IsPlain;
 import org.apache.myfaces.tobago.internal.taglib.declaration.IsVisual;
 
-import jakarta.faces.component.UIOutput;
+import java.text.MessageFormat;
 
 /**
  * Renders a text
@@ -98,4 +99,14 @@ public interface OutTagDeclaration
   @TagAttribute
   @UIComponentTagAttribute(type = "boolean", defaultValue = "false")
   void setCompact(String compact);
+
+  /**
+   * Activates formatting of the value with the method {@link MessageFormat#format(String, Object...)}
+   * A parameters the values of the children of type {@link jakarta.faces.component.UIParameter} are used.
+   *
+   * @since 5.5.0
+   */
+  @TagAttribute
+  @UIComponentTagAttribute(type = "boolean", defaultValue = "false")
+  void setMessageFormat(String messageFormat);
 }
diff --git a/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/config/AbstractTobagoTestBase.java b/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/config/AbstractTobagoTestBase.java
index d53678b358..72f1010306 100644
--- a/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/config/AbstractTobagoTestBase.java
+++ b/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/config/AbstractTobagoTestBase.java
@@ -19,6 +19,11 @@
 
 package org.apache.myfaces.tobago.internal.config;
 
+import jakarta.faces.component.UIParameter;
+import jakarta.faces.component.behavior.AjaxBehavior;
+import jakarta.faces.convert.DateTimeConverter;
+import jakarta.faces.render.RenderKit;
+import jakarta.servlet.ServletContext;
 import org.apache.myfaces.test.base.junit4.AbstractJsfTestCase;
 import org.apache.myfaces.test.config.ResourceBundleVarNames;
 import org.apache.myfaces.test.mock.MockFacesContext;
@@ -64,9 +69,6 @@ import org.apache.myfaces.tobago.component.UITreeIndent;
 import org.apache.myfaces.tobago.component.UITreeNode;
 import org.apache.myfaces.tobago.component.UITreeSelect;
 import org.apache.myfaces.tobago.config.TobagoConfig;
-
-import static org.apache.myfaces.tobago.config.TobagoConfig.TOBAGO_CONFIG;
-
 import org.apache.myfaces.tobago.context.TobagoContext;
 import org.apache.myfaces.tobago.internal.behavior.EventBehavior;
 import org.apache.myfaces.tobago.internal.renderkit.renderer.BadgeRenderer;
@@ -108,23 +110,18 @@ import org.apache.myfaces.tobago.internal.renderkit.renderer.TreeNodeRenderer;
 import org.apache.myfaces.tobago.internal.renderkit.renderer.TreeRenderer;
 import org.apache.myfaces.tobago.internal.renderkit.renderer.TreeSelectRenderer;
 import org.apache.myfaces.tobago.internal.webapp.HtmlResponseWriter;
-
-import static org.apache.myfaces.tobago.util.ResourceUtils.TOBAGO_RESOURCE_BUNDLE;
-
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 
-import jakarta.faces.component.behavior.AjaxBehavior;
-import jakarta.faces.convert.DateTimeConverter;
-import jakarta.faces.render.RenderKit;
-import jakarta.servlet.ServletContext;
-
 import java.io.IOException;
 import java.io.StringWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.Date;
 import java.util.Locale;
 
+import static org.apache.myfaces.tobago.config.TobagoConfig.TOBAGO_CONFIG;
+import static org.apache.myfaces.tobago.util.ResourceUtils.TOBAGO_RESOURCE_BUNDLE;
+
 /**
  * <p>Abstract JUnit test case base class, which sets up the JavaServer Faces
  * mock object environment for a particular simulated request.
@@ -201,6 +198,7 @@ public abstract class AbstractTobagoTestBase extends AbstractJsfTestCase {
     application.addComponent(Tags.treeNode.componentType(), UITreeNode.class.getName());
     application.addComponent(Tags.treeIndent.componentType(), UITreeIndent.class.getName());
     application.addComponent(Tags.treeSelect.componentType(), UITreeSelect.class.getName());
+    application.addComponent(UIParameter.COMPONENT_TYPE, UIParameter.class.getName());
 
     application.addBehavior(AjaxBehavior.BEHAVIOR_ID, AjaxBehavior.class.getName());
     application.addBehavior(EventBehavior.BEHAVIOR_ID, EventBehavior.class.getName());
diff --git a/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/renderkit/renderer/OutRendererUnitTest.java b/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/renderkit/renderer/OutRendererUnitTest.java
index c743086f20..8ef5017450 100644
--- a/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/renderkit/renderer/OutRendererUnitTest.java
+++ b/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/renderkit/renderer/OutRendererUnitTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.myfaces.tobago.internal.renderkit.renderer;
 
+import jakarta.faces.component.UIParameter;
 import org.apache.myfaces.tobago.component.RendererTypes;
 import org.apache.myfaces.tobago.component.Tags;
 import org.apache.myfaces.tobago.component.UIOut;
@@ -91,4 +92,26 @@ public class OutRendererUnitTest extends RendererTestBase {
     Assertions.assertEquals(loadHtml("renderer/out/outSanitizeNever.html"), formattedResult());
   }
 
+  @Test
+  public void outFormat() throws IOException {
+    final UIOut c = (UIOut) ComponentUtils.createComponent(
+        facesContext, Tags.out.componentType(), RendererTypes.Out, "id");
+    c.setValue("Hello {0} {1}!");
+    c.setMessageFormat(true);
+
+    final UIParameter p0 = (UIParameter) ComponentUtils.createComponent(
+        facesContext, UIParameter.COMPONENT_TYPE, null, "p0");
+    p0.setValue("Mrs");
+    c.getChildren().add(p0);
+
+    final UIParameter p1 = (UIParameter) ComponentUtils.createComponent(
+        facesContext, UIParameter.COMPONENT_TYPE, null, "p1");
+    p1.setValue("Smith");
+    c.getChildren().add(p1);
+
+    c.encodeAll(facesContext);
+
+    Assertions.assertEquals(loadHtml("renderer/out/outFormat.html"), formattedResult());
+  }
+
 }
diff --git a/tobago-core/src/test/resources/renderer/out/outFormat.html b/tobago-core/src/test/resources/renderer/out/outFormat.html
new file mode 100644
index 0000000000..ecf3ca6b2c
--- /dev/null
+++ b/tobago-core/src/test/resources/renderer/out/outFormat.html
@@ -0,0 +1,18 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<tobago-out id='id' class='tobago-auto-spacing'><span class='form-control-plaintext'>Hello Mrs Smith!</span></tobago-out>
diff --git a/tobago-example/tobago-example-demo/src/main/resources/org/apache/myfaces/tobago/example/demo/DemoBundle.properties b/tobago-example/tobago-example-demo/src/main/resources/org/apache/myfaces/tobago/example/demo/DemoBundle.properties
index ae68c5e8ad..38f04e1fa1 100644
--- a/tobago-example/tobago-example-demo/src/main/resources/org/apache/myfaces/tobago/example/demo/DemoBundle.properties
+++ b/tobago-example/tobago-example-demo/src/main/resources/org/apache/myfaces/tobago/example/demo/DemoBundle.properties
@@ -15,3 +15,4 @@
 
 locale_short=default
 not_translated=Some content of the demo is not translated!
+message_format_example=Welcome {0} {1}!
diff --git a/tobago-example/tobago-example-demo/src/main/resources/org/apache/myfaces/tobago/example/demo/DemoBundle_de.properties b/tobago-example/tobago-example-demo/src/main/resources/org/apache/myfaces/tobago/example/demo/DemoBundle_de.properties
index 6b72675004..3b3b104445 100644
--- a/tobago-example/tobago-example-demo/src/main/resources/org/apache/myfaces/tobago/example/demo/DemoBundle_de.properties
+++ b/tobago-example/tobago-example-demo/src/main/resources/org/apache/myfaces/tobago/example/demo/DemoBundle_de.properties
@@ -15,3 +15,4 @@
 
 locale_short=de
 not_translated=Einige Teile dieser Seite sind nicht in allen Sprachen verf\u00FCgbar.
+message_format_example=Willkommen {0} {1}!
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/020-output/10-out/Out.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/020-output/10-out/Out.xhtml
index 4e5738f507..eef6d94df3 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/020-output/10-out/Out.xhtml
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/020-output/10-out/Out.xhtml
@@ -20,7 +20,8 @@
 <ui:composition template="/main.xhtml"
                 xmlns="http://www.w3.org/1999/xhtml"
                 xmlns:tc="http://myfaces.apache.org/tobago/component"
-                xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
+                xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
+                xmlns:f="http://java.sun.com/jsf/core">
 
   <p>The <code class="language-markup">&lt;tc:out/></code> display an outputtext combined with a label.</p>
   <tc:link label="Tag Library Documentation" image="#{request.contextPath}/image/feather-leaf.png"
@@ -66,6 +67,17 @@
       <tc:out value="combination: deleted danger italic" markup="deleted danger italic"/>
     </tc:segmentLayout>
   </tc:section>
+  <tc:section label="Message Format">
+    Using message formatting for i18n strings.
+    <demo-highlight language="markup">&lt;tc:out value="\#{demoBundle.message_format_example}" messageFormat="true"&gt;
+  &lt;f:param value="Mrs"/&gt;
+  &lt;f:param value="Smith"/&gt;
+&lt;/tc:out&gt;</demo-highlight>
+    <tc:out value="#{demoBundle.message_format_example}" messageFormat="true">
+      <f:param value="Mrs"/>
+      <f:param value="Smith"/>
+    </tc:out>
+  </tc:section>
   <tc:section label="Escape">
     <p>If the given string to the output field is HTML/XML code, it will be escaped by default.
       But you can turn it off with the <code>escape</code> attribute.</p>