Update Liquid Implementation

This commit is contained in:
Grahame Grieve 2020-05-23 08:15:37 +10:00
parent 50ea83322f
commit 3f4d574571
7 changed files with 338 additions and 97 deletions

View File

@ -61,7 +61,7 @@ public class LiquidRenderer extends ResourceRenderer {
XhtmlNode xn; XhtmlNode xn;
try { try {
LiquidDocument doc = engine.parse(liquidTemplate, "template"); LiquidDocument doc = engine.parse(liquidTemplate, "template");
String html = "rto do"; // engine.evaluate(doc, r, rcontext); String html = engine.evaluate(doc, r.getBase(), rcontext);
xn = new XhtmlParser().parseFragment(html); xn = new XhtmlParser().parseFragment(html);
if (!x.getName().equals("div")) if (!x.getName().equals("div"))
throw new FHIRException("Error in template: Root element is not 'div'"); throw new FHIRException("Error in template: Root element is not 'div'");

View File

@ -42,6 +42,7 @@ public class BaseWrappers {
public List<ResourceWrapper> getContained(); public List<ResourceWrapper> getContained();
public String getId(); public String getId();
public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException; public XhtmlNode getNarrative() throws FHIRFormatError, IOException, FHIRException;
public Base getBase();
public String getName(); public String getName();
public void describe(XhtmlNode x) throws UnsupportedEncodingException, IOException; public void describe(XhtmlNode x) throws UnsupportedEncodingException, IOException;
public void injectNarrative(XhtmlNode x, NarrativeStatus status) throws IOException; public void injectNarrative(XhtmlNode x, NarrativeStatus status) throws IOException;

View File

@ -307,6 +307,11 @@ public class DOMWrappers {
public StructureDefinition getDefinition() { public StructureDefinition getDefinition() {
return definition; return definition;
} }
@Override
public Base getBase() {
throw new Error("Not Implemented yet");
}
} }
} }

View File

@ -214,6 +214,11 @@ public class DirectWrappers {
public StructureDefinition getDefinition() { public StructureDefinition getDefinition() {
return context.getWorker().fetchTypeDefinition(wrapped.fhirType()); return context.getWorker().fetchTypeDefinition(wrapped.fhirType());
} }
@Override
public Base getBase() {
return wrapped;
}
} }
} }

View File

