Fix identation with section segments

This commit is contained in:
Adam Gent
2023-11-27 13:27:58 -05:00
parent 57f182c157
commit 388406eab5
4 changed files with 181 additions and 37 deletions
@@ -470,7 +470,15 @@ public class Mustache {
return segs; return segs;
} }
static Template.Segment[] indentSegs(Template.Segment[] _segs, String indent) { /**
* Indents segments by indent.
* @param _segs segments to be cloned if indentation is needed
* @param indent the space to use for indent.
* @param _first whether to append an indent on the first segment
* @param _last whether to append an indent on the last segment last empty newline (no character after \n).
* @return cloned segments if changed
*/
static Template.Segment[] indentSegs(Template.Segment[] _segs, String indent, boolean _first, boolean _last) {
// unlike trim this method clones the segments if they have changed // unlike trim this method clones the segments if they have changed
// so the return value must be handled // so the return value must be handled
// a simple identity check on the return can be used to determine // a simple identity check on the return can be used to determine
@@ -486,38 +494,104 @@ public class Mustache {
Template.Segment seg = _segs[i]; Template.Segment seg = _segs[i];
Template.Segment pseg = (i > 0) ? _segs[i-1] : null; Template.Segment pseg = (i > 0) ? _segs[i-1] : null;
Template.Segment nseg = (i < length - 1) ? _segs[i+1] : null; Template.Segment nseg = (i < length - 1) ? _segs[i+1] : null;
/*
* If we are the first segment then we rely on the indentation Template.Segment copy;
* already present before the partial tag. if (seg instanceof BlockSegment) {
* [ WS ]{{> partial }} BlockSegment bs = (BlockSegment) seg;
* boolean first;
* That is partial tags do not have the trailing blank boolean last;
* removed. if (pseg == null) {
* // We are the first segment so we inherit
* This avoids needlessley creating StringSegment tags. // outer first
*/ first = _first;
boolean first = pseg == null ? false : seg.isStandalone() || pseg.isStandalone(); }
boolean last; else if (bs.isStandaloneStart()) {
if (nseg instanceof BlockSegment){ first = true;
BlockSegment bs = (BlockSegment) nseg; }
last = bs.isStandaloneStart(); else {
first = false;
}
if (bs.isStandalone()) {
// the closing tag owns the last new line
// in the section so we do not indent
last = false;
}
else if (nseg == null) {
// We are the last segment so we inherit the
// outer last
last = _last;
}
else {
last = true;
}
copy = bs.indent(indent, first, last);
} }
else if (nseg == null || nseg.isStandalone()) { else if (seg instanceof StringSegment) {
last = false; boolean first;
boolean last;
if (pseg == null) {
first = _first;
}
else if (pseg.isStandalone()) {
first = true;
}
else {
first = false;
}
if (nseg == null) {
last = _last;
}
else if (nseg instanceof BlockSegment) {
BlockSegment bs = (BlockSegment) nseg;
last = ! bs.isStandaloneStart();
}
else if (nseg.isStandalone()) {
last = false;
}
else {
last = true;
}
copy = seg.indent(indent, first, last);
}
else if (seg instanceof IncludedTemplateSegment) {
/*
* If we are standalone then we rely on the indentation
* already present before the partial tag.
* [ WS ]{{> partial }}[\n]
*
* That is partial tags do not have the trailing blank
* removed during the trim process.
*
* This avoids needlessley creating StringSegment tags.
*/
if (seg.isStandalone()) {
boolean last;
if (nseg == null) {
last = _last;
}
else if (nseg.isStandalone()) {
last = false;
}
else {
last = true;
}
// Again first = false here because we
//already have the indentation set on a previous segment
copy = seg.indent(indent, false, last);
}
else {
copy = seg;
}
} }
else { else {
last = true; copy = seg.indent(indent, _first, _last);
} }
/*
* Recursively indent
*/
Template.Segment copy = seg.indent(indent, first, last);
if (copy != seg) { if (copy != seg) {
changed = true; changed = true;
} }
copySegs[i] = copy; copySegs[i] = copy;
} }
if (changed) { if (changed) {
return copySegs; return copySegs;
} }
return _segs; return _segs;
@@ -1162,10 +1236,12 @@ public class Mustache {
} }
} }
@Override protected SectionSegment indent (String indent, boolean first, boolean last) { @Override protected SectionSegment indent (String indent, boolean first, boolean last) {
if (indent.equals("")) { // If the end tag is standalone we do NOT add the indent on the end
return this; // (e.g. the newline following the end tag).
} // This is because the block always owns the newlines within the block but
Template.Segment[] segs = indentSegs(_segs, indent); // it only owns the last newline following the end tag if and only if
// the closing tag is standalone.
Template.Segment[] segs = indentSegs(_segs, indent, first, last);
if (segs == _segs) { if (segs == _segs) {
return this; return this;
} }
@@ -1222,10 +1298,7 @@ public class Mustache {
} }
@Override @Override
protected InvertedSegment indent (String indent, boolean first, boolean last) { protected InvertedSegment indent (String indent, boolean first, boolean last) {
if (indent.equals("")) { Template.Segment[] segs = indentSegs(_segs, indent, first, last);
return this;
}
Template.Segment[] segs = indentSegs(_segs, indent);
if (segs == _segs) { if (segs == _segs) {
return this; return this;
} }
@@ -176,7 +176,7 @@ public class Template {
if (indent.equals("")) { if (indent.equals("")) {
return this; return this;
} }
Segment[] copySegs = Mustache.indentSegs(_segs, indent); Segment[] copySegs = Mustache.indentSegs(_segs, indent, false,false);
if (copySegs == _segs) { if (copySegs == _segs) {
return this; return this;
} }
@@ -31,7 +31,7 @@ public abstract class SpecTest {
public void test () throws Exception { public void test () throws Exception {
//System.out.println("Testing: " + name); //System.out.println("Testing: " + name);
SpecAwareTemplateLoader loader = new SpecAwareTemplateLoader(spec); SpecAwareTemplateLoader loader = new SpecAwareTemplateLoader(spec);
Mustache.Compiler compiler = Mustache.compiler().defaultValue("").withLoader(loader); Mustache.Compiler compiler = Mustache.compiler().emptyStringIsFalse(true).defaultValue("").withLoader(loader);
String tmpl = spec.getTemplate(); String tmpl = spec.getTemplate();
String desc = String.format("Template: '''%s'''\nData: '%s'\n", String desc = String.format("Template: '''%s'''\nData: '%s'\n",
uncrlf(tmpl), uncrlf(spec.getData().toString())); uncrlf(tmpl), uncrlf(spec.getData().toString()));
+74 -3
View File
@@ -24,8 +24,8 @@ tests:
nest: "2\n{{{content}}}\n2\n" nest: "2\n{{{content}}}\n2\n"
expected: "|\n 1\n 2\n <\n->\n 2\n 1\n|\n" expected: "|\n 1\n 2\n <\n->\n 2\n 1\n|\n"
- name: Standalone Indentation - name: Partial Section Indentation End Content
desc: Each line of the partial should be indented before rendering. desc: Closing end sections that have content on same line should be indented
data: { content: "<\n->" } data: { content: "<\n->" }
template: | template: |
\ \
@@ -34,12 +34,83 @@ tests:
partials: partials:
partial: | partial: |
| |
{{{content}}} {{#content}}
{{{.}}}
{{/content}}-
| |
expected: | expected: |
\ \
| |
< <
-> ->
-
| |
/ /
- name: Partial Section Indentation Inside Start Content
desc: Content that is not white space on same line as section start tag inside should be indented
data: { content: "<\n->" }
template: |
\
{{>partial}}
/
partials:
partial: |
|
{{#content}}-
{{{.}}}
{{/content}}
|
expected: |
\
|
-
<
->
|
/
- name: Partial Section Indentation Start Content
desc: Content that is not white space on same line as section start tags should be indented
data: { content: "<\n->" }
template: |
\
{{>partial}}
/
partials:
partial: |
|
-{{#content}}
{{{.}}}
{{/content}}
|
expected: |
\
|
-
<
->
|
/
- name: Partial Indentation With Empty Sections
desc: Empty standlone sections should not have indentation before or after
data: { content: "" }
template: |
\
{{>partial}}
/
partials:
partial: |
|
{{#content}}
{{{.}}}
{{/content}}
|
expected: |
\
|
|
/
# The extra newline on the end is required