diff --git a/src/main/java/com/sshtools/tinytemplate/Templates.java b/src/main/java/com/sshtools/tinytemplate/Templates.java index 19bbf86..35e2bc8 100644 --- a/src/main/java/com/sshtools/tinytemplate/Templates.java +++ b/src/main/java/com/sshtools/tinytemplate/Templates.java @@ -393,7 +393,6 @@ public static TemplateModel ofResource(String resource, Optional lo } - final Reader text; final Map> conditions = new HashMap<>(); final Map>> lists = new HashMap<>(); final Map> templates = new HashMap<>(); @@ -404,11 +403,14 @@ public static TemplateModel ofResource(String resource, Optional lo final Map> defaultVariables = new HashMap<>(); Optional> locale = Optional.empty(); Optional 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() { @@ -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); } @@ -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 */) { @@ -738,6 +769,7 @@ private final static class Block { this.reader = reader; this.scope = scope; } + } private TemplateProcessor(Builder bldr) { @@ -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(); @@ -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: @@ -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); @@ -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; } @@ -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 { @@ -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); @@ -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); @@ -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; @@ -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()); } @@ -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()); } diff --git a/src/test/java/com/sshtools/tinytemplate/TemplatesTest.java b/src/test/java/com/sshtools/tinytemplate/TemplatesTest.java index c8ee90a..b41d1a6 100644 --- a/src/test/java/com/sshtools/tinytemplate/TemplatesTest.java +++ b/src/test/java/com/sshtools/tinytemplate/TemplatesTest.java @@ -870,6 +870,54 @@ public void testTemplateNestedIf2() { } + @Test + public void testTemplateElseWithNestedIfThatsTrue() { + Assertions.assertEquals( + "\n" + + " \n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + " \n" + + "\n" + + "\n" + + "
Some help
\n\n" + , + createParser().process(TemplateModel.ofContent(""" + + +
+ + +
+ + + + + + + + +
+ + +
+ +
${help}
+
+ """). + variable("label", "Some label"). + variable("help", "Some help"). + variable("input.id", "12345"). + include("input", TemplateModel.ofContent("").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(""" @@ -890,7 +938,7 @@ public void testTemplateNestedIf3() {

Outside

Don't show this

- +

Or this

@@ -931,6 +979,31 @@ public void testTemplateObjectMissing() { } + @Test + public void testTemplateIncludeMorThanOnce() { + Assertions.assertEquals(""" + + +

First Time + Some content + Some content + + + """, + createParser().process( + TemplateModel.ofContent(""" + + +

First Time + + + + + """). + include("templ", TemplateModel.ofContent("Some content")))); + + } + @Test public void testTemplateObject() { Assertions.assertEquals("""