Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed whitespace handling for partials #212

Merged
merged 5 commits into from
Mar 30, 2021
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 @@ -35,7 +35,7 @@ public class DefaultMustacheFactory implements MustacheFactory {
/**
* This parser should work with any MustacheFactory
*/
protected final MustacheParser mc = new MustacheParser(this);
protected final MustacheParser mc = createParser();

/**
* New templates that are generated at runtime are cached here. The template key
Expand Down Expand Up @@ -259,6 +259,10 @@ public Mustache compilePartial(String s) {
}
}

protected MustacheParser createParser() {
return new MustacheParser(this);
}

protected Function<String, Mustache> getMustacheCacheFunction() {
return mc::compile;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public void checkName(TemplateContext templateContext, String variable, Mustache
}

@Override
public void partial(TemplateContext tc, final String variable) {
public void partial(TemplateContext tc, final String variable, String indent) {
TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine());
list.add(new PartialCode(partialTC, df, variable));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public MustacheVisitor createMustacheVisitor() {
final AtomicLong id = new AtomicLong(0);
return new DefaultMustacheVisitor(this) {
@Override
public void partial(TemplateContext tc, final String variable) {
public void partial(TemplateContext tc, final String variable, final String indent) {
TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine());
final Long divid = id.incrementAndGet();
list.add(new PartialCode(partialTC, df, variable) {
Expand Down
21 changes: 19 additions & 2 deletions compiler/src/main/java/com/github/mustachejava/MustacheParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@
public class MustacheParser {
public static final String DEFAULT_SM = "{{";
public static final String DEFAULT_EM = "}}";
private final boolean specConformWhitespace;
private MustacheFactory mf;

protected MustacheParser(MustacheFactory mf) {
protected MustacheParser(MustacheFactory mf, boolean specConformWhitespace) {
this.mf = mf;
this.specConformWhitespace = specConformWhitespace;
}

protected MustacheParser(MustacheFactory mf) {
this(mf, false);
}

public Mustache compile(String file) {
Expand Down Expand Up @@ -181,9 +187,20 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger
return mv.mustache(new TemplateContext(sm, em, file, 0, startOfLine));
}
case '>': {
String indent = (onlywhitespace && startOfLine) ? out.toString() : "";
out = write(mv, out, file, currentLine.intValue(), startOfLine);
startOfLine = startOfLine & onlywhitespace;
mv.partial(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable);
mv.partial(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable, indent);

// a new line following a partial is dropped
if (specConformWhitespace && startOfLine) {
br.mark(2);
int ca = br.read();
if (ca == '\r') {
ca = br.read();
}
if (ca != '\n') br.reset();
}
break;
}
case '{': {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public interface MustacheVisitor {

void notIterable(TemplateContext templateContext, String variable, Mustache mustache);

void partial(TemplateContext templateContext, String variable);
void partial(TemplateContext templateContext, String variable, String indent);

void value(TemplateContext templateContext, String variable, boolean encoded);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.github.mustachejava;

import com.github.mustachejava.resolver.DefaultResolver;

import java.io.File;

/**
* This factory is similar to DefaultMustacheFactory but handles whitespace according to the mustache specification.
* Therefore the rendering is less performant than with the DefaultMustacheFactory.
*/
public class SpecMustacheFactory extends DefaultMustacheFactory {
@Override
public MustacheVisitor createMustacheVisitor() {
return new SpecMustacheVisitor(this);
}

public SpecMustacheFactory() {
super();
}

public SpecMustacheFactory(MustacheResolver mustacheResolver) {
super(mustacheResolver);
}

/**
* Use the classpath to resolve mustache templates.
*
* @param classpathResourceRoot the location in the resources where templates are stored
*/
public SpecMustacheFactory(String classpathResourceRoot) {
super(classpathResourceRoot);
}

/**
* Use the file system to resolve mustache templates.
*
* @param fileRoot the root of the file system where templates are stored
*/
public SpecMustacheFactory(File fileRoot) {
super(fileRoot);
}

@Override
protected MustacheParser createParser() {
return new MustacheParser(this, true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.github.mustachejava;

import com.github.mustachejava.codes.PartialCode;
import com.github.mustachejava.codes.ValueCode;
import com.github.mustachejava.util.IndentWriter;

import java.io.IOException;
import java.io.Writer;
import java.util.List;

public class SpecMustacheVisitor extends DefaultMustacheVisitor {
public SpecMustacheVisitor(DefaultMustacheFactory df) {
super(df);
}

@Override
public void partial(TemplateContext tc, final String variable, String indent) {
TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine());
list.add(new SpecPartialCode(partialTC, df, variable, indent));
}

@Override
public void value(TemplateContext tc, final String variable, boolean encoded) {
list.add(new SpecValueCode(tc, df, variable, encoded));
}

static class SpecPartialCode extends PartialCode {
private final String indent;

public SpecPartialCode(TemplateContext tc, DefaultMustacheFactory cf, String variable, String indent) {
super(tc, cf, variable);
this.indent = indent;
}

@Override
protected Writer executePartial(Writer writer, final List<Object> scopes) {
partial.execute(new IndentWriter(writer, indent), scopes);
return writer;
}
}

static class SpecValueCode extends ValueCode {

public SpecValueCode(TemplateContext tc, DefaultMustacheFactory df, String variable, boolean encoded) {
super(tc, df, variable, encoded);
}

@Override
protected void execute(Writer writer, final String value) throws IOException {
if (writer instanceof IndentWriter) {
IndentWriter iw = (IndentWriter) writer;
iw.flushIndent();
writer = iw.inner;
while (writer instanceof IndentWriter) {
writer = ((IndentWriter) writer).inner;
}
}

super.execute(writer, value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class PartialCode extends DefaultCode {

protected PartialCode(TemplateContext tc, DefaultMustacheFactory df, Mustache mustache, String type, String variable) {
super(tc, df, mustache, variable, type);

// Use the name of the parent to get the name of the partial
String file = tc.file();
int dotindex = file.lastIndexOf(".");
Expand Down Expand Up @@ -67,14 +68,18 @@ public Writer execute(Writer writer, final List<Object> scopes) {
}
writer = depthLimitedWriter;
}
Writer execute = partial.execute(writer, scopes);
Writer execute = executePartial(writer, scopes);
if (isRecursive) {
assert depthLimitedWriter != null;
depthLimitedWriter.decr();
}
return appendText(execute);
}

protected Writer executePartial(Writer writer, final List<Object> scopes) {
return partial.execute(writer, scopes);
}

@Override
public synchronized void init() {
filterText();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.github.mustachejava.util;

import java.io.IOException;
import java.io.Writer;

public class IndentWriter extends Writer {

public final Writer inner;
private final String indent;
private boolean prependIndent = false;

public IndentWriter(Writer inner, String indent) {
this.inner = inner;
this.indent = indent;
}

@Override
public void write(char[] chars, int off, int len) throws IOException {
int newOff = off;
for (int i = newOff; i < len; ++i) {
if (chars[i] == '\n') {
// write character up to newline
writeLine(chars, newOff, i + 1 - newOff);
this.prependIndent = true;

newOff = i + 1;
}
}
writeLine(chars, newOff, len - (newOff - off));
}

public void flushIndent() throws IOException {
if (this.prependIndent) {
inner.append(indent);
this.prependIndent = false;
}
}

private void writeLine(char[] chars, int off, int len) throws IOException {
if (len <= 0) {
return;
}

this.flushIndent();
inner.write(chars, off, len);
}

@Override
public void flush() throws IOException {
inner.flush();
}

@Override
public void close() throws IOException {
inner.close();
}
}
56 changes: 56 additions & 0 deletions compiler/src/test/java/com/github/mustachejava/FullSpecTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.github.mustachejava;

import com.fasterxml.jackson.databind.JsonNode;
import org.junit.Ignore;
import org.junit.Test;

import java.io.Reader;
import java.io.StringReader;

public class FullSpecTest extends SpecTest {
@Override
@Test
@Ignore("not ready yet")
public void interpolations() {
}

@Override
@Test
@Ignore("not ready yet")
public void sections() {
}

@Override
@Test
@Ignore("not ready yet")
public void delimiters() {
}

@Override
@Test
@Ignore("not ready yet")
public void inverted() {
}

@Override
@Test
@Ignore("not ready yet")
public void lambdas() {
}

@Override
protected DefaultMustacheFactory createMustacheFactory(final JsonNode test) {
return new SpecMustacheFactory("/spec/specs") {
@Override
public Reader getReader(String resourceName) {
JsonNode partial = test.get("partials").get(resourceName);
return new StringReader(partial == null ? "" : partial.asText());
}
};
}

@Override
protected String transformOutput(String output) {
return output;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ public void testDynamicPartial() throws MustacheException, IOException {
public MustacheVisitor createMustacheVisitor() {
return new DefaultMustacheVisitor(this) {
@Override
public void partial(TemplateContext tc, String variable) {
public void partial(TemplateContext tc, String variable, String indent) {
if (variable.startsWith("+")) {
// This is a dynamic partial rather than a static one
TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine());
Expand Down Expand Up @@ -713,7 +713,7 @@ public Writer execute(Writer writer, List<Object> scopes) {
}
});
} else {
super.partial(tc, variable);
super.partial(tc, variable, indent);
}
}
};
Expand Down Expand Up @@ -1222,7 +1222,7 @@ public void testOverrideExtension() throws IOException {
public MustacheVisitor createMustacheVisitor() {
return new DefaultMustacheVisitor(this) {
@Override
public void partial(TemplateContext tc, String variable) {
public void partial(TemplateContext tc, String variable, String indent) {
TemplateContext partialTC = new TemplateContext("{{", "}}", tc.file(), tc.line(), tc.startOfLine());
list.add(new PartialCode(partialTC, df, variable) {
@Override
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/test/java/com/github/mustachejava/SpecTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ Function lambda() {
StringWriter writer = new StringWriter();
compile.execute(writer, new Object[]{new ObjectMapper().readValue(data.toString(), Map.class), functionMap.get(file)});
String expected = test.get("expected").asText();
if (writer.toString().replaceAll("\\s+", "").equals(expected.replaceAll("\\s+", ""))) {
if (transformOutput(writer.toString()).equals(transformOutput(expected))) {
System.out.print(": success");
if (writer.toString().equals(expected)) {
System.out.println("!");
Expand All @@ -174,6 +174,10 @@ Function lambda() {
assertFalse(fail > 0);
}

protected String transformOutput(String output) {
return output.replaceAll("\\s+", "");
}

protected DefaultMustacheFactory createMustacheFactory(final JsonNode test) {
return new DefaultMustacheFactory("/spec/specs") {
@Override
Expand Down