liquid fixes to FHIRPath

This commit is contained in:
Grahame Grieve 2022-12-20 23:09:44 +13:00
parent e2dff7d118
commit 2f0a45046e
3 changed files with 87 additions and 4 deletions

View File

@ -76,6 +76,7 @@ public class FHIRLexer {
private SourceLocation currentStartLocation; private SourceLocation currentStartLocation;
private int id; private int id;
private String name; private String name;
private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the host
public FHIRLexer(String source, String name) throws FHIRLexerException { public FHIRLexer(String source, String name) throws FHIRLexerException {
this.source = source == null ? "" : source; this.source = source == null ? "" : source;
@ -272,6 +273,12 @@ public class FHIRLexer {
throw error("Unterminated string"); throw error("Unterminated string");
cursor++; cursor++;
current = "`"+source.substring(currentStart+1, cursor-1)+"`"; current = "`"+source.substring(currentStart+1, cursor-1)+"`";
} else if (ch == '|' && liquidMode) {
cursor++;
ch = source.charAt(cursor);
if (ch == '|')
cursor++;
current = source.substring(currentStart, cursor);
} else if (ch == '@'){ } else if (ch == '@'){
int start = cursor; int start = cursor;
cursor++; cursor++;
@ -520,5 +527,11 @@ public class FHIRLexer {
public String getSource() { public String getSource() {
return source; return source;
} }
public boolean isLiquidMode() {
return liquidMode;
}
public void setLiquidMode(boolean liquidMode) {
this.liquidMode = liquidMode;
}
} }

View File

@ -262,6 +262,7 @@ public class FHIRPathEngine {
private String location; // for error messages private String location; // for error messages
private boolean allowPolymorphicNames; private boolean allowPolymorphicNames;
private boolean doImplicitStringConversion; private boolean doImplicitStringConversion;
private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the host
// if the fhir path expressions are allowed to use constants beyond those defined in the specification // if the fhir path expressions are allowed to use constants beyond those defined in the specification
// the application can implement them by providing a constant resolver // the application can implement them by providing a constant resolver
@ -6034,5 +6035,13 @@ public class FHIRPathEngine {
this.allowPolymorphicNames = allowPolymorphicNames; this.allowPolymorphicNames = allowPolymorphicNames;
} }
public boolean isLiquidMode() {
return liquidMode;
}
public void setLiquidMode(boolean liquidMode) {
this.liquidMode = liquidMode;
}
} }

View File

@ -93,6 +93,7 @@ public class LiquidEngine implements IEvaluationContext {
this.externalHostServices = hostServices; this.externalHostServices = hostServices;
engine = new FHIRPathEngine(context); engine = new FHIRPathEngine(context);
engine.setHostServices(this); engine.setHostServices(this);
engine.setLiquidMode(true);
} }
public ILiquidEngineIncludeResolver getIncludeResolver() { public ILiquidEngineIncludeResolver getIncludeResolver() {
@ -152,22 +153,82 @@ public class LiquidEngine implements IEvaluationContext {
} }
} }
private enum LiquidFilter {
PREPEND;
public static LiquidFilter fromCode(String code) {
if ("prepend".equals(code)) {
return PREPEND;
}
return null;
}
}
private class LiquidExpressionNode {
private LiquidFilter filter; // null at root
private ExpressionNode expression; // null for some filters
public LiquidExpressionNode(LiquidFilter filter, ExpressionNode expression) {
super();
this.filter = filter;
this.expression = expression;
}
}
private class LiquidStatement extends LiquidNode { private class LiquidStatement extends LiquidNode {
private String statement; private String statement;
private ExpressionNode compiled; private List<LiquidExpressionNode> compiled = new ArrayList<>();
@Override @Override
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException { public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
if (compiled == null) if (compiled.size() == 0) {
compiled = engine.parse(statement); FHIRLexer lexer = new FHIRLexer(statement, "liquid statement");
List<Base> items = engine.evaluate(ctxt, resource, resource, resource, compiled); lexer.setLiquidMode(true);
compiled.add(new LiquidExpressionNode(null, engine.parse(lexer)));
while (!lexer.done()) {
if (lexer.getCurrent().equals("||")) {
lexer.next();
String f = lexer.getCurrent();
LiquidFilter filter = LiquidFilter.fromCode(f);
if (filter == null) {
lexer.error("Unknown Liquid filter '"+f+"'");
}
lexer.next();
if (!lexer.done() && lexer.getCurrent().equals(":")) {
lexer.next();
compiled.add(new LiquidExpressionNode(filter, engine.parse(lexer)));
} else {
compiled.add(new LiquidExpressionNode(filter, null));
}
} else {
lexer.error("Unexpected syntax parsing liquid statement");
}
}
}
String t = null;
for (LiquidExpressionNode i : compiled) {
if (i.filter == null) { // first
t = stmtToString(ctxt, engine.evaluate(ctxt, resource, resource, resource, i.expression));
} else switch (i.filter) {
case PREPEND:
t = stmtToString(ctxt, engine.evaluate(ctxt, resource, resource, resource, i.expression)) + t;
break;
}
}
b.append(t);
}
private String stmtToString(LiquidEngineContext ctxt, List<Base> items) {
StringBuilder b = new StringBuilder();
boolean first = true; boolean first = true;
for (Base i : items) { for (Base i : items) {
if (first) first = false; else b.append(", "); if (first) first = false; else b.append(", ");
String s = renderingSupport != null ? renderingSupport.renderForLiquid(ctxt.externalContext, i) : null; String s = renderingSupport != null ? renderingSupport.renderForLiquid(ctxt.externalContext, i) : null;
b.append(s != null ? s : engine.convertToString(i)); b.append(s != null ? s : engine.convertToString(i));
} }
return b.toString();
} }
} }