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

[myfaces-tobago] branch tobago-5.x updated: Tobago 5.x message format (#3657)

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

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


The following commit(s) were added to refs/heads/tobago-5.x by this push:
     new 641e35a762 Tobago 5.x message format (#3657)
641e35a762 is described below

commit 641e35a762c024450960af69da7e3746149082e0
Author: Udo Schnurpfeil <lo...@apache.org>
AuthorDate: Thu Jan 12 13:51:44 2023 +0100

    Tobago 5.x message format (#3657)
    
    * feat: message-format for tc:out
    
    issue: TOBAGO-2177
---
 .../myfaces/tobago/component/Attributes.java       |  1 +
 .../tobago/internal/component/AbstractUIOut.java   |  2 +
 .../internal/renderkit/renderer/OutRenderer.java   | 59 ++++++++++++++++++++++
 .../taglib/component/OutTagDeclaration.java        | 11 ++++
 .../internal/config/AbstractTobagoTestBase.java    |  2 +
 .../renderkit/renderer/OutRendererUnitTest.java    | 23 +++++++++
 .../src/test/resources/renderer/out/outFormat.html | 18 +++++++
 .../tobago-example-demo/package-lock.json          | 14 ++---
 .../tobago/example/demo/DemoBundle.properties      |  1 +
 .../tobago/example/demo/DemoBundle_de.properties   |  1 +
 .../webapp/content/020-output/10-out/Out.xhtml     | 14 ++++-
 11 files changed, 136 insertions(+), 10 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 49bcdec70e..60288a6c56 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 371058ebc2..0ed948f682 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
@@ -30,13 +30,25 @@ 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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import javax.faces.component.UIComponent;
+import javax.faces.component.UIParameter;
 import javax.faces.context.FacesContext;
 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;
@@ -105,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");
@@ -129,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 1591a29f2f..864a2c789b 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
@@ -37,6 +37,7 @@ import org.apache.myfaces.tobago.internal.taglib.declaration.IsPlain;
 import org.apache.myfaces.tobago.internal.taglib.declaration.IsVisual;
 
 import javax.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 javax.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 b6f2c4e2b6..9d9a071e4f 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
@@ -114,6 +114,7 @@ import static org.apache.myfaces.tobago.util.ResourceUtils.TOBAGO_RESOURCE_BUNDL
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 
+import javax.faces.component.UIParameter;
 import javax.faces.component.behavior.AjaxBehavior;
 import javax.faces.convert.DateTimeConverter;
 import javax.faces.render.RenderKit;
@@ -200,6 +201,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..f0927b3c8a 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
@@ -28,6 +28,7 @@ import org.apache.myfaces.tobago.util.ComponentUtils;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import javax.faces.component.UIParameter;
 import java.io.IOException;
 
 public class OutRendererUnitTest extends RendererTestBase {
@@ -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/package-lock.json b/tobago-example/tobago-example-demo/package-lock.json
index 235efd5d63..8ac044cbef 100644
--- a/tobago-example/tobago-example-demo/package-lock.json
+++ b/tobago-example/tobago-example-demo/package-lock.json
@@ -10,6 +10,7 @@
       "license": "Apache-2.0",
       "dependencies": {
         "font-awesome": "4.7.0",
+        "glob-parent": "^6.0.2",
         "jasmine-core": "^4.5.0",
         "prismjs": "^1.29.0"
       },
@@ -774,7 +775,6 @@
       "version": "6.0.2",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
       "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
-      "dev": true,
       "dependencies": {
         "is-glob": "^4.0.3"
       },
@@ -1031,7 +1031,6 @@
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
       "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -1049,7 +1048,6 @@
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
       "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
-      "dev": true,
       "dependencies": {
         "is-extglob": "^2.1.1"
       },
@@ -2888,9 +2886,9 @@
       }
     },
     "glob-parent": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
-      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
       "dev": true,
       "requires": {
         "is-glob": "^4.0.3"
@@ -3075,8 +3073,7 @@
     "is-extglob": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
-      "dev": true
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
     },
     "is-fullwidth-code-point": {
       "version": "3.0.0",
@@ -3088,7 +3085,6 @@
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
       "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
-      "dev": true,
       "requires": {
         "is-extglob": "^2.1.1"
       }
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>