Fix R4B liquid processor

This commit is contained in:
Grahame Grieve 2023-07-27 20:07:36 +10:00
parent 06c865badf
commit 4677f319cb
4 changed files with 302 additions and 178 deletions

View File

@ -48,24 +48,34 @@ import org.hl7.fhir.utilities.Utilities;
public class FHIRLexer { public class FHIRLexer {
public class FHIRLexerException extends FHIRException { public class FHIRLexerException extends FHIRException {
public FHIRLexerException() { private SourceLocation location;
super();
}
public FHIRLexerException(String message, Throwable cause) { // public FHIRLexerException() {
super(message, cause); // super();
} // }
//
// public FHIRLexerException(String message, Throwable cause) {
// super(message, cause);
// }
//
// public FHIRLexerException(String message) {
// super(message);
// }
//
// public FHIRLexerException(Throwable cause) {
// super(cause);
// }
public FHIRLexerException(String message) { public FHIRLexerException(String message, SourceLocation location) {
super(message); super(message);
this.location = location;
} }
public FHIRLexerException(Throwable cause) { public SourceLocation getLocation() {
super(cause); return location;
} }
} }
private String source; private String source;
private int cursor; private int cursor;
private int currentStart; private int currentStart;
@ -75,8 +85,10 @@ 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 private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the host
// host private SourceLocation commentLocation;
private boolean metadataFormat;
private boolean allowDoubleQuotes;
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;
@ -84,35 +96,44 @@ public class FHIRLexer {
currentLocation = new SourceLocation(1, 1); currentLocation = new SourceLocation(1, 1);
next(); next();
} }
public FHIRLexer(String source, int i) throws FHIRLexerException { public FHIRLexer(String source, int i) throws FHIRLexerException {
this.source = source; this.source = source;
this.cursor = i; this.cursor = i;
currentLocation = new SourceLocation(1, 1); currentLocation = new SourceLocation(1, 1);
next(); next();
} }
public FHIRLexer(String source, int i, boolean allowDoubleQuotes) throws FHIRLexerException {
this.source = source;
this.cursor = i;
this.allowDoubleQuotes = allowDoubleQuotes;
currentLocation = new SourceLocation(1, 1);
next();
}
public FHIRLexer(String source, String name, boolean metadataFormat, boolean allowDoubleQuotes) throws FHIRLexerException {
this.source = source == null ? "" : source;
this.name = name == null ? "??" : name;
this.metadataFormat = metadataFormat;
this.allowDoubleQuotes = allowDoubleQuotes;
currentLocation = new SourceLocation(1, 1);
next();
}
public String getCurrent() { public String getCurrent() {
return current; return current;
} }
public SourceLocation getCurrentLocation() { public SourceLocation getCurrentLocation() {
return currentLocation; return currentLocation;
} }
public boolean isConstant() { public boolean isConstant() {
return !Utilities.noString(current) && ((current.charAt(0) == '\'' || current.charAt(0) == '"') return FHIRPathConstant.isFHIRPathConstant(current);
|| current.charAt(0) == '@' || current.charAt(0) == '%' || current.charAt(0) == '-' || current.charAt(0) == '+'
|| (current.charAt(0) >= '0' && current.charAt(0) <= '9') || current.equals("true") || current.equals("false")
|| current.equals("{}"));
} }
public boolean isFixedName() { public boolean isFixedName() {
return current != null && (current.charAt(0) == '`'); return FHIRPathConstant.isFHIRPathFixedName(current);
} }
public boolean isStringConstant() { public boolean isStringConstant() {
return current.charAt(0) == '\'' || current.charAt(0) == '"' || current.charAt(0) == '`'; return FHIRPathConstant.isFHIRPathStringConstant(current);
} }
public String take() throws FHIRLexerException { public String take() throws FHIRLexerException {
@ -124,7 +145,7 @@ public class FHIRLexer {
public int takeInt() throws FHIRLexerException { public int takeInt() throws FHIRLexerException {
String s = current; String s = current;
if (!Utilities.isInteger(s)) if (!Utilities.isInteger(s))
throw error("Found " + current + " expecting an integer"); throw error("Found "+current+" expecting an integer");
next(); next();
return Integer.parseInt(s); return Integer.parseInt(s);
} }
@ -139,12 +160,10 @@ public class FHIRLexer {
if (current.equals("*") || current.equals("**")) if (current.equals("*") || current.equals("**"))
return true; return true;
if ((current.charAt(0) >= 'A' && current.charAt(0) <= 'Z') if ((current.charAt(0) >= 'A' && current.charAt(0) <= 'Z') || (current.charAt(0) >= 'a' && current.charAt(0) <= 'z')) {
|| (current.charAt(0) >= 'a' && current.charAt(0) <= 'z')) { for (int i = 1; i < current.length(); i++)
for (int i = 1; i < current.length(); i++) if (!( (current.charAt(1) >= 'A' && current.charAt(1) <= 'Z') || (current.charAt(1) >= 'a' && current.charAt(1) <= 'z') ||
if (!((current.charAt(1) >= 'A' && current.charAt(1) <= 'Z') (current.charAt(1) >= '0' && current.charAt(1) <= '9')))
|| (current.charAt(1) >= 'a' && current.charAt(1) <= 'z')
|| (current.charAt(1) >= '0' && current.charAt(1) <= '9')))
return false; return false;
return true; return true;
} }
@ -152,11 +171,11 @@ public class FHIRLexer {
} }
public FHIRLexerException error(String msg) { public FHIRLexerException error(String msg) {
return error(msg, currentLocation.toString()); return error(msg, currentLocation.toString(), currentLocation);
} }
public FHIRLexerException error(String msg, String location) { public FHIRLexerException error(String msg, String location, SourceLocation loc) {
return new FHIRLexerException("Error @" + location + ": " + msg); return new FHIRLexerException("Error @"+location+": "+msg, loc);
} }
public void next() throws FHIRLexerException { public void next() throws FHIRLexerException {
@ -166,34 +185,30 @@ public class FHIRLexer {
currentStartLocation = currentLocation; currentStartLocation = currentLocation;
if (cursor < source.length()) { if (cursor < source.length()) {
char ch = source.charAt(cursor); char ch = source.charAt(cursor);
if (ch == '!' || ch == '>' || ch == '<' || ch == ':' || ch == '-' || ch == '=') { if (ch == '!' || ch == '>' || ch == '<' || ch == ':' || ch == '-' || ch == '=') {
cursor++; cursor++;
if (cursor < source.length() if (cursor < source.length() && (source.charAt(cursor) == '=' || source.charAt(cursor) == '~' || source.charAt(cursor) == '-') || (ch == '-' && source.charAt(cursor) == '>'))
&& (source.charAt(cursor) == '=' || source.charAt(cursor) == '~' || source.charAt(cursor) == '-')
|| (ch == '-' && source.charAt(cursor) == '>'))
cursor++; cursor++;
current = source.substring(currentStart, cursor); current = source.substring(currentStart, cursor);
} else if (ch == '.') { } else if (ch == '.' ) {
cursor++; cursor++;
if (cursor < source.length() && (source.charAt(cursor) == '.')) if (cursor < source.length() && (source.charAt(cursor) == '.'))
cursor++; cursor++;
current = source.substring(currentStart, cursor); current = source.substring(currentStart, cursor);
} else if (ch >= '0' && ch <= '9') { } else if (ch >= '0' && ch <= '9') {
cursor++; cursor++;
boolean dotted = false; boolean dotted = false;
while (cursor < source.length() && ((source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') while (cursor < source.length() && ((source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || (source.charAt(cursor) == '.') && !dotted)) {
|| (source.charAt(cursor) == '.') && !dotted)) {
if (source.charAt(cursor) == '.') if (source.charAt(cursor) == '.')
dotted = true; dotted = true;
cursor++; cursor++;
} }
if (source.charAt(cursor - 1) == '.') if (source.charAt(cursor-1) == '.')
cursor--; cursor--;
current = source.substring(currentStart, cursor); current = source.substring(currentStart, cursor);
} else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') ||
|| (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == '_'))
|| (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == '_'))
cursor++; cursor++;
current = source.substring(currentStart, cursor); current = source.substring(currentStart, cursor);
} else if (ch == '%') { } else if (ch == '%') {
@ -204,19 +219,20 @@ public class FHIRLexer {
cursor++; cursor++;
cursor++; cursor++;
} else } else
while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') ||
|| (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' || source.charAt(cursor) == '-'))
|| (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' cursor++;
|| source.charAt(cursor) == '-'))
cursor++;
current = source.substring(currentStart, cursor); current = source.substring(currentStart, cursor);
} else if (ch == '/') { } else if (ch == '/') {
cursor++; cursor++;
if (cursor < source.length() && (source.charAt(cursor) == '/')) { if (cursor < source.length() && (source.charAt(cursor) == '/')) {
// this is en error - should already have been skipped // we've run into metadata
error("This shouldn't happen?"); cursor++;
cursor++;
current = source.substring(currentStart, cursor);
} else {
current = source.substring(currentStart, cursor);
} }
current = source.substring(currentStart, cursor);
} else if (ch == '$') { } else if (ch == '$') {
cursor++; cursor++;
while (cursor < source.length() && (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z')) while (cursor < source.length() && (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z'))
@ -228,42 +244,42 @@ public class FHIRLexer {
if (ch == '}') if (ch == '}')
cursor++; cursor++;
current = source.substring(currentStart, cursor); current = source.substring(currentStart, cursor);
} else if (ch == '"') { } else if (ch == '"' && allowDoubleQuotes) {
cursor++; cursor++;
boolean escape = false; boolean escape = false;
while (cursor < source.length() && (escape || source.charAt(cursor) != '"')) { while (cursor < source.length() && (escape || source.charAt(cursor) != '"')) {
if (escape) if (escape)
escape = false; escape = false;
else else
escape = (source.charAt(cursor) == '\\'); escape = (source.charAt(cursor) == '\\');
cursor++; cursor++;
} }
if (cursor == source.length()) if (cursor == source.length())
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 == '`') { } else if (ch == '`') {
cursor++; cursor++;
boolean escape = false; boolean escape = false;
while (cursor < source.length() && (escape || source.charAt(cursor) != '`')) { while (cursor < source.length() && (escape || source.charAt(cursor) != '`')) {
if (escape) if (escape)
escape = false; escape = false;
else else
escape = (source.charAt(cursor) == '\\'); escape = (source.charAt(cursor) == '\\');
cursor++; cursor++;
} }
if (cursor == source.length()) if (cursor == source.length())
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 == '\'') { } else if (ch == '\''){
cursor++; cursor++;
char ech = ch; char ech = ch;
boolean escape = false; boolean escape = false;
while (cursor < source.length() && (escape || source.charAt(cursor) != ech)) { while (cursor < source.length() && (escape || source.charAt(cursor) != ech)) {
if (escape) if (escape)
escape = false; escape = false;
else else
escape = (source.charAt(cursor) == '\\'); escape = (source.charAt(cursor) == '\\');
cursor++; cursor++;
} }
@ -272,32 +288,32 @@ public class FHIRLexer {
cursor++; cursor++;
current = source.substring(currentStart, cursor); current = source.substring(currentStart, cursor);
if (ech == '\'') if (ech == '\'')
current = "\'" + current.substring(1, current.length() - 1) + "\'"; current = "\'"+current.substring(1, current.length() - 1)+"\'";
} else if (ch == '`') { } else if (ch == '`') {
cursor++; cursor++;
boolean escape = false; boolean escape = false;
while (cursor < source.length() && (escape || source.charAt(cursor) != '`')) { while (cursor < source.length() && (escape || source.charAt(cursor) != '`')) {
if (escape) if (escape)
escape = false; escape = false;
else else
escape = (source.charAt(cursor) == '\\'); escape = (source.charAt(cursor) == '\\');
cursor++; cursor++;
} }
if (cursor == source.length()) if (cursor == source.length())
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) { } else if (ch == '|' && liquidMode) {
cursor++; cursor++;
ch = source.charAt(cursor); ch = source.charAt(cursor);
if (ch == '|') if (ch == '|')
cursor++; cursor++;
current = source.substring(currentStart, cursor); current = source.substring(currentStart, cursor);
} else if (ch == '@') { } else if (ch == '@'){
int start = cursor; int start = cursor;
cursor++; cursor++;
while (cursor < source.length() && isDateChar(source.charAt(cursor), start)) while (cursor < source.length() && isDateChar(source.charAt(cursor), start))
cursor++; cursor++;
current = source.substring(currentStart, cursor); current = source.substring(currentStart, cursor);
} else { // if CharInSet(ch, ['.', ',', '(', ')', '=', '$']) then } else { // if CharInSet(ch, ['.', ',', '(', ')', '=', '$']) then
cursor++; cursor++;
@ -308,28 +324,35 @@ public class FHIRLexer {
private void skipWhitespaceAndComments() { private void skipWhitespaceAndComments() {
comments.clear(); comments.clear();
commentLocation = null;
boolean last13 = false; boolean last13 = false;
boolean done = false; boolean done = false;
while (cursor < source.length() && !done) { while (cursor < source.length() && !done) {
if (cursor < source.length() - 1 && "//".equals(source.substring(cursor, cursor + 2))) { if (cursor < source.length() -1 && "//".equals(source.substring(cursor, cursor+2)) && !isMetadataStart()) {
int start = cursor + 2; if (commentLocation == null) {
while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n')) { commentLocation = currentLocation.copy();
cursor++; }
int start = cursor+2;
while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n')) {
cursor++;
} }
comments.add(source.substring(start, cursor).trim()); comments.add(source.substring(start, cursor).trim());
} else if (cursor < source.length() - 1 && "/*".equals(source.substring(cursor, cursor + 2))) { } else if (cursor < source.length() - 1 && "/*".equals(source.substring(cursor, cursor+2))) {
int start = cursor + 2; if (commentLocation == null) {
while (cursor < source.length() - 1 && !"*/".equals(source.substring(cursor, cursor + 2))) { commentLocation = currentLocation.copy();
last13 = currentLocation.checkChar(source.charAt(cursor), last13);
cursor++;
} }
if (cursor >= source.length() - 1) { int start = cursor+2;
while (cursor < source.length() - 1 && !"*/".equals(source.substring(cursor, cursor+2))) {
last13 = currentLocation.checkChar(source.charAt(cursor), last13);
cursor++;
}
if (cursor >= source.length() -1) {
error("Unfinished comment"); error("Unfinished comment");
} else { } else {
comments.add(source.substring(start, cursor).trim()); comments.add(source.substring(start, cursor).trim());
cursor = cursor + 2; cursor = cursor + 2;
} }
} else if (Character.isWhitespace(source.charAt(cursor))) { } else if (Utilities.isWhitespace(source.charAt(cursor))) {
last13 = currentLocation.checkChar(source.charAt(cursor), last13); last13 = currentLocation.checkChar(source.charAt(cursor), last13);
cursor++; cursor++;
} else { } else {
@ -337,32 +360,30 @@ public class FHIRLexer {
} }
} }
} }
private boolean isDateChar(char ch, int start) { private boolean isMetadataStart() {
int eot = source.charAt(start + 1) == 'T' ? 10 : 20; return metadataFormat && cursor < source.length() - 2 && "///".equals(source.substring(cursor, cursor+3));
}
return ch == '-' || ch == ':' || ch == 'T' || ch == '+' || ch == 'Z' || Character.isDigit(ch)
|| (cursor - start == eot && ch == '.' && cursor < source.length() - 1 private boolean isDateChar(char ch,int start) {
&& Character.isDigit(source.charAt(cursor + 1))); int eot = source.charAt(start+1) == 'T' ? 10 : 20;
return ch == '-' || ch == ':' || ch == 'T' || ch == '+' || ch == 'Z' || Character.isDigit(ch) || (cursor-start == eot && ch == '.' && cursor < source.length()-1&& Character.isDigit(source.charAt(cursor+1)));
} }
public boolean isOp() { public boolean isOp() {
return ExpressionNode.Operation.fromCode(current) != null; return ExpressionNode.Operation.fromCode(current) != null;
} }
public boolean done() { public boolean done() {
return currentStart >= source.length(); return currentStart >= source.length();
} }
public int nextId() { public int nextId() {
id++; id++;
return id; return id;
} }
public SourceLocation getCurrentStartLocation() { public SourceLocation getCurrentStartLocation() {
return currentStartLocation; return currentStartLocation;
} }
// special case use // special case use
public void setCurrent(String current) { public void setCurrent(String current) {
this.current = current; this.current = current;
@ -387,7 +408,7 @@ public class FHIRLexer {
if (hasComments()) { if (hasComments()) {
String s = comments.get(0); String s = comments.get(0);
comments.remove(0); comments.remove(0);
return s; return s;
} else { } else {
return null; return null;
} }
@ -396,32 +417,31 @@ public class FHIRLexer {
public boolean hasToken(String kw) { public boolean hasToken(String kw) {
return !done() && kw.equals(current); return !done() && kw.equals(current);
} }
public boolean hasToken(String... names) { public boolean hasToken(String... names) {
if (done()) if (done())
return false; return false;
for (String s : names) for (String s : names)
if (s.equals(current)) if (s.equals(current))
return true; return true;
return false; return false;
} }
public void token(String kw) throws FHIRLexerException { public void token(String kw) throws FHIRLexerException {
if (!kw.equals(current)) if (!kw.equals(current))
throw error("Found \"" + current + "\" expecting \"" + kw + "\""); throw error("Found \""+current+"\" expecting \""+kw+"\"");
next(); next();
} }
public String readConstant(String desc) throws FHIRLexerException { public String readConstant(String desc) throws FHIRLexerException {
if (!isStringConstant()) if (!isStringConstant())
throw error("Found " + current + " expecting \"[" + desc + "]\""); throw error("Found "+current+" expecting \"["+desc+"]\"");
return processConstant(take()); return processConstant(take());
} }
public String readFixedName(String desc) throws FHIRLexerException { public String readFixedName(String desc) throws FHIRLexerException {
if (!isFixedName()) if (!isFixedName())
throw error("Found " + current + " expecting \"[" + desc + "]\""); throw error("Found "+current+" expecting \"["+desc+"]\"");
return processFixedName(take()); return processFixedName(take());
} }
@ -429,21 +449,21 @@ public class FHIRLexer {
public String processConstant(String s) throws FHIRLexerException { public String processConstant(String s) throws FHIRLexerException {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
int i = 1; int i = 1;
while (i < s.length() - 1) { while (i < s.length()-1) {
char ch = s.charAt(i); char ch = s.charAt(i);
if (ch == '\\') { if (ch == '\\') {
i++; i++;
switch (s.charAt(i)) { switch (s.charAt(i)) {
case 't': case 't':
b.append('\t'); b.append('\t');
break; break;
case 'r': case 'r':
b.append('\r'); b.append('\r');
break; break;
case 'n': case 'n':
b.append('\n'); b.append('\n');
break; break;
case 'f': case 'f':
b.append('\f'); b.append('\f');
break; break;
case '\'': case '\'':
@ -455,20 +475,20 @@ public class FHIRLexer {
case '`': case '`':
b.append('`'); b.append('`');
break; break;
case '\\': case '\\':
b.append('\\'); b.append('\\');
break; break;
case '/': case '/':
b.append('/'); b.append('/');
break; break;
case 'u': case 'u':
i++; i++;
int uc = Integer.parseInt(s.substring(i, i + 4), 16); int uc = Integer.parseInt(s.substring(i, i+4), 16);
b.append((char) uc); b.append((char) uc);
i = i + 4; i = i + 4;
break; break;
default: default:
throw new FHIRLexerException("Unknown character escape \\" + s.charAt(i)); throw new FHIRLexerException("Unknown character escape \\"+s.charAt(i), currentLocation);
} }
} else { } else {
b.append(ch); b.append(ch);
@ -477,25 +497,25 @@ public class FHIRLexer {
} }
return b.toString(); return b.toString();
} }
public String processFixedName(String s) throws FHIRLexerException { public String processFixedName(String s) throws FHIRLexerException {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
int i = 1; int i = 1;
while (i < s.length() - 1) { while (i < s.length()-1) {
char ch = s.charAt(i); char ch = s.charAt(i);
if (ch == '\\') { if (ch == '\\') {
i++; i++;
switch (s.charAt(i)) { switch (s.charAt(i)) {
case 't': case 't':
b.append('\t'); b.append('\t');
break; break;
case 'r': case 'r':
b.append('\r'); b.append('\r');
break; break;
case 'n': case 'n':
b.append('\n'); b.append('\n');
break; break;
case 'f': case 'f':
b.append('\f'); b.append('\f');
break; break;
case '\'': case '\'':
@ -504,20 +524,20 @@ public class FHIRLexer {
case '"': case '"':
b.append('"'); b.append('"');
break; break;
case '\\': case '\\':
b.append('\\'); b.append('\\');
break; break;
case '/': case '/':
b.append('/'); b.append('/');
break; break;
case 'u': case 'u':
i++; i++;
int uc = Integer.parseInt(s.substring(i, i + 4), 16); int uc = Integer.parseInt(s.substring(i, i+4), 16);
b.append((char) uc); b.append((char) uc);
i = i + 4; i = i + 4;
break; break;
default: default:
throw new FHIRLexerException("Unknown character escape \\" + s.charAt(i)); throw new FHIRLexerException("Unknown character escape \\"+s.charAt(i), currentLocation);
} }
} else { } else {
b.append(ch); b.append(ch);
@ -530,9 +550,8 @@ public class FHIRLexer {
public void skipToken(String token) throws FHIRLexerException { public void skipToken(String token) throws FHIRLexerException {
if (getCurrent().equals(token)) if (getCurrent().equals(token))
next(); next();
} }
public String takeDottedToken() throws FHIRLexerException { public String takeDottedToken() throws FHIRLexerException {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append(take()); b.append(take());
@ -542,21 +561,43 @@ public class FHIRLexer {
} }
return b.toString(); return b.toString();
} }
public int getCurrentStart() { public int getCurrentStart() {
return currentStart; return currentStart;
} }
public String getSource() { public String getSource() {
return source; return source;
} }
public boolean isLiquidMode() { public boolean isLiquidMode() {
return liquidMode; return liquidMode;
} }
public void setLiquidMode(boolean liquidMode) { public void setLiquidMode(boolean liquidMode) {
this.liquidMode = liquidMode; this.liquidMode = liquidMode;
} }
public SourceLocation getCommentLocation() {
return this.commentLocation;
}
public boolean isMetadataFormat() {
return metadataFormat;
}
public void setMetadataFormat(boolean metadataFormat) {
this.metadataFormat = metadataFormat;
}
public List<String> cloneComments() {
List<String> res = new ArrayList<>();
res.addAll(getComments());
return res;
}
public String tokenWithTrailingComment(String token) {
int line = getCurrentLocation().getLine();
token(token);
if (getComments().size() > 0 && getCommentLocation().getLine() == line) {
return getFirstComment();
} else {
return null;
}
}
public boolean isAllowDoubleQuotes() {
return allowDoubleQuotes;
}
} }

View File

@ -0,0 +1,20 @@
package org.hl7.fhir.r4b.utils;
import org.hl7.fhir.utilities.Utilities;
public class FHIRPathConstant {
public static boolean isFHIRPathConstant(String string) {
return !Utilities.noString(string) && ((string.charAt(0) == '\'' || string.charAt(0) == '"') || string.charAt(0) == '@' || string.charAt(0) == '%' ||
string.charAt(0) == '-' || string.charAt(0) == '+' || (string.charAt(0) >= '0' && string.charAt(0) <= '9') ||
string.equals("true") || string.equals("false") || string.equals("{}"));
}
public static boolean isFHIRPathFixedName(String string) {
return string != null && (string.charAt(0) == '`');
}
public static boolean isFHIRPathStringConstant(String string) {
return string.charAt(0) == '\'' || string.charAt(0) == '"' || string.charAt(0) == '`';
}
}

View File

@ -282,6 +282,7 @@ public class FHIRPathEngine {
// host // host
private boolean doNotEnforceAsSingletonRule; private boolean doNotEnforceAsSingletonRule;
private boolean doNotEnforceAsCaseSensitive; private boolean doNotEnforceAsCaseSensitive;
private boolean allowDoubleQuotes;
// if the fhir path expressions are allowed to use constants beyond those // if the fhir path expressions are allowed to use constants beyond those
// defined in the specification // defined in the specification
@ -531,7 +532,7 @@ public class FHIRPathEngine {
} }
public ExpressionNode parse(String path, String name) throws FHIRLexerException { public ExpressionNode parse(String path, String name) throws FHIRLexerException {
FHIRLexer lexer = new FHIRLexer(path, name); FHIRLexer lexer = new FHIRLexer(path, name, false, allowDoubleQuotes);
if (lexer.done()) { if (lexer.done()) {
throw lexer.error("Path cannot be empty"); throw lexer.error("Path cannot be empty");
} }
@ -572,7 +573,7 @@ public class FHIRPathEngine {
* @throws Exception * @throws Exception
*/ */
public ExpressionNodeWithOffset parsePartial(String path, int i) throws FHIRLexerException { public ExpressionNodeWithOffset parsePartial(String path, int i) throws FHIRLexerException {
FHIRLexer lexer = new FHIRLexer(path, i); FHIRLexer lexer = new FHIRLexer(path, i, allowDoubleQuotes);
if (lexer.done()) { if (lexer.done()) {
throw lexer.error("Path cannot be empty"); throw lexer.error("Path cannot be empty");
} }
@ -1400,8 +1401,7 @@ public class FHIRPathEngine {
private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count)
throws FHIRLexerException { throws FHIRLexerException {
if (exp.getParameters().size() != count) { if (exp.getParameters().size() != count) {
throw lexer.error("The function \"" + exp.getName() + "\" requires " + Integer.toString(count) + " parameters", throw lexer.error("The function \"" + exp.getName() + "\" requires " + Integer.toString(count) + " parameters");
location.toString());
} }
return true; return true;
} }
@ -1410,7 +1410,7 @@ public class FHIRPathEngine {
int countMax) throws FHIRLexerException { int countMax) throws FHIRLexerException {
if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax) { if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax) {
throw lexer.error("The function \"" + exp.getName() + "\" requires between " + Integer.toString(countMin) throw lexer.error("The function \"" + exp.getName() + "\" requires between " + Integer.toString(countMin)
+ " and " + Integer.toString(countMax) + " parameters", location.toString()); + " and " + Integer.toString(countMax) + " parameters");
} }
return true; return true;
} }
@ -6722,4 +6722,14 @@ public class FHIRPathEngine {
this.liquidMode = liquidMode; this.liquidMode = liquidMode;
} }
public ProfileUtilities getProfileUtilities() {
return profileUtilities;
}
public boolean isAllowDoubleQuotes() {
return allowDoubleQuotes;
}
public void setAllowDoubleQuotes(boolean allowDoubleQuotes) {
this.allowDoubleQuotes = allowDoubleQuotes;
}
} }

View File

@ -50,6 +50,7 @@ import org.hl7.fhir.r4b.model.ValueSet;
import org.hl7.fhir.r4b.utils.FHIRPathEngine.ExpressionNodeWithOffset; import org.hl7.fhir.r4b.utils.FHIRPathEngine.ExpressionNodeWithOffset;
import org.hl7.fhir.r4b.utils.FHIRPathEngine.IEvaluationContext; import org.hl7.fhir.r4b.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.hl7.fhir.utilities.xhtml.XhtmlNode;
@ -70,17 +71,27 @@ public class LiquidEngine implements IEvaluationContext {
private class LiquidEngineContext { private class LiquidEngineContext {
private Object externalContext; private Object externalContext;
private Map<String, Base> vars = new HashMap<>(); private Map<String, Base> loopVars = new HashMap<>();
private Map<String, Base> globalVars = new HashMap<>();
public LiquidEngineContext(Object externalContext) { public LiquidEngineContext(Object externalContext) {
super(); super();
this.externalContext = externalContext; this.externalContext = externalContext;
globalVars = new HashMap<>();
}
public LiquidEngineContext(Object externalContext, LiquidEngineContext existing) {
super();
this.externalContext = externalContext;
loopVars.putAll(existing.loopVars);
globalVars = existing.globalVars;
} }
public LiquidEngineContext(LiquidEngineContext existing) { public LiquidEngineContext(LiquidEngineContext existing) {
super(); super();
externalContext = existing.externalContext; externalContext = existing.externalContext;
vars.putAll(existing.vars); loopVars.putAll(existing.loopVars);
globalVars = existing.globalVars;
} }
} }
@ -120,6 +131,7 @@ public class LiquidEngine implements IEvaluationContext {
} }
return b.toString(); return b.toString();
} }
private abstract class LiquidNode { private abstract class LiquidNode {
protected void closeUp() { protected void closeUp() {
@ -148,10 +160,10 @@ public class LiquidEngine implements IEvaluationContext {
} }
} }
private enum LiquidFilter { private enum LiquidFilter {
PREPEND; PREPEND;
public static LiquidFilter fromCode(String code) { public static LiquidFilter fromCode(String code) {
if ("prepend".equals(code)) { if ("prepend".equals(code)) {
return PREPEND; return PREPEND;
@ -163,15 +175,14 @@ public class LiquidEngine implements IEvaluationContext {
private class LiquidExpressionNode { private class LiquidExpressionNode {
private LiquidFilter filter; // null at root private LiquidFilter filter; // null at root
private ExpressionNode expression; // null for some filters private ExpressionNode expression; // null for some filters
public LiquidExpressionNode(LiquidFilter filter, ExpressionNode expression) { public LiquidExpressionNode(LiquidFilter filter, ExpressionNode expression) {
super(); super();
this.filter = filter; this.filter = filter;
this.expression = expression; this.expression = expression;
} }
} }
private class LiquidStatement extends LiquidNode { private class LiquidStatement extends LiquidNode {
private String statement; private String statement;
private List<LiquidExpressionNode> compiled = new ArrayList<>(); private List<LiquidExpressionNode> compiled = new ArrayList<>();
@ -179,7 +190,7 @@ public class LiquidEngine implements IEvaluationContext {
@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.size() == 0) { if (compiled.size() == 0) {
FHIRLexer lexer = new FHIRLexer(statement, "liquid statement"); FHIRLexer lexer = new FHIRLexer(statement, "liquid statement", false, true);
lexer.setLiquidMode(true); lexer.setLiquidMode(true);
compiled.add(new LiquidExpressionNode(null, engine.parse(lexer))); compiled.add(new LiquidExpressionNode(null, engine.parse(lexer)));
while (!lexer.done()) { while (!lexer.done()) {
@ -188,7 +199,7 @@ public class LiquidEngine implements IEvaluationContext {
String f = lexer.getCurrent(); String f = lexer.getCurrent();
LiquidFilter filter = LiquidFilter.fromCode(f); LiquidFilter filter = LiquidFilter.fromCode(f);
if (filter == null) { if (filter == null) {
lexer.error("Unknown Liquid filter '" + f + "'"); lexer.error(engine.getWorker().formatMessage(I18nConstants.LIQUID_UNKNOWN_FILTER, f));
} }
lexer.next(); lexer.next();
if (!lexer.done() && lexer.getCurrent().equals(":")) { if (!lexer.done() && lexer.getCurrent().equals(":")) {
@ -198,21 +209,20 @@ public class LiquidEngine implements IEvaluationContext {
compiled.add(new LiquidExpressionNode(filter, null)); compiled.add(new LiquidExpressionNode(filter, null));
} }
} else { } else {
lexer.error("Unexpected syntax parsing liquid statement"); lexer.error(engine.getWorker().formatMessage(I18nConstants.LIQUID_UNKNOWN_SYNTAX));
} }
} }
} }
String t = null; String t = null;
for (LiquidExpressionNode i : compiled) { for (LiquidExpressionNode i : compiled) {
if (i.filter == null) { // first if (i.filter == null) { // first
t = stmtToString(ctxt, engine.evaluate(ctxt, resource, resource, resource, i.expression)); t = stmtToString(ctxt, engine.evaluate(ctxt, resource, resource, resource, i.expression));
} else } else switch (i.filter) {
switch (i.filter) { case PREPEND:
case PREPEND: t = stmtToString(ctxt, engine.evaluate(ctxt, resource, resource, resource, i.expression)) + t;
t = stmtToString(ctxt, engine.evaluate(ctxt, resource, resource, resource, i.expression)) + t; break;
break; }
}
} }
b.append(t); b.append(t);
} }
@ -221,10 +231,7 @@ public class LiquidEngine implements IEvaluationContext {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
boolean first = true; boolean first = true;
for (Base i : items) { for (Base i : items) {
if (first) if (first) first = false; else b.append(", ");
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));
} }
@ -312,6 +319,30 @@ public class LiquidEngine implements IEvaluationContext {
} }
} }
private class LiquidAssign extends LiquidNode {
private String varName;
private String expression;
private ExpressionNode compiled;
@Override
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
if (compiled == null) {
boolean dbl = engine.isAllowDoubleQuotes();
engine.setAllowDoubleQuotes(true);
ExpressionNodeWithOffset po = engine.parsePartial(expression, 0);
compiled = po.getNode();
engine.setAllowDoubleQuotes(dbl);
}
List<Base> list = engine.evaluate(ctxt, resource, resource, resource, compiled);
if (list.isEmpty()) {
ctxt.globalVars.remove(varName);
} else if (list.size() == 1) {
ctxt.globalVars.put(varName, list.get(0));
} else {
throw new Error("Assign returned a list?");
}
}
}
private class LiquidFor extends LiquidNode { private class LiquidFor extends LiquidNode {
private String varName; private String varName;
private String condition; private String condition;
@ -349,8 +380,11 @@ public class LiquidEngine implements IEvaluationContext {
} }
if (limit >= 0 && i == limit) { if (limit >= 0 && i == limit) {
break; break;
}
if (lctxt.globalVars.containsKey(varName)) {
throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_VARIABLE_ALREADY_ASSIGNED, varName));
} }
lctxt.vars.put(varName, o); lctxt.loopVars.put(varName, o);
boolean wantBreak = false; boolean wantBreak = false;
for (LiquidNode n : body) { for (LiquidNode n : body) {
try { try {
@ -379,7 +413,7 @@ public class LiquidEngine implements IEvaluationContext {
} else if (cnt.startsWith("limit")) { } else if (cnt.startsWith("limit")) {
cnt = cnt.substring(5).trim(); cnt = cnt.substring(5).trim();
if (!cnt.startsWith(":")) { if (!cnt.startsWith(":")) {
throw new FHIRException("Exception evaluating " + src + ": limit is not followed by ':'"); throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_COLON, src));
} }
cnt = cnt.substring(1).trim(); cnt = cnt.substring(1).trim();
int i = 0; int i = 0;
@ -387,14 +421,14 @@ public class LiquidEngine implements IEvaluationContext {
i++; i++;
} }
if (i == 0) { if (i == 0) {
throw new FHIRException("Exception evaluating " + src + ": limit is not followed by a number"); throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_NUMBER, src));
} }
limit = Integer.parseInt(cnt.substring(0, i)); limit = Integer.parseInt(cnt.substring(0, i));
cnt = cnt.substring(i); cnt = cnt.substring(i);
} else if (cnt.startsWith("offset")) { } else if (cnt.startsWith("offset")) {
cnt = cnt.substring(6).trim(); cnt = cnt.substring(6).trim();
if (!cnt.startsWith(":")) { if (!cnt.startsWith(":")) {
throw new FHIRException("Exception evaluating " + src + ": limit is not followed by ':'"); throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_COLON, src));
} }
cnt = cnt.substring(1).trim(); cnt = cnt.substring(1).trim();
int i = 0; int i = 0;
@ -402,14 +436,14 @@ public class LiquidEngine implements IEvaluationContext {
i++; i++;
} }
if (i == 0) { if (i == 0) {
throw new FHIRException("Exception evaluating " + src + ": limit is not followed by a number"); throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_NUMBER, src));
} }
offset = Integer.parseInt(cnt.substring(0, i)); offset = Integer.parseInt(cnt.substring(0, i));
cnt = cnt.substring(i); cnt = cnt.substring(i);
} else { } else {
throw new FHIRException("Exception evaluating " + src + ": unexpected content at " + cnt); throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_UNEXPECTED, cnt));
} }
} }
} }
} }
@ -422,9 +456,9 @@ public class LiquidEngine implements IEvaluationContext {
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, ctxt);
Tuple incl = new Tuple(); Tuple incl = new Tuple();
nctxt.vars.put("include", incl); nctxt.loopVars.put("include", incl);
for (String s : params.keySet()) { for (String s : params.keySet()) {
incl.addProperty(s, engine.evaluate(ctxt, resource, resource, resource, params.get(s))); incl.addProperty(s, engine.evaluate(ctxt, resource, resource, resource, params.get(s)));
} }
@ -481,13 +515,11 @@ public class LiquidEngine implements IEvaluationContext {
cnt = "," + cnt.substring(5).trim(); cnt = "," + cnt.substring(5).trim();
while (!Utilities.noString(cnt)) { while (!Utilities.noString(cnt)) {
if (!cnt.startsWith(",")) { if (!cnt.startsWith(",")) {
throw new FHIRException( throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_EXPECTING, name, cnt.charAt(0), ','));
"Script " + name + ": Script " + name + ": Found " + cnt.charAt(0) + " expecting ',' parsing cycle");
} }
cnt = cnt.substring(1).trim(); cnt = cnt.substring(1).trim();
if (!cnt.startsWith("\"")) { if (!cnt.startsWith("\"")) {
throw new FHIRException( throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_EXPECTING, name, cnt.charAt(0), '"'));
"Script " + name + ": Script " + name + ": Found " + cnt.charAt(0) + " expecting '\"' parsing cycle");
} }
cnt = cnt.substring(1); cnt = cnt.substring(1);
int i = 0; int i = 0;
@ -495,7 +527,7 @@ public class LiquidEngine implements IEvaluationContext {
i++; i++;
} }
if (i == cnt.length()) { if (i == cnt.length()) {
throw new FHIRException("Script " + name + ": Script " + name + ": Found unterminated string parsing cycle"); throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_UNTERMINATED, name));
} }
res.list.add(cnt.substring(0, i)); res.list.add(cnt.substring(0, i));
cnt = cnt.substring(i + 1).trim(); cnt = cnt.substring(i + 1).trim();
@ -527,9 +559,10 @@ public class LiquidEngine implements IEvaluationContext {
list.add(parseCycle(cnt)); 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 if (cnt.startsWith("assign "))
list.add(parseAssign(cnt.substring(6).trim()));
else else
throw new FHIRException( throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_UNKNOWN_FLOW_STMT,name, cnt));
"Script " + name + ": Script " + name + ": Unknown flow control statement " + cnt);
} else { // next2() == '{' } else { // next2() == '{'
list.add(parseStatement()); list.add(parseStatement());
} }
@ -543,8 +576,7 @@ public class LiquidEngine implements IEvaluationContext {
n.closeUp(); n.closeUp();
if (terminators.length > 0) if (terminators.length > 0)
if (!isTerminator(close, terminators)) if (!isTerminator(close, terminators))
throw new FHIRException( throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_UNKNOWN_NOEND, name, terminators));
"Script " + name + ": Script " + name + ": Found end of script looking for " + terminators);
return close; return close;
} }
@ -588,7 +620,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(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_INCLUDE, 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)))
@ -598,10 +630,10 @@ public class LiquidEngine implements IEvaluationContext {
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(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_INCLUDE, name, 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(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_INCLUDE, name, cnt));
i++; i++;
ExpressionNodeWithOffset t = engine.parsePartial(cnt, i); ExpressionNodeWithOffset t = engine.parsePartial(cnt, i);
i = t.getOffset(); i = t.getOffset();
@ -618,13 +650,16 @@ public class LiquidEngine implements IEvaluationContext {
i++; i++;
LiquidFor res = new LiquidFor(); LiquidFor res = new LiquidFor();
res.varName = cnt.substring(0, i); res.varName = cnt.substring(0, i);
if ("include".equals(res.varName)) {
throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_VARIABLE_ILLEGAL, res.varName));
}
while (Character.isWhitespace(cnt.charAt(i))) while (Character.isWhitespace(cnt.charAt(i)))
i++; i++;
int j = i; int j = i;
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(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_LOOP, name, cnt));
res.condition = cnt.substring(i).trim(); res.condition = cnt.substring(i).trim();
parseList(res.body, false, new String[] { "endloop" }); parseList(res.body, false, new String[] { "endloop" });
return res; return res;
@ -636,13 +671,16 @@ public class LiquidEngine implements IEvaluationContext {
i++; i++;
LiquidFor res = new LiquidFor(); LiquidFor res = new LiquidFor();
res.varName = cnt.substring(0, i); res.varName = cnt.substring(0, i);
if ("include".equals(res.varName)) {
throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_VARIABLE_ILLEGAL, res.varName));
}
while (Character.isWhitespace(cnt.charAt(i))) while (Character.isWhitespace(cnt.charAt(i)))
i++; i++;
int j = i; int j = i;
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(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_LOOP, name, cnt));
res.condition = cnt.substring(i).trim(); res.condition = cnt.substring(i).trim();
String term = parseList(res.body, true, new String[] { "endfor", "else" }); String term = parseList(res.body, true, new String[] { "endfor", "else" });
if ("else".equals(term)) { if ("else".equals(term)) {
@ -651,6 +689,21 @@ public class LiquidEngine implements IEvaluationContext {
return res; return res;
} }
private LiquidNode parseAssign(String cnt) throws FHIRException {
int i = 0;
while (!Character.isWhitespace(cnt.charAt(i)))
i++;
LiquidAssign res = new LiquidAssign();
res.varName = cnt.substring(0, i);
while (Character.isWhitespace(cnt.charAt(i)))
i++;
int j = i;
while (!Character.isWhitespace(cnt.charAt(i)))
i++;
res.expression = cnt.substring(i).trim();
return res;
}
private String parseTag(char ch) throws FHIRException { private String parseTag(char ch) throws FHIRException {
grab(); grab();
grab(); grab();
@ -659,7 +712,7 @@ public class LiquidEngine implements IEvaluationContext {
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(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_NOTERM, name, "{% " + b.toString()));
grab(); grab();
grab(); grab();
return b.toString().trim(); return b.toString().trim();
@ -673,7 +726,7 @@ public class LiquidEngine implements IEvaluationContext {
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(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_NOTERM, name, "{{ " + b.toString()));
grab(); grab();
grab(); grab();
LiquidStatement res = new LiquidStatement(); LiquidStatement res = new LiquidStatement();
@ -686,8 +739,10 @@ public class LiquidEngine implements IEvaluationContext {
@Override @Override
public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException { public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
LiquidEngineContext ctxt = (LiquidEngineContext) appContext; LiquidEngineContext ctxt = (LiquidEngineContext) appContext;
if (ctxt.vars.containsKey(name)) if (ctxt.loopVars.containsKey(name))
return new ArrayList<Base>(Arrays.asList(ctxt.vars.get(name))); return new ArrayList<Base>(Arrays.asList(ctxt.loopVars.get(name)));
if (ctxt.globalVars.containsKey(name))
return new ArrayList<Base>(Arrays.asList(ctxt.globalVars.get(name)));
if (externalHostServices == null) if (externalHostServices == null)
return new ArrayList<Base>(); return new ArrayList<Base>();
return externalHostServices.resolveConstant(ctxt.externalContext, name, beforeContext); return externalHostServices.resolveConstant(ctxt.externalContext, name, beforeContext);
@ -716,8 +771,7 @@ public class LiquidEngine implements IEvaluationContext {
} }
@Override @Override
public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException {
throws PathEngineException {
if (externalHostServices == null) if (externalHostServices == null)
return null; return null;
LiquidEngineContext ctxt = (LiquidEngineContext) appContext; LiquidEngineContext ctxt = (LiquidEngineContext) appContext;
@ -725,8 +779,7 @@ public class LiquidEngine implements IEvaluationContext {
} }
@Override @Override
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
List<List<Base>> parameters) {
if (externalHostServices == null) if (externalHostServices == null)
return null; return null;
LiquidEngineContext ctxt = (LiquidEngineContext) appContext; LiquidEngineContext ctxt = (LiquidEngineContext) appContext;