Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ public interface BuiltInFeatures {
"IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS";
String OUTPUT_UNDEFINED_VARIABLES_ERROR = "OUTPUT_UNDEFINED_VARIABLES_ERROR";
String INTEGER_SET_TO_LONG_CONVERSION = "INTEGER_SET_TO_LONG_CONVERSION";
String PRESERVE_VOID_IN_TRUNCATE_HTML = "PRESERVE_VOID_IN_TRUNCATE_HTML";
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.hubspot.jinjava.doc.annotations.JinjavaDoc;
import com.hubspot.jinjava.doc.annotations.JinjavaParam;
import com.hubspot.jinjava.doc.annotations.JinjavaSnippet;
import com.hubspot.jinjava.features.BuiltInFeatures;
import com.hubspot.jinjava.interpret.InvalidArgumentException;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.interpret.TemplateError;
Expand Down Expand Up @@ -144,11 +145,20 @@ public Object filter(Object var, JinjavaInterpreter interpreter, String... args)
killwords = BooleanUtils.toBoolean(args[2]);
}

boolean preserveVoidElements = interpreter
.getConfig()
.getFeatures()
.isActive(
BuiltInFeatures.PRESERVE_VOID_IN_TRUNCATE_HTML,
interpreter.getContext()
);

Document dom = Jsoup.parseBodyFragment((String) var);
ContentTruncatingNodeVisitor visitor = new ContentTruncatingNodeVisitor(
length,
ends,
killwords
killwords,
preserveVoidElements
);
dom.select("body").traverse(visitor);
dom.select("." + DELETE_MARKER_CLASS).remove();
Expand All @@ -165,13 +175,20 @@ private static class ContentTruncatingNodeVisitor implements NodeVisitor {
private int textLen;
private final String ending;
private final boolean killwords;
private final boolean preserveVoidElements;
private final Deque<Integer> keptVoidElementsAtEntry = new ArrayDeque<>();
private int keptVoidElements;

ContentTruncatingNodeVisitor(int maxTextLen, String ending, boolean killwords) {
ContentTruncatingNodeVisitor(
int maxTextLen,
String ending,
boolean killwords,
boolean preserveVoidElements
) {
this.maxTextLen = maxTextLen;
this.ending = ending;
this.killwords = killwords;
this.preserveVoidElements = preserveVoidElements;
}

@Override
Expand All @@ -193,7 +210,7 @@ public void head(Node node, int depth) {
} else {
textLen += textContent.length();
}
} else if (node instanceof Element el) {
} else if (preserveVoidElements && node instanceof Element el) {
keptVoidElementsAtEntry.push(keptVoidElements);
if (isVoidElement(el)) {
if (textLen >= maxTextLen) {
Expand All @@ -207,15 +224,19 @@ public void head(Node node, int depth) {

@Override
public void tail(Node node, int depth) {
if (node instanceof Element el) {
boolean containsKeptVoidElement =
keptVoidElements > keptVoidElementsAtEntry.pop();
if (
!isVoidElement(el) && !containsKeptVoidElement && StringUtils.isBlank(el.text())
) {
el.addClass(DELETE_MARKER_CLASS);
}
if (node instanceof Element el && shouldRemoveOnExit(el)) {
el.addClass(DELETE_MARKER_CLASS);
}
}

private boolean shouldRemoveOnExit(Element el) {
if (!preserveVoidElements) {
return StringUtils.isBlank(el.text());
}
boolean containsKeptVoidElement = keptVoidElements > keptVoidElementsAtEntry.pop();
return (
!isVoidElement(el) && !containsKeptVoidElement && StringUtils.isBlank(el.text())
);
}

private static boolean isVoidElement(Element el) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Resources;
import com.hubspot.jinjava.BaseInterpretingTest;
import com.hubspot.jinjava.BaseJinjavaTest;
import com.hubspot.jinjava.Jinjava;
import com.hubspot.jinjava.features.BuiltInFeatures;
import com.hubspot.jinjava.features.FeatureConfig;
import com.hubspot.jinjava.features.FeatureStrategies;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.junit.Before;
Expand All @@ -13,10 +19,27 @@
public class TruncateHtmlFilterTest extends BaseInterpretingTest {

TruncateHtmlFilter filter;
JinjavaInterpreter preserveVoidInterpreter;

@Before
public void setup() {
filter = new TruncateHtmlFilter();
preserveVoidInterpreter =
new Jinjava(
BaseJinjavaTest
.newConfigBuilder()
.withFeatureConfig(
FeatureConfig
.newBuilder()
.add(
BuiltInFeatures.PRESERVE_VOID_IN_TRUNCATE_HTML,
FeatureStrategies.ACTIVE
)
.build()
)
.build()
)
.newInterpreter();
}

@Test
Expand Down Expand Up @@ -82,7 +105,7 @@ public void itTakesKwargs() {
public void itPreservesVoidElementsWhenContentIsUnderTheLimit() {
String result = (String) filter.filter(
"<p>Hey,<br>Great to hear!</p><p><img src=\"a.png\"></p>",
interpreter,
preserveVoidInterpreter,
"255"
);
assertThat(result)
Expand All @@ -93,7 +116,7 @@ public void itPreservesVoidElementsWhenContentIsUnderTheLimit() {
public void itPreservesVoidElementsBeforeTheTruncationPoint() {
String result = (String) filter.filter(
"<p>Hello<br>world, this is long text</p>",
interpreter,
preserveVoidInterpreter,
"10",
"...",
"true"
Expand All @@ -105,7 +128,7 @@ public void itPreservesVoidElementsBeforeTheTruncationPoint() {
public void itRemovesVoidElementsAfterTheTruncationPoint() {
String result = (String) filter.filter(
"<p>Hello world</p><p>more<br>text</p><img src=\"a.png\">",
interpreter,
preserveVoidInterpreter,
"5",
"...",
"true"
Expand All @@ -117,7 +140,7 @@ public void itRemovesVoidElementsAfterTheTruncationPoint() {
public void itPreservesSelfClosingBrTagsBeforeTheTruncationPoint() {
String result = (String) filter.filter(
"<p>Hey,<br/>Great to hear!</p>",
interpreter,
preserveVoidInterpreter,
"255"
);
assertThat(result).isEqualTo("<p>Hey,\n <br>\n Great to hear!</p>");
Expand All @@ -127,7 +150,7 @@ public void itPreservesSelfClosingBrTagsBeforeTheTruncationPoint() {
public void itRemovesSelfClosingBrTagsAfterTheTruncationPoint() {
String result = (String) filter.filter(
"<p>Hello world<br/>more text</p><br/>",
interpreter,
preserveVoidInterpreter,
"5",
"...",
"true"
Expand All @@ -139,7 +162,7 @@ public void itRemovesSelfClosingBrTagsAfterTheTruncationPoint() {
public void itPreservesVoidElementsWithExplicitClosingTagsBeforeTheTruncationPoint() {
String result = (String) filter.filter(
"<p><img src=\"a.png\" width=\"10\"></img>Great to hear!</p>",
interpreter,
preserveVoidInterpreter,
"255"
);
assertThat(result).isEqualTo("<p><img src=\"a.png\" width=\"10\">Great to hear!</p>");
Expand All @@ -149,14 +172,36 @@ public void itPreservesVoidElementsWithExplicitClosingTagsBeforeTheTruncationPoi
public void itRemovesVoidElementsWithExplicitClosingTagsAfterTheTruncationPoint() {
String result = (String) filter.filter(
"<p>Hello world</p><p><img src=\"a.png\" width=\"10\"></img></p>",
interpreter,
preserveVoidInterpreter,
"5",
"...",
"true"
);
assertThat(result).isEqualTo("<p>Hello...</p>");
}

@Test
public void itRemovesVoidElementsWhenFeatureIsInactive() {
String result = (String) filter.filter(
"<p>Hey,<br>Great to hear!</p><p><img src=\"a.png\"></p>",
interpreter,
"255"
);
assertThat(result).isEqualTo("<p>Hey,Great to hear!</p>");
}

@Test
public void itRemovesVoidElementsBeforeTheTruncationPointWhenFeatureIsInactive() {
String result = (String) filter.filter(
"<p>Hello<br>world, this is long text</p>",
interpreter,
"10",
"...",
"true"
);
assertThat(result).isEqualTo("<p>Helloworld...</p>");
}

private static String fixture(String name) {
try {
return Resources.toString(Resources.getResource(name), StandardCharsets.UTF_8);
Expand Down
Loading