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

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

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

James Yong-2
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 aab02b5  Improved: Well-formed html in ftl template (OFBIZ-11996)
aab02b5 is described below

commit aab02b514275a096f5bbb2dbb04ead601e0cfc62
Author: James Yong <[hidden email]>
AuthorDate: Thu Sep 10 13:10:32 2020 +0800

    Improved: Well-formed html in ftl template (OFBIZ-11996)
   
    Add code to check closing tags and refactoring
---
 .../java/org/apache/ofbiz/base/util/UtilHtml.java  | 109 +++++++++++++++++++++
 .../org/apache/ofbiz/base/util/UtilHtmlTest.java   |  34 +++++++
 .../org/apache/ofbiz/widget/model/HtmlWidget.java  |  33 ++-----
 3 files changed, 153 insertions(+), 23 deletions(-)

diff --git a/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilHtml.java b/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilHtml.java
new file mode 100644
index 0000000..39f185e
--- /dev/null
+++ b/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilHtml.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * 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.
+ *******************************************************************************/
+package org.apache.ofbiz.base.util;
+
+import org.jsoup.parser.ParseError;
+import org.jsoup.parser.Parser;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Stack;
+
+public final class UtilHtml {
+
+    private static final String MODULE = UtilHtml.class.getName();
+    private static final Parser JSOUP_HTML_PARSER = createJSoupHtmlParser();
+    private static final String[] TAG_SHOULD_CLOSE_LIST = new String[]{"div"};
+    private UtilHtml() { }
+
+    private static Parser createJSoupHtmlParser() {
+        Parser parser = Parser.htmlParser();
+        parser.setTrackErrors(100);
+        return parser;
+    }
+
+    public static List<ParseError> validateHtmlFragmentWithJSoup(String content) {
+        if (content != null) {
+            JSOUP_HTML_PARSER.parseInput(content, "");
+            if (JSOUP_HTML_PARSER.isTrackErrors()) {
+                return JSOUP_HTML_PARSER.getErrors();
+            }
+        }
+        return null;
+    }
+
+    /**
+     *
+     * @param content
+     * @param locationInfo for printing location information
+     * @return true if there is error
+     */
+    public static boolean hasUnclosedTag(String content, String locationInfo) {
+        XMLInputFactory inputFactory = XMLInputFactory.newInstance();
+        XMLEventReader eventReader = null;
+        try {
+            eventReader = inputFactory.createXMLEventReader(
+                    new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), "utf-8");
+        } catch (XMLStreamException e) {
+            Debug.logError(e.getMessage(), MODULE);
+            return true;
+        }
+
+        Stack<StartElement> stack = new Stack<StartElement>();
+        boolean hasError = false;
+        while (eventReader.hasNext()) {
+            try {
+                XMLEvent event = eventReader.nextEvent();
+                if (event.isStartElement()) {
+                    StartElement startElement = event.asStartElement();
+                    stack.push(startElement);
+                }
+                if (event.isEndElement()) {
+                    EndElement endElement = event.asEndElement();
+                    stack.pop();
+                }
+            } catch (XMLStreamException e) {
+                if (!stack.isEmpty()) {
+                    StartElement startElement = stack.pop();
+                    String elementName = startElement.getName().getLocalPart();
+                    if (Arrays.stream(TAG_SHOULD_CLOSE_LIST).anyMatch(elementName::equals)) {
+                        hasError = true;
+                        UtilHtml.logFormattedError(content, locationInfo, e.getMessage(), MODULE);
+                    }
+                } else {
+                    UtilHtml.logFormattedError(content, locationInfo, e.getMessage(), MODULE);
+                }
+                break;
+            }
+        }
+        return hasError;
+    }
+
+    public static void logFormattedError(String content, String location, String error, String module) {
+        Debug.logError("[Parsing " + location + "]" + error, module);
+    }
+}
diff --git a/framework/base/src/test/java/org/apache/ofbiz/base/util/UtilHtmlTest.java b/framework/base/src/test/java/org/apache/ofbiz/base/util/UtilHtmlTest.java
new file mode 100644
index 0000000..e5a132e
--- /dev/null
+++ b/framework/base/src/test/java/org/apache/ofbiz/base/util/UtilHtmlTest.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * 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.
+ *******************************************************************************/
+package org.apache.ofbiz.base.util;
+
+import org.junit.Test;
+
+import javax.xml.stream.XMLStreamException;
+
+import static org.junit.Assert.assertEquals;
+
+public class UtilHtmlTest {
+
+    @Test
+    public void parseHtmlFragment_1() throws XMLStreamException {
+        assertEquals(true, UtilHtml.hasUnclosedTag("<div><div></div>", "location.ftl"));
+    }
+}
+
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 163fffd..b3368e0 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
@@ -31,6 +31,7 @@ import org.apache.ofbiz.base.util.Debug;
 import org.apache.ofbiz.base.util.GeneralException;
 import org.apache.ofbiz.base.util.UtilCodec;
 import org.apache.ofbiz.base.util.UtilGenerics;
+import org.apache.ofbiz.base.util.UtilHtml;
 import org.apache.ofbiz.base.util.UtilValidate;
 import org.apache.ofbiz.base.util.UtilXml;
 import org.apache.ofbiz.base.util.cache.UtilCache;
@@ -44,8 +45,6 @@ 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;
 
@@ -270,32 +269,20 @@ public class HtmlWidget extends ModelScreenWidget {
             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);
+                // check for unclosed tags
+                boolean hasError = false; //UtilHtml.hasUnclosedTag(data, location);
+                if (!hasError) {
+                    List<ParseError> errList = UtilHtml.validateHtmlFragmentWithJSoup(data);
+                    if (UtilValidate.isNotEmpty(errList)) {
+                        Consumer<ParseError> logError = a -> UtilHtml.logFormattedError(data, location, a.toString(), MODULE);
+                        errList.forEach(logError);
+                    }
                 }
-            } else {
-                doc = Jsoup.parseBodyFragment(data);
             }
 
             if (isMultiBlock()) {
+                Document doc = Jsoup.parseBodyFragment(data);
                 // extract js script tags
                 Elements scriptElements = doc.select("script");
                 if (scriptElements != null && scriptElements.size() > 0) {