Merged master into issue/SLING-7681
* brought in fixes for SLING-7710, SLING-7549
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParser.java b/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParser.java
index 877e8c4..6fce2b3 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParser.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParser.java
@@ -21,6 +21,10 @@
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.Reader;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
/**
* HTML parser. Invokes a <code>DocumentHandler</code> whenever an event occurs.
@@ -68,6 +72,9 @@
/** Expression state constant */
private final static int EXPR_MAYBE = 1;
+ final static Set<String> VOID_ELEMENTS = Collections.unmodifiableSet(new HashSet<>(
+ Arrays.asList("area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr")));
+
/** Parse state */
private PARSE_STATE parseState = PARSE_STATE.OUTSIDE;
@@ -479,9 +486,8 @@
tokenizer.tokenize(snippet, 0, snippet.length);
if (!tokenizer.endTag()) {
- documentHandler.onStartElement(tokenizer.tagName(), tokenizer
- .attributes(), tokenizer
- .endSlash());
+ documentHandler.onStartElement(tokenizer.tagName(), tokenizer.attributes(),
+ tokenizer.endSlash() || VOID_ELEMENTS.contains(tokenizer.tagName().toLowerCase()));
} else {
documentHandler.onEndElement(tokenizer.tagName());
}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java b/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java
index 58c6ccb..0e41724 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java
@@ -32,6 +32,7 @@
import org.apache.sling.scripting.sightly.compiler.commands.VariableBinding;
import org.apache.sling.scripting.sightly.compiler.expression.Expression;
import org.apache.sling.scripting.sightly.compiler.expression.ExpressionNode;
+import org.apache.sling.scripting.sightly.compiler.expression.MarkupContext;
import org.apache.sling.scripting.sightly.compiler.expression.nodes.BinaryOperation;
import org.apache.sling.scripting.sightly.compiler.expression.nodes.BinaryOperator;
import org.apache.sling.scripting.sightly.compiler.expression.nodes.BooleanConstant;
@@ -51,7 +52,6 @@
import org.apache.sling.scripting.sightly.impl.filter.ExpressionContext;
import org.apache.sling.scripting.sightly.impl.filter.Filter;
import org.apache.sling.scripting.sightly.impl.html.MarkupUtils;
-import org.apache.sling.scripting.sightly.compiler.expression.MarkupContext;
import org.apache.sling.scripting.sightly.impl.plugin.Plugin;
import org.apache.sling.scripting.sightly.impl.plugin.PluginCallInfo;
import org.apache.sling.scripting.sightly.impl.plugin.PluginInvoke;
@@ -85,7 +85,15 @@
public void onAttribute(String name, String value, char quoteChar) {
ElementContext context = elementStack.peek();
if (Syntax.isPluginAttribute(name)) {
- handlePlugin(name, StringUtils.defaultString(value, ""), context);
+ try {
+ handlePlugin(name, StringUtils.defaultString(value, ""), context);
+ } catch (SightlyCompilerException e) {
+ if (StringUtils.isEmpty(e.getOffendingInput())) {
+ throw new SightlyCompilerException(e.getMessage(),
+ name + (StringUtils.isNotEmpty(value) ? "=" + quoteChar + value + quoteChar : ""));
+ }
+ throw e;
+ }
} else {
context.addAttribute(name, value, quoteChar);
}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/TreeTraverser.java b/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/TreeTraverser.java
index 30e2701..43611d7 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/TreeTraverser.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/TreeTraverser.java
@@ -57,12 +57,12 @@
}
String tagName = elem.getName();
- if (elem.isHasStartElement()) {
+ if (elem.isStartElement()) {
handler.onOpenTagStart("<" + tagName, tagName);
for (TemplateAttribute attribute : elem.getAttributes()) {
handler.onAttribute(attribute.getName(), attribute.getValue(), attribute.getQuoteChar());
}
- if (elem.isHasEndSlash()) {
+ if (elem.hasEndSlash()) {
handler.onOpenTagEnd("/>");
} else {
handler.onOpenTagEnd(">");
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateElementNode.java b/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateElementNode.java
index 266a748..d6b04be 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateElementNode.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/template/TemplateElementNode.java
@@ -55,11 +55,11 @@
return name;
}
- public boolean isHasEndSlash() {
+ public boolean hasEndSlash() {
return hasEndSlash;
}
- public boolean isHasStartElement() {
+ public boolean isStartElement() {
return hasStartElement;
}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/CallPlugin.java b/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/CallPlugin.java
index 455612e..b666575 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/CallPlugin.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/CallPlugin.java
@@ -39,7 +39,7 @@
final PluginCallInfo callInfo,
final CompilerContext compilerContext) {
if (callInfo.getArguments().length > 0) {
- throw new SightlyCompilerException("Call plugin should have no arguments.", "data-sly-call." + callInfo.getArguments()[0]);
+ throw new SightlyCompilerException("Call plugin should have no arguments.");
}
return new DefaultPluginInvoke() {
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/TemplatePlugin.java b/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/TemplatePlugin.java
index 2890c19..a22116f 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/TemplatePlugin.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/TemplatePlugin.java
@@ -41,6 +41,8 @@
@Override
public PluginInvoke invoke(final Expression expressionNode, final PluginCallInfo callInfo, CompilerContext compilerContext) {
+ final String templateName = decodeName(callInfo);
+
return new DefaultPluginInvoke() {
@Override
@@ -51,9 +53,8 @@
@Override
public void beforeElement(PushStream stream, String tagName) {
- String name = decodeName();
Set<String> parameters = extractParameters();
- stream.write(new Procedure.Start(name, parameters));
+ stream.write(new Procedure.Start(templateName, parameters));
}
@Override
@@ -81,13 +82,14 @@
return options.keySet();
}
- private String decodeName() {
- String[] arguments = callInfo.getArguments();
- if (arguments.length == 0) {
- throw new SightlyCompilerException("Template name was not provided.", "data-sly-template=");
- }
- return arguments[0];
- }
};
}
+
+ private String decodeName(PluginCallInfo callInfo) {
+ String[] arguments = callInfo.getArguments();
+ if (arguments.length == 0) {
+ throw new SightlyCompilerException("Template name was not provided.");
+ }
+ return arguments[0];
+ }
}
diff --git a/src/test/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParserTest.java b/src/test/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParserTest.java
index c849cf7..c1ea70b 100644
--- a/src/test/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParserTest.java
+++ b/src/test/java/org/apache/sling/scripting/sightly/impl/html/dom/HtmlParserTest.java
@@ -18,20 +18,33 @@
******************************************************************************/
package org.apache.sling.scripting.sightly.impl.html.dom;
+import java.io.IOException;
import java.io.StringReader;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import org.apache.commons.lang3.StringUtils;
-import org.apache.sling.scripting.sightly.impl.html.dom.template.*;
-
+import org.apache.sling.scripting.sightly.impl.html.dom.template.Template;
+import org.apache.sling.scripting.sightly.impl.html.dom.template.TemplateCommentNode;
+import org.apache.sling.scripting.sightly.impl.html.dom.template.TemplateElementNode;
+import org.apache.sling.scripting.sightly.impl.html.dom.template.TemplateNode;
+import org.apache.sling.scripting.sightly.impl.html.dom.template.TemplateParser;
+import org.apache.sling.scripting.sightly.impl.html.dom.template.TemplateTextNode;
import org.junit.Test;
import org.powermock.reflect.Whitebox;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class HtmlParserTest {
+ private static final String DATA_TEST_ATTRIBUTE = "data-test-attribute";
+
/**
* Pretty print a template nodes structure for debugging of failed tests
*
@@ -79,7 +92,7 @@
*/
private static void assertSameStructure(TemplateNode reference, TemplateNode parsed) {
if (parsed == null || reference == null) {
- assertTrue("Expecting both null", parsed == reference);
+ assertSame("Expecting both null", parsed, reference);
return;
}
assertEquals("Expecting same class", reference.getClass(), parsed.getClass());
@@ -113,7 +126,7 @@
}
if (parsedChildren == null || referenceChildren == null) {
- assertTrue("Expecting both children null", parsedChildren == referenceChildren);
+ assertSame("Expecting both children null", parsedChildren, referenceChildren);
return;
}
@@ -130,7 +143,7 @@
* Create a basic template nodes structure containing one text nodes and one comment nodes
*
* @param textAndComment - String containing text (optional) and comment
- * @return
+ * @return a reference template
*/
private Template createReference(String textAndComment) {
int commentIx = textAndComment.indexOf("<!");
@@ -185,4 +198,123 @@
throw e;
}
}
+
+ @Test
+ public void testVoidElements() throws IOException {
+ VoidAndSelfClosingElementsDocumentHandler voidAndSelfClosingElementsDocumentHandler = new VoidAndSelfClosingElementsDocumentHandler();
+ for (String tag : HtmlParser.VOID_ELEMENTS) {
+ StringReader stringReader = new StringReader("<" + tag + " " + DATA_TEST_ATTRIBUTE + ">");
+ HtmlParser.parse(stringReader, voidAndSelfClosingElementsDocumentHandler);
+ assertEquals("Parsed tag is not what was expected.", tag, voidAndSelfClosingElementsDocumentHandler.getLastProcessedTag());
+ }
+ assertEquals("Expected 0 invocations for 'onEndElement'", 0, voidAndSelfClosingElementsDocumentHandler.onEndElementInvocations);
+ assertEquals("Expected as many invocations for 'onStartElement' as many void elements.", HtmlParser.VOID_ELEMENTS.size(),
+ voidAndSelfClosingElementsDocumentHandler.onStartElementInvocations);
+
+ HtmlParser.parse(new StringReader("<img " + DATA_TEST_ATTRIBUTE + "/>"), voidAndSelfClosingElementsDocumentHandler);
+ assertEquals("Expected 0 invocations for 'onEndElement'", 0, voidAndSelfClosingElementsDocumentHandler.onEndElementInvocations);
+ assertEquals("Expected as many invocations for 'onStartElement' as many void elements + 1.", HtmlParser.VOID_ELEMENTS.size() + 1,
+ voidAndSelfClosingElementsDocumentHandler.onStartElementInvocations);
+ }
+
+ @Test
+ public void testExplicitlyClosedElements() throws IOException {
+ TestDocumentHandler handler = new TestDocumentHandler();
+ Set<String> tags = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a", "p", "div")));
+ for (String tag : tags) {
+ StringReader stringReader = new StringReader("<" + tag + " " + DATA_TEST_ATTRIBUTE + "></" + tag + ">");
+ HtmlParser.parse(stringReader, handler);
+ assertEquals("Parsed tag is not what was expected.", tag, handler.getLastProcessedTag());
+ }
+ assertEquals("Expected as many invocations for 'onEndElement' as many test elements.", tags.size(),
+ handler.onEndElementInvocations);
+ assertEquals("Expected as many invocations for 'onStartElement' as many test elements.", tags.size(),
+ handler.onStartElementInvocations);
+ }
+
+ abstract class AbstractDocumentHandler implements DocumentHandler {
+
+ private String lastProcessedTag;
+
+ @Override
+ public void onCharacters(char[] ch, int off, int len) {
+
+ }
+
+ @Override
+ public void onComment(String characters) {
+
+ }
+
+ @Override
+ public void onStartElement(String name, AttributeList attList, boolean endSlash) {
+ lastProcessedTag = name;
+ }
+
+ @Override
+ public void onEndElement(String name) {
+
+ }
+
+ @Override
+ public void onStart() {
+
+ }
+
+ @Override
+ public void onEnd() {
+
+ }
+
+ String getLastProcessedTag() {
+ return lastProcessedTag;
+ }
+ }
+
+ class VoidAndSelfClosingElementsDocumentHandler extends AbstractDocumentHandler {
+
+ int onEndElementInvocations = 0;
+ int onStartElementInvocations = 0;
+
+ @Override
+ public void onStartElement(String name, AttributeList attList, boolean endSlash) {
+ super.onStartElement(name, attList, endSlash);
+ assertTrue("Expected a " + DATA_TEST_ATTRIBUTE + ".",
+ attList.attributeCount() == 1 && attList.containsAttribute(DATA_TEST_ATTRIBUTE) &&
+ attList.getValue(DATA_TEST_ATTRIBUTE) == null);
+ assertTrue("Expected a self-closing attribute.", endSlash);
+ onStartElementInvocations++;
+ }
+
+ @Override
+ public void onEndElement(String name) {
+ super.onEndElement(name);
+ onEndElementInvocations++;
+ }
+ }
+
+ class TestDocumentHandler extends AbstractDocumentHandler {
+ int onEndElementInvocations = 0;
+ int onStartElementInvocations = 0;
+
+ @Override
+ public void onStartElement(String name, AttributeList attList, boolean endSlash) {
+ super.onStartElement(name, attList, endSlash);
+ assertTrue("Expected a " + DATA_TEST_ATTRIBUTE + ".",
+ attList.attributeCount() == 1 && attList.containsAttribute(DATA_TEST_ATTRIBUTE) &&
+ attList.getValue(DATA_TEST_ATTRIBUTE) == null);
+ assertFalse("Did not expect a self-closing attribute.", endSlash);
+ onStartElementInvocations++;
+ }
+
+ @Override
+ public void onEndElement(String name) {
+ super.onEndElement(name);
+ onEndElementInvocations++;
+ }
+ }
+
+
+
+
}