You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by ja...@apache.org on 2020/09/05 23:56:02 UTC

[ofbiz-framework] branch trunk updated: Improved: Well-formed html in ftl template (OFBIZ-11996)

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

jamesyong pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 8cbdb6a  Improved: Well-formed html in ftl template (OFBIZ-11996)
8cbdb6a is described below

commit 8cbdb6a225eed7b4571840bf6ff9cbe139ae088c
Author: James Yong <ja...@apache.org>
AuthorDate: Sun Sep 6 07:44:40 2020 +0800

    Improved: Well-formed html in ftl template (OFBIZ-11996)
    
    When print.verbose=true, check for well-formed html in ftl templates to catch programming errors.
---
 .../org/apache/ofbiz/widget/model/HtmlWidget.java  | 174 ++++++++++++---------
 .../widget/model/MultiBlockHtmlTemplateUtil.java   |   2 +-
 .../ofbiz/widget/renderer/ScreenRenderer.java      |   4 +-
 3 files changed, 101 insertions(+), 79 deletions(-)

diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java
index 0ae8dea..5a046bf 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java
@@ -43,6 +43,9 @@ import org.apache.ofbiz.widget.renderer.ScreenStringRenderer;
 import org.apache.ofbiz.widget.renderer.html.HtmlWidgetRenderer;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
+import org.jsoup.parser.ParseError;
+import org.jsoup.parser.ParseErrorList;
+import org.jsoup.parser.Parser;
 import org.jsoup.select.Elements;
 import org.w3c.dom.Element;
 
@@ -56,6 +59,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Stack;
+import java.util.function.Consumer;
 
 /**
  * Widget Library - Screen model HTML class.
@@ -187,80 +191,6 @@ public class HtmlWidget extends ModelScreenWidget {
         }
     }
 
-    /**
-     * Render html template when multi-block=true. We use stack to store the string writer because a freemarker template may also render a sub screen
-     * widget by using ${screens.render(link to the screen)}. So before rendering the sub screen widget, ScreenRenderer class will check for the
-     * existence of the stack and retrieve the correct string writer. Inline script tags are removed from the final rendering.
-     * @param writer
-     * @param locationExdr
-     * @param context
-     * @throws IOException
-     */
-    public static void renderHtmlTemplateWithMultiBlock(Appendable writer, FlexibleStringExpander locationExdr,
-                                                        Map<String, Object> context) throws IOException {
-        String location = locationExdr.expandString(context);
-
-        StringWriter stringWriter = new StringWriter();
-        Stack<StringWriter> stringWriterStack = UtilGenerics.cast(context.get(MultiBlockHtmlTemplateUtil.MULTI_BLOCK_WRITER));
-        if (stringWriterStack == null) {
-            stringWriterStack = new Stack<>();
-        }
-        stringWriterStack.push(stringWriter);
-        context.put(MultiBlockHtmlTemplateUtil.MULTI_BLOCK_WRITER, stringWriterStack);
-        renderHtmlTemplate(stringWriter, locationExdr, context);
-        stringWriterStack.pop();
-        // check if no more parent freemarker template before removing from context
-        if (stringWriterStack.empty()) {
-            context.remove(MultiBlockHtmlTemplateUtil.MULTI_BLOCK_WRITER);
-        }
-        String data = stringWriter.toString();
-        stringWriter.close();
-
-        Document doc = Jsoup.parseBodyFragment(data);
-
-        // extract js script tags
-        Elements scriptElements = doc.select("script");
-        if (scriptElements != null && scriptElements.size() > 0) {
-            StringBuilder scripts = new StringBuilder();
-            for (org.jsoup.nodes.Element script : scriptElements) {
-                String type = script.attr("type");
-                String src = script.attr("src");
-                if (UtilValidate.isEmpty(src)) {
-                    if (UtilValidate.isEmpty(type) || "application/javascript".equals(type)) {
-                        scripts.append(script.data());
-                        script.remove();
-                    }
-                }
-            }
-
-            if (scripts.length() > 0) {
-                // store script for retrieval by the browser
-                String fileName = location;
-                fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
-                if (fileName.endsWith(".ftl")) {
-                    fileName = fileName.substring(0, fileName.length() - 4);
-                }
-                String key = MultiBlockHtmlTemplateUtil.putScriptInCache(context, fileName, scripts.toString());
-
-                // construct script link
-                String webappName = (String) context.get("webappName");
-                String url = "/" + webappName + "/control/getJs?name=" + key;
-
-                // add csrf token to script link
-                HttpServletRequest request = (HttpServletRequest) context.get("request");
-                String tokenValue = CsrfUtil.generateTokenForNonAjax(request, "getJs");
-                url = CsrfUtil.addOrUpdateTokenInUrl(url, tokenValue);
-
-                // store script link to be output by scriptTagsFooter freemarker macro
-                MultiBlockHtmlTemplateUtil.addScriptLinkForFoot(request, url);
-            }
-        }
-
-        // the 'template' block
-        String body = doc.body().html();
-        writer.append(body);
-    }
-
     // TODO: We can make this more fancy, but for now this is very functional
     public static void writeError(Appendable writer, String message) {
         try {
@@ -299,10 +229,102 @@ public class HtmlWidget extends ModelScreenWidget {
         @Override
         public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws IOException {
 
+            if (!isMultiBlock() && !Debug.verboseOn()) {
+                renderHtmlTemplate(writer, locationExdr, context);
+                return;
+            }
+
+            /**
+             * We use stack to store the string writer because a freemarker template may also render a sub screen
+             * widget by using ${screens.render(link to the screen)}. So before rendering the sub screen widget, ScreenRenderer class will check for the
+             * existence of the stack and retrieve the correct string writer.
+             * Inline script tags are removed from the final rendering, if multi-block = true
+             */
+            String location = locationExdr.expandString(context);
+            StringWriter stringWriter = new StringWriter();
+            Stack<StringWriter> stringWriterStack = UtilGenerics.cast(context.get(MultiBlockHtmlTemplateUtil.FTL_WRITER));
+            if (stringWriterStack == null) {
+                stringWriterStack = new Stack<>();
+            }
+            stringWriterStack.push(stringWriter);
+            context.put(MultiBlockHtmlTemplateUtil.FTL_WRITER, stringWriterStack);
+            renderHtmlTemplate(stringWriter, locationExdr, context);
+            stringWriterStack.pop();
+            // check if no more parent freemarker template before removing from context
+            if (stringWriterStack.empty()) {
+                context.remove(MultiBlockHtmlTemplateUtil.FTL_WRITER);
+            }
+            String data = stringWriter.toString();
+            stringWriter.close();
+
+            Document doc = null;
+            if (Debug.verboseOn()) {
+                Parser parser = Parser.htmlParser();
+                parser.setTrackErrors(100);
+                doc = parser.parseInput(data, "");
+
+                // check for any error during parsing
+                int dataLength = data.length();
+                Consumer<ParseError> logError = a -> Debug.logError(a.toString() + " [Parsing " + location + "]\n"
+                                + "..."
+                                + data.substring(Math.max(a.getPosition() - 50, 0), a.getPosition()).replaceAll("^\\s+", "")
+                                + " ^^^ "
+                                + data.substring(a.getPosition(), Math.min(a.getPosition() + 50, dataLength - 1)).replaceAll("\\s+$", "")
+                                + "...",
+                        MODULE);
+
+                // print any parse error
+                if (parser.isTrackErrors()) {
+                    ParseErrorList list = parser.getErrors();
+                    list.forEach(logError);
+                }
+            } else {
+                doc = Jsoup.parseBodyFragment(data);
+            }
+
             if (isMultiBlock()) {
-                renderHtmlTemplateWithMultiBlock(writer, this.locationExdr, context);
+                // extract js script tags
+                Elements scriptElements = doc.select("script");
+                if (scriptElements != null && scriptElements.size() > 0) {
+                    StringBuilder scripts = new StringBuilder();
+                    for (org.jsoup.nodes.Element script : scriptElements) {
+                        String type = script.attr("type");
+                        String src = script.attr("src");
+                        if (UtilValidate.isEmpty(src)) {
+                            if (UtilValidate.isEmpty(type) || "application/javascript".equals(type)) {
+                                scripts.append(script.data());
+                                script.remove();
+                            }
+                        }
+                    }
+
+                    if (scripts.length() > 0) {
+                        // store script for retrieval by the browser
+                        String fileName = location;
+                        fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
+                        if (fileName.endsWith(".ftl")) {
+                            fileName = fileName.substring(0, fileName.length() - 4);
+                        }
+                        String key = MultiBlockHtmlTemplateUtil.putScriptInCache(context, fileName, scripts.toString());
+
+                        // construct script link
+                        String webappName = (String) context.get("webappName");
+                        String url = "/" + webappName + "/control/getJs?name=" + key;
+
+                        // add csrf token to script link
+                        HttpServletRequest request = (HttpServletRequest) context.get("request");
+                        String tokenValue = CsrfUtil.generateTokenForNonAjax(request, "getJs");
+                        url = CsrfUtil.addOrUpdateTokenInUrl(url, tokenValue);
+
+                        // store script link to be output by scriptTagsFooter freemarker macro
+                        MultiBlockHtmlTemplateUtil.addScriptLinkForFoot(request, url);
+                    }
+                }
+                // the 'template' block
+                String body = doc.body().html();
+                writer.append(body);
             } else {
-                renderHtmlTemplate(writer, this.locationExdr, context);
+                writer.append(data);
             }
         }
 
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/MultiBlockHtmlTemplateUtil.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/MultiBlockHtmlTemplateUtil.java
index 4392d21..ee722d2 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/MultiBlockHtmlTemplateUtil.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/MultiBlockHtmlTemplateUtil.java
@@ -36,7 +36,7 @@ import java.util.Set;
 public final class MultiBlockHtmlTemplateUtil {
 
     private static final String MODULE = MultiBlockHtmlTemplateUtil.class.getName();
-    public static final String MULTI_BLOCK_WRITER = "multiBlockWriter";
+    public static final String FTL_WRITER = "WriterForFTL";
     private static final String SCRIPT_LINKS_FOR_FOOT = "ScriptLinksForFoot";
     private static int maxScriptCacheSizePerUserSession = 15;
     private static int estimatedConcurrentUserSessions = 250;
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java
index 00852bb..bcb1118 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java
@@ -139,8 +139,8 @@ public class ScreenRenderer {
             }
         } else {
             context.put("renderFormSeqNumber", String.valueOf(renderFormSeqNumber));
-            if (context.get(MultiBlockHtmlTemplateUtil.MULTI_BLOCK_WRITER) != null) {
-                Stack<StringWriter> stringWriterStack = UtilGenerics.cast(context.get(MultiBlockHtmlTemplateUtil.MULTI_BLOCK_WRITER));
+            if (context.get(MultiBlockHtmlTemplateUtil.FTL_WRITER) != null) {
+                Stack<StringWriter> stringWriterStack = UtilGenerics.cast(context.get(MultiBlockHtmlTemplateUtil.FTL_WRITER));
                 modelScreen.renderScreenString(stringWriterStack.peek(), context, screenStringRenderer);
             } else {
                 modelScreen.renderScreenString(writer, context, screenStringRenderer);