Skip to content

Commit

Permalink
More fixes for nested ifs. Also now allows same template to be includ…
Browse files Browse the repository at this point in the history
…ed more than once by buffering the include contents. The root document does not buffer.
  • Loading branch information
brett-smith committed Mar 13, 2024
1 parent d120f22 commit 5917c20
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 37 deletions.
100 changes: 64 additions & 36 deletions src/main/java/com/sshtools/tinytemplate/Templates.java
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,6 @@ public static TemplateModel ofResource(String resource, Optional<ClassLoader> lo

}

final Reader text;
final Map<String, Supplier<Boolean>> conditions = new HashMap<>();
final Map<String, Function<String, List<TemplateModel>>> lists = new HashMap<>();
final Map<String, Function<String, TemplateModel>> templates = new HashMap<>();
Expand All @@ -404,11 +403,14 @@ public static TemplateModel ofResource(String resource, Optional<ClassLoader> lo
final Map<String, Supplier<?>> defaultVariables = new HashMap<>();
Optional<Supplier<Locale>> locale = Optional.empty();
Optional<TemplateModel> parent = Optional.empty();

private final Reader xtext;
private StringBuilder buffer = null;

public final static Object[] NO_ARGS = new Object[0];

private TemplateModel(Reader text) {
this.text = text;
this.xtext = text;

defaultVariableStore = new VariableStore() {

Expand All @@ -425,11 +427,40 @@ public Object apply(String key) {
};
variables.add(defaultVariableStore);
}

Reader text(boolean cached) {
if(buffer == null) {
var buffer = new StringBuilder();
return new Reader() {

@Override
public int read(char[] cbuf, int off, int len) throws IOException {
var r = xtext.read(cbuf, off, len);
if(r > -1) {
buffer.append(new String(cbuf, off, len));
}
else {
TemplateModel.this.buffer = buffer;
}
return r;
}

@Override
public void close() throws IOException {
buffer.setLength(0);
xtext.close();
}
};
}
else {
return new StringReader(buffer.toString());
}
}

@Override
public void close() {
try {
text.close();
xtext.close();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
Expand Down Expand Up @@ -724,7 +755,7 @@ private final static class Block {
State state = State.START;
boolean match = true;
boolean capture = false;
boolean inElse = false;
boolean inElse;
int nestDepth = 0;

Block(TemplateModel model, VariableExpander expander, Reader reader/* , String scope */) {
Expand All @@ -738,6 +769,7 @@ private final static class Block {
this.reader = reader;
this.scope = scope;
}

}

private TemplateProcessor(Builder bldr) {
Expand All @@ -749,7 +781,7 @@ private TemplateProcessor(Builder bldr) {
}

public String process(TemplateModel model) {
var block = new Block(model, getExpanderForModel(model), model.text);
var block = new Block(model, getExpanderForModel(model), model.text(false));
read(block);

return block.out.toString();
Expand Down Expand Up @@ -793,7 +825,8 @@ private void read(Block block) {
continue;
}

var process = !block.capture && ((block.match && !block.inElse) || (block.match && block.inElse));
//var process = !block.capture && ((block.match && !block.inElse) || (block.match && block.inElse));
var process = !block.capture && block.match;

switch (block.state) {
case START:
Expand Down Expand Up @@ -857,9 +890,6 @@ else if (ch == 't') {
case T_TAG_NAME:
if (ch == '>') {
var directive = buf.toString().substring(1).trim();
if(directive.startsWith("t:if ") || directive.startsWith("t:list ") || directive.startsWith("t:object ")) {
block.nestDepth++;
}
if(process) {
if(processDirective(block, directive)) {
buf.setLength(0);
Expand All @@ -870,6 +900,9 @@ else if (ch == 't') {
}
}
else {
if(directive.startsWith("t:if ") || directive.startsWith("t:list ") || directive.startsWith("t:object ")) {
block.nestDepth++;
}
flushBuf(ch, buf, block);
block.state = State.START;
}
Expand Down Expand Up @@ -901,29 +934,21 @@ else if (ch == 't') {
case T_TAG_END:
if(ch == '>') {
var directive = buf.toString().substring(4).trim();
var isNest = directive.equals("if") || directive.equals("list");
var isNest = directive.equals("if") || directive.equals("list") || directive.equals("object");
if(isNest) {
block.nestDepth--;
}
try {
if(directive.equals(block.scope) && (!isNest || (isNest && block.nestDepth == 0))) {
logger.ifPresent(lg -> lg.debug("Leaving scope {0}", block.scope));
return;
} else {
if(directive.equals(block.scope) && isNest && process) {
buf.setLength(0);
}
else {
flushBuf(ch, buf, block);
}
block.state = State.START;
if(directive.equals(block.scope) && (!isNest || (isNest && block.nestDepth == 0))) {
logger.ifPresent(lg -> lg.debug("Leaving scope {0}", block.scope));
return;
} else {
if(directive.equals(block.scope) && isNest && process) {
buf.setLength(0);
}
}
finally {
if(directive.equals("if") && block.inElse) {
block.inElse = false;
block.match = !block.match;
else {
flushBuf(ch, buf, block);
}
block.state = State.START;
}
}
else {
Expand All @@ -932,10 +957,13 @@ else if (ch == 't') {
break;
case T_TAG_LEAF_END:
if (ch == '>') {
if(process || (block.scope.endsWith("if") && !block.inElse && !block.match)) {
var directive = buf.toString().substring(1, buf.length() - 1);
while(directive.endsWith("/"))
directive = directive.substring(0, directive.length() - 1);
var directive = buf.toString().substring(1, buf.length() - 1);
while(directive.endsWith("/"))
directive = directive.substring(0, directive.length() - 1);

var aboutToProcess = !process && directive.equals("t:else");

if (process || aboutToProcess) {
directive = directive.trim();
if(processDirective(block, directive)) {
buf.setLength(0);
Expand Down Expand Up @@ -991,7 +1019,7 @@ private boolean processDirective(Block block, String directive) {
if(condition.negate)
match = !match;

var ifBlock = new Block(block.model, getExpanderForModel(block.model), block.model.text, "if", match);
var ifBlock = new Block(block.model, getExpanderForModel(block.model), block.model.text(true), "if", match);
ifBlock.nestDepth = 1;

read(ifBlock);
Expand All @@ -1012,7 +1040,7 @@ else if(dir.equals("t:include")) {
return false;
} else {
var include = includeModel.get();
var incBlock = new Block(include, getExpanderForModel(include), include.text);
var incBlock = new Block(include, getExpanderForModel(include), include.text(true));
read(incBlock);
block.out.append(incBlock.out.toString());
block.state = State.START;
Expand Down Expand Up @@ -1044,7 +1072,7 @@ else if(dir.equals("t:object")) {
var was = templ.parent;
try {
templ.parent = Optional.of(block.model);
var listBlock = new Block(templ, getExpanderForModel(templ), templ.text);
var listBlock = new Block(templ, getExpanderForModel(templ), templ.text(true));
read(listBlock);
block.out.append(listBlock.out.toString());
}
Expand Down Expand Up @@ -1081,7 +1109,7 @@ else if(dir.equals("t:list")) {
templ.variable("_index", index);
templ.variable("_first", index == 0);
templ.variable("_last", index == templates.size() - 1);
var listBlock = new Block(templ, getExpanderForModel(templ), templ.text);
var listBlock = new Block(templ, getExpanderForModel(templ), templ.text(true));
read(listBlock);
block.out.append(listBlock.out.toString());
}
Expand Down
75 changes: 74 additions & 1 deletion src/test/java/com/sshtools/tinytemplate/TemplatesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,54 @@ public void testTemplateNestedIf2() {

}

@Test
public void testTemplateElseWithNestedIfThatsTrue() {
Assertions.assertEquals(
"\n" +
" \n" +
" <div class=\"form-floating\">\n" +
" <textarea>Some content with blahg</textarea>\n" +
" <label for=\"12345\" class=\"label-class\">Some label</label>\n" +
" </div>\n" +
" \n" +
"\n" +
"\n" +
" <div id=\"zzzzzHelp\" class=\"form-text text-muted\">Some help</div>\n\n"
,
createParser().process(TemplateModel.ofContent("""
<t:if label>
<t:if label.floating>
<div class="form-floating">
<t:include input/>
<label for="${input.id}" class="${label.class}">${label}</label>
</div>
<t:else/>
<t:if label.first>
<label for="${input.id}" class="${label.class}">${label}</label>
</t:if>
<t:include input/>
<t:if !label.first>
<label for="${input.id}" class="${label.class}">${label}</label>
</t:if>
</t:if>
<t:else/>
<t:include input/>
</t:if>
<t:if help>
<div id="${id}Help" class="form-text text-muted">${help}</div>
</t:if>
""").
variable("label", "Some label").
variable("help", "Some help").
variable("input.id", "12345").
include("input", TemplateModel.ofContent("<textarea>Some content with ${aVar}</textarea>").variable("aVar", "blahg")).
variable("id", "zzzzz").
condition("has.label", true).
condition("label.first", false).
condition("label.floating", true).
variable("label.class", "label-class")));
}

@Test
public void testTemplateNestedIf3() {
Assertions.assertEquals("""
Expand All @@ -890,7 +938,7 @@ public void testTemplateNestedIf3() {
<p>Outside</p>
<t:if bCondition>
<p>Don't show this</p>
<t:if bcondition>
<t:if bCondition>
<p>Or this</p>
</t:if>
</t:if>
Expand Down Expand Up @@ -931,6 +979,31 @@ public void testTemplateObjectMissing() {

}

@Test
public void testTemplateIncludeMorThanOnce() {
Assertions.assertEquals("""
<html>
<body>
<p>First Time
Some content
Some content
</body>
</html>
""",
createParser().process(
TemplateModel.ofContent("""
<html>
<body>
<p>First Time
<t:include templ/>
<t:include templ/>
</body>
</html>
""").
include("templ", TemplateModel.ofContent("Some content"))));

}

@Test
public void testTemplateObject() {
Assertions.assertEquals("""
Expand Down

0 comments on commit 5917c20

Please sign in to comment.