@ -214,6 +214,11 @@ public class ElementWrappers {
public StructureDefinition getDefinition() { public StructureDefinition getDefinition() {
return definition; return definition;
} }
@Override
public Base getBase() {
return wrapped;
}
} }
public static class PropertyWrapperMetaElement extends RendererWrapperImpl implements PropertyWrapper { public static class PropertyWrapperMetaElement extends RendererWrapperImpl implements PropertyWrapper {

View File

@ -1,39 +1,39 @@
package org.hl7.fhir.r5.utils; package org.hl7.fhir.r5.utils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/* /*
Copyright (c) 2011+, HL7, Inc. Copyright (c) 2011+, HL7, Inc.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this * Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer. list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, * Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to * Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific endorse or promote products derived from this software without specific
prior written permission. prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. POSSIBILITY OF SUCH DAMAGE.
*/ */
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.exceptions.PathEngineException;
@ -55,10 +55,10 @@ public class LiquidEngine implements IEvaluationContext {
public interface ILiquidEngineIcludeResolver { public interface ILiquidEngineIcludeResolver {
public String fetchInclude(LiquidEngine engine, String name); public String fetchInclude(LiquidEngine engine, String name);
} }
private IEvaluationContext externalHostServices; private IEvaluationContext externalHostServices;
private FHIRPathEngine engine; private FHIRPathEngine engine;
private ILiquidEngineIcludeResolver includeResolver; private ILiquidEngineIcludeResolver includeResolver;
private class LiquidEngineContext { private class LiquidEngineContext {
private Object externalContext; private Object externalContext;
@ -82,7 +82,7 @@ public class LiquidEngine implements IEvaluationContext {
engine = new FHIRPathEngine(context); engine = new FHIRPathEngine(context);
engine.setHostServices(this); engine.setHostServices(this);
} }
public ILiquidEngineIcludeResolver getIncludeResolver() { public ILiquidEngineIcludeResolver getIncludeResolver() {
return includeResolver; return includeResolver;
} }
@ -95,7 +95,7 @@ public class LiquidEngine implements IEvaluationContext {
return new LiquidParser(source).parse(sourceName); return new LiquidParser(source).parse(sourceName);
} }
public String evaluate(LiquidDocument document, Resource resource, Object appContext) throws FHIRException { public String evaluate(LiquidDocument document, Base resource, Object appContext) throws FHIRException {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
LiquidEngineContext ctxt = new LiquidEngineContext(appContext); LiquidEngineContext ctxt = new LiquidEngineContext(appContext);
for (LiquidNode n : document.body) { for (LiquidNode n : document.body) {
@ -105,9 +105,10 @@ public class LiquidEngine implements IEvaluationContext {
} }
private abstract class LiquidNode { private abstract class LiquidNode {
protected void closeUp() {} protected void closeUp() {
}
public abstract void evaluate(StringBuilder b, Resource resource, LiquidEngineContext ctxt) throws FHIRException; public abstract void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException;
} }
private class LiquidConstant extends LiquidNode { private class LiquidConstant extends LiquidNode {
@ -125,7 +126,7 @@ public class LiquidEngine implements IEvaluationContext {
} }
@Override @Override
public void evaluate(StringBuilder b, Resource resource, LiquidEngineContext ctxt) { public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) {
b.append(constant); b.append(constant);
} }
} }
@ -135,48 +136,191 @@ public class LiquidEngine implements IEvaluationContext {
private ExpressionNode compiled; private ExpressionNode compiled;
@Override @Override
public void evaluate(StringBuilder b, Resource resource, LiquidEngineContext ctxt) throws FHIRException { public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
if (compiled == null) if (compiled == null)
compiled = engine.parse(statement); compiled = engine.parse(statement);
b.append(engine.evaluateToString(ctxt, resource, resource, resource, compiled)); b.append(engine.evaluateToString(ctxt, resource, resource, resource, compiled));
} }
} }
private class LiquidElsIf extends LiquidNode {
private String condition;
private ExpressionNode compiled;
private List<LiquidNode> body = new ArrayList<>();
@Override
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
for (LiquidNode n : body) {
n.evaluate(b, resource, ctxt);
}
}
}
private class LiquidIf extends LiquidNode { private class LiquidIf extends LiquidNode {
private String condition; private String condition;
private ExpressionNode compiled; private ExpressionNode compiled;
private List<LiquidNode> thenBody = new ArrayList<>(); private List<LiquidNode> thenBody = new ArrayList<>();
private List<LiquidElsIf> elseIf = new ArrayList<>();
private List<LiquidNode> elseBody = new ArrayList<>(); private List<LiquidNode> elseBody = new ArrayList<>();
@Override @Override
public void evaluate(StringBuilder b, Resource resource, LiquidEngineContext ctxt) throws FHIRException { public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
if (compiled == null) if (compiled == null)
compiled = engine.parse(condition); compiled = engine.parse(condition);
boolean ok = engine.evaluateToBoolean(ctxt, resource, resource, resource, compiled); boolean ok = engine.evaluateToBoolean(ctxt, resource, resource, resource, compiled);
List<LiquidNode> list = ok ? thenBody : elseBody; List<LiquidNode> list = null;
if (ok) {
list = thenBody;
} else {
list = elseBody;
for (LiquidElsIf i : elseIf) {
if (i.compiled == null)
i.compiled = engine.parse(i.condition);
ok = engine.evaluateToBoolean(ctxt, resource, resource, resource, i.compiled);
if (ok) {
list = i.body;
break;
}
}
}
for (LiquidNode n : list) { for (LiquidNode n : list) {
n.evaluate(b, resource, ctxt); n.evaluate(b, resource, ctxt);
} }
} }
} }
private class LiquidLoop extends LiquidNode { private class LiquidContinueExecuted extends FHIRException {
private static final long serialVersionUID = 4748737094188943721L;
}
private class LiquidContinue extends LiquidNode {
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
throw new LiquidContinueExecuted();
}
}
private class LiquidBreakExecuted extends FHIRException {
private static final long serialVersionUID = 6328496371172871082L;
}
private class LiquidBreak extends LiquidNode {
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
throw new LiquidBreakExecuted();
}
}
private class LiquidCycle extends LiquidNode {
private List<String> list = new ArrayList<>();
private int cursor = 0;
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
b.append(list.get(cursor));
cursor++;
if (cursor == list.size()) {
cursor = 0;
}
}
}
private class LiquidFor extends LiquidNode {
private String varName; private String varName;
private String condition; private String condition;
private ExpressionNode compiled; private ExpressionNode compiled;
private boolean reversed = false;
private int limit = -1;
private int offset = -1;
private List<LiquidNode> body = new ArrayList<>(); private List<LiquidNode> body = new ArrayList<>();
private List<LiquidNode> elseBody = new ArrayList<>();
@Override @Override
public void evaluate(StringBuilder b, Resource resource, LiquidEngineContext ctxt) throws FHIRException { public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
if (compiled == null) if (compiled == null) {
compiled = engine.parse(condition); ExpressionNodeWithOffset po = engine.parsePartial(condition, 0);
List<Base> list = engine.evaluate(ctxt, resource, resource, resource, compiled); compiled = po.getNode();
LiquidEngineContext lctxt = new LiquidEngineContext(ctxt); if (po.getOffset() < condition.length()) {
for (Base o : list) { parseModifiers(condition.substring(po.getOffset()));
lctxt.vars.put(varName, o);
for (LiquidNode n : body) {
n.evaluate(b, resource, lctxt);
} }
} }
List<Base> list = engine.evaluate(ctxt, resource, resource, resource, compiled);
LiquidEngineContext lctxt = new LiquidEngineContext(ctxt);
if (list.isEmpty()) {
for (LiquidNode n : elseBody) {
n.evaluate(b, resource, lctxt);
}
} else {
if (reversed) {
Collections.reverse(list);
}
int i = 0;
for (Base o : list) {
if (offset >= 0 && i < offset) {
i++;
continue;
}
if (limit >= 0 && i == limit) {
break;
}
lctxt.vars.put(varName, o);
boolean wantBreak = false;
for (LiquidNode n : body) {
try {
n.evaluate(b, resource, lctxt);
} catch (LiquidContinueExecuted e) {
break;
} catch (LiquidBreakExecuted e) {
wantBreak = true;
break;
}
}
if (wantBreak) {
break;
}
i++;
}
}
}
private void parseModifiers(String cnt) {
String src = cnt;
while (!Utilities.noString(cnt)) {
if (cnt.startsWith("reversed")) {
reversed = true;
cnt = cnt.substring(8);
} else if (cnt.startsWith("limit")) {
cnt = cnt.substring(5).trim();
if (!cnt.startsWith(":")) {
throw new FHIRException("Exception evaluating "+src+": limit is not followed by ':'");
}
cnt = cnt.substring(1).trim();
int i = 0;
while (i < cnt.length() && Character.isDigit(cnt.charAt(i))) {
i++;
}
if (i == 0) {
throw new FHIRException("Exception evaluating "+src+": limit is not followed by a number");
}
limit = Integer.parseInt(cnt.substring(0, i));
cnt = cnt.substring(i);
} else if (cnt.startsWith("offset")) {
cnt = cnt.substring(6).trim();
if (!cnt.startsWith(":")) {
throw new FHIRException("Exception evaluating "+src+": limit is not followed by ':'");
}
cnt = cnt.substring(1).trim();
int i = 0;
while (i < cnt.length() && Character.isDigit(cnt.charAt(i))) {
i++;
}
if (i == 0) {
throw new FHIRException("Exception evaluating "+src+": limit is not followed by a number");
}
offset = Integer.parseInt(cnt.substring(0, i));
cnt = cnt.substring(i);
} else {
throw new FHIRException("Exception evaluating "+src+": unexpected content at "+cnt);
}
}
} }
} }
@ -185,11 +329,11 @@ public class LiquidEngine implements IEvaluationContext {
private Map<String, ExpressionNode> params = new HashMap<>(); private Map<String, ExpressionNode> params = new HashMap<>();
@Override @Override
public void evaluate(StringBuilder b, Resource resource, LiquidEngineContext ctxt) throws FHIRException { public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
String src = includeResolver.fetchInclude(LiquidEngine.this, page); String src = includeResolver.fetchInclude(LiquidEngine.this, page);
LiquidParser parser = new LiquidParser(src); LiquidParser parser = new LiquidParser(src);
LiquidDocument doc = parser.parse(page); LiquidDocument doc = parser.parse(page);
LiquidEngineContext nctxt = new LiquidEngineContext(ctxt.externalContext); LiquidEngineContext nctxt = new LiquidEngineContext(ctxt.externalContext);
Tuple incl = new Tuple(); Tuple incl = new Tuple();
nctxt.vars.put("include", incl); nctxt.vars.put("include", incl);
for (String s : params.keySet()) { for (String s : params.keySet()) {
@ -201,7 +345,7 @@ public class LiquidEngine implements IEvaluationContext {
} }
} }
public static class LiquidDocument { public static class LiquidDocument {
private List<LiquidNode> body = new ArrayList<>(); private List<LiquidNode> body = new ArrayList<>();
} }
@ -225,64 +369,124 @@ public class LiquidEngine implements IEvaluationContext {
} }
private char next2() { private char next2() {
if (cursor >= source.length()-1) if (cursor >= source.length() - 1)
return 0; return 0;
else else
return source.charAt(cursor+1); return source.charAt(cursor + 1);
} }
private char grab() { private char grab() {
cursor++; cursor++;
return source.charAt(cursor-1); return source.charAt(cursor - 1);
} }
public LiquidDocument parse(String name) throws FHIRException { public LiquidDocument parse(String name) throws FHIRException {
this.name = name; this.name = name;
LiquidDocument doc = new LiquidDocument(); LiquidDocument doc = new LiquidDocument();
parseList(doc.body, new String[0]); parseList(doc.body, false, new String[0]);
return doc; return doc;
} }
private String parseList(List<LiquidNode> list, String[] terminators) throws FHIRException { public LiquidCycle parseCycle(String cnt) {
LiquidCycle res = new LiquidCycle();
cnt = "," + cnt.substring(5).trim();
while (!Utilities.noString(cnt)) {
if (!cnt.startsWith(",")) {
throw new FHIRException("Script " + name + ": Script " + name + ": Found " + cnt.charAt(0) + " expecting ',' parsing cycle");
}
cnt = cnt.substring(1).trim();
if (!cnt.startsWith("\"")) {
throw new FHIRException("Script " + name + ": Script " + name + ": Found " + cnt.charAt(0) + " expecting '\"' parsing cycle");
}
cnt = cnt.substring(1);
int i = 0;
while (i < cnt.length() && cnt.charAt(i) != '"') {
i++;
}
if (i == cnt.length()) {
throw new FHIRException("Script " + name + ": Script " + name + ": Found unterminated string parsing cycle");
}
res.list.add(cnt.substring(0, i));
cnt = cnt.substring(i + 1).trim();
}
return res;
}
private String parseList(List<LiquidNode> list, boolean inLoop, String[] terminators) throws FHIRException {
String close = null; String close = null;
while (cursor < source.length()) { while (cursor < source.length()) {
if (next1() == '{' && (next2() == '%' || next2() == '{' )) { if (next1() == '{' && (next2() == '%' || next2() == '{')) {
if (next2() == '%') { if (next2() == '%') {
String cnt = parseTag('%'); String cnt = parseTag('%');
if (Utilities.existsInList(cnt, terminators)) { if (isTerminator(cnt, terminators)) {
close = cnt; close = cnt;
break; break;
} else if (cnt.startsWith("if ")) } else if (cnt.startsWith("if "))
list.add(parseIf(cnt)); list.add(parseIf(cnt, inLoop));
else if (cnt.startsWith("loop ")) else if (cnt.startsWith("loop ")) // loop is deprecated, but still
// supported
list.add(parseLoop(cnt.substring(4).trim())); list.add(parseLoop(cnt.substring(4).trim()));
else if (cnt.startsWith("for "))
list.add(parseFor(cnt.substring(3).trim()));
else if (inLoop && cnt.equals("continue"))
list.add(new LiquidContinue());
else if (inLoop && cnt.equals("break"))
list.add(new LiquidBreak());
else if (inLoop && cnt.startsWith("cycle "))
list.add(parseCycle(cnt));
else if (cnt.startsWith("include ")) else if (cnt.startsWith("include "))
list.add(parseInclude(cnt.substring(7).trim())); list.add(parseInclude(cnt.substring(7).trim()));
else else
throw new FHIRException("Script "+name+": Script "+name+": Unknown flow control statement "+cnt); throw new FHIRException("Script " + name + ": Script " + name + ": Unknown flow control statement " + cnt);
} else { // next2() == '{' } else { // next2() == '{'
list.add(parseStatement()); list.add(parseStatement());
} }
} else { } else {
if (list.size() == 0 || !(list.get(list.size()-1) instanceof LiquidConstant)) if (list.size() == 0 || !(list.get(list.size() - 1) instanceof LiquidConstant))
list.add(new LiquidConstant()); list.add(new LiquidConstant());
((LiquidConstant) list.get(list.size()-1)).addChar(grab()); ((LiquidConstant) list.get(list.size() - 1)).addChar(grab());
} }
} }
for (LiquidNode n : list) for (LiquidNode n : list)
n.closeUp(); n.closeUp();
if (terminators.length > 0) if (terminators.length > 0)
if (!Utilities.existsInList(close, terminators)) if (!isTerminator(close, terminators))
throw new FHIRException("Script "+name+": Script "+name+": Found end of script looking for "+terminators); throw new FHIRException("Script " + name + ": Script " + name + ": Found end of script looking for " + terminators);
return close; return close;
} }
private LiquidNode parseIf(String cnt) throws FHIRException { private boolean isTerminator(String cnt, String[] terminators) {
if (Utilities.noString(cnt)) {
return false;
}
for (String t : terminators) {
if (t.endsWith(" ")) {
if (cnt.startsWith(t)) {
return true;
}
} else {
if (cnt.equals(t)) {
return true;
}
}
}
return false;
}
private LiquidNode parseIf(String cnt, boolean inLoop) throws FHIRException {
LiquidIf res = new LiquidIf(); LiquidIf res = new LiquidIf();
res.condition = cnt.substring(3).trim(); res.condition = cnt.substring(3).trim();
String term = parseList(res.thenBody, new String[] { "else", "endif"} ); String term = parseList(res.thenBody, inLoop, new String[] { "else", "elsif ", "endif" });
if ("else".equals(term)) while (term.startsWith("elsif ")) {
term = parseList(res.elseBody, new String[] { "endif"} ); LiquidElsIf elsIf = new LiquidElsIf();
res.elseIf.add(elsIf);
elsIf.condition = term.substring(5).trim();
term = parseList(elsIf.body, inLoop, new String[] { "elsif ", "else", "endif" });
}
if ("else".equals(term)) {
term = parseList(res.elseBody, inLoop, new String[] { "endif" });
}
return res; return res;
} }
@ -291,7 +495,7 @@ public class LiquidEngine implements IEvaluationContext {
while (i < cnt.length() && !Character.isWhitespace(cnt.charAt(i))) while (i < cnt.length() && !Character.isWhitespace(cnt.charAt(i)))
i++; i++;
if (i == cnt.length() || i == 0) if (i == cnt.length() || i == 0)
throw new FHIRException("Script "+name+": Error reading include: "+cnt); throw new FHIRException("Script " + name + ": Error reading include: " + cnt);
LiquidInclude res = new LiquidInclude(); LiquidInclude res = new LiquidInclude();
res.page = cnt.substring(0, i); res.page = cnt.substring(0, i);
while (i < cnt.length() && Character.isWhitespace(cnt.charAt(i))) while (i < cnt.length() && Character.isWhitespace(cnt.charAt(i)))
@ -300,27 +504,26 @@ public class LiquidEngine implements IEvaluationContext {
int j = i; int j = i;
while (i < cnt.length() && cnt.charAt(i) != '=') while (i < cnt.length() && cnt.charAt(i) != '=')
i++; i++;
if (i >= cnt.length() || j == i) if (i >= cnt.length() || j == i)
throw new FHIRException("Script "+name+": Error reading include: "+cnt); throw new FHIRException("Script " + name + ": Error reading include: " + cnt);
String n = cnt.substring(j, i); String n = cnt.substring(j, i);
if (res.params.containsKey(n)) if (res.params.containsKey(n))
throw new FHIRException("Script "+name+": Error reading include: "+cnt); throw new FHIRException("Script " + name + ": Error reading include: " + cnt);
i++;
ExpressionNodeWithOffset t = engine.parsePartial(cnt, i);
i = t.getOffset();
res.params.put(n, t.getNode());
while (i < cnt.length() && Character.isWhitespace(cnt.charAt(i)))
i++; i++;
ExpressionNodeWithOffset t = engine.parsePartial(cnt, i);
i = t.getOffset();
res.params.put(n, t.getNode());
while (i < cnt.length() && Character.isWhitespace(cnt.charAt(i)))
i++;
} }
return res; return res;
} }
private LiquidNode parseLoop(String cnt) throws FHIRException { private LiquidNode parseLoop(String cnt) throws FHIRException {
int i = 0; int i = 0;
while (!Character.isWhitespace(cnt.charAt(i))) while (!Character.isWhitespace(cnt.charAt(i)))
i++; i++;
LiquidLoop res = new LiquidLoop(); LiquidFor res = new LiquidFor();
res.varName = cnt.substring(0, i); res.varName = cnt.substring(0, i);
while (Character.isWhitespace(cnt.charAt(i))) while (Character.isWhitespace(cnt.charAt(i)))
i++; i++;
@ -328,36 +531,58 @@ public class LiquidEngine implements IEvaluationContext {
while (!Character.isWhitespace(cnt.charAt(i))) while (!Character.isWhitespace(cnt.charAt(i)))
i++; i++;
if (!"in".equals(cnt.substring(j, i))) if (!"in".equals(cnt.substring(j, i)))
throw new FHIRException("Script "+name+": Script "+name+": Error reading loop: "+cnt); throw new FHIRException("Script " + name + ": Script " + name + ": Error reading loop: " + cnt);
res.condition = cnt.substring(i).trim(); res.condition = cnt.substring(i).trim();
parseList(res.body, new String[] { "endloop"} ); parseList(res.body, false, new String[] { "endloop" });
return res; return res;
} }
private LiquidNode parseFor(String cnt) throws FHIRException {
int i = 0;
while (!Character.isWhitespace(cnt.charAt(i)))
i++;
LiquidFor res = new LiquidFor();
res.varName = cnt.substring(0, i);
while (Character.isWhitespace(cnt.charAt(i)))
i++;
int j = i;
while (!Character.isWhitespace(cnt.charAt(i)))
i++;
if (!"in".equals(cnt.substring(j, i)))
throw new FHIRException("Script " + name + ": Script " + name + ": Error reading loop: " + cnt);
res.condition = cnt.substring(i).trim();
String term = parseList(res.body, true, new String[] { "endfor", "else" });
if ("else".equals(term)) {
parseList(res.elseBody, false, new String[] { "endfor" });
}
return res;
}
private String parseTag(char ch) throws FHIRException { private String parseTag(char ch) throws FHIRException {
grab(); grab();
grab(); grab();
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
while (cursor < source.length() && !(next1() == '%' && next2() == '}')) { while (cursor < source.length() && !(next1() == '%' && next2() == '}')) {
b.append(grab()); b.append(grab());
} }
if (!(next1() == '%' && next2() == '}')) if (!(next1() == '%' && next2() == '}'))
throw new FHIRException("Script "+name+": Unterminated Liquid statement {% "+b.toString()); throw new FHIRException("Script " + name + ": Unterminated Liquid statement {% " + b.toString());
grab(); grab();
grab(); grab();
return b.toString().trim(); return b.toString().trim();
} }
private LiquidStatement parseStatement() throws FHIRException { private LiquidStatement parseStatement() throws FHIRException {
grab(); grab();
grab(); grab();
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
while (cursor < source.length() && !(next1() == '}' && next2() == '}')) { while (cursor < source.length() && !(next1() == '}' && next2() == '}')) {
b.append(grab()); b.append(grab());
} }
if (!(next1() == '}' && next2() == '}')) if (!(next1() == '}' && next2() == '}'))
throw new FHIRException("Script "+name+": Unterminated Liquid statement {{ "+b.toString()); throw new FHIRException("Script " + name + ": Unterminated Liquid statement {{ " + b.toString());
grab(); grab();
grab(); grab();
LiquidStatement res = new LiquidStatement(); LiquidStatement res = new LiquidStatement();
res.statement = b.toString().trim(); res.statement = b.toString().trim();
@ -471,7 +696,7 @@ public class LiquidEngine implements IEvaluationContext {
if (!cnt.equals(node.getAttributes().get(an))) { if (!cnt.equals(node.getAttributes().get(an))) {
node.getAttributes().put(an, cnt); node.getAttributes().put(an, cnt);
replaced = true; replaced = true;
} }
} }
} }
return replaced; return replaced;

View File

@ -57,7 +57,7 @@ public class LiquidEngineTests implements ILiquidEngineIcludeResolver {
this.test = test; this.test = test;
LiquidDocument doc = engine.parse(test.get("template").getAsString(), "test-script"); LiquidDocument doc = engine.parse(test.get("template").getAsString(), "test-script");
String output = engine.evaluate(doc, loadResource(), null); String output = engine.evaluate(doc, loadResource(), null);
Assertions.assertTrue(test.get("output").getAsString().equals(output)); Assertions.assertEquals(test.get("output").getAsString(), output);
} }
@Override @Override