Merge pull request #1366 from hapifhir/2023-07-gg-fhirpath-fixes
2023 07 gg fhirpath fixes
This commit is contained in:
commit
b9d6249d49
|
@ -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 {
|
||||||
|
@ -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')
|
if (!( (current.charAt(1) >= 'A' && current.charAt(1) <= 'Z') || (current.charAt(1) >= 'a' && current.charAt(1) <= 'z') ||
|
||||||
|| (current.charAt(1) >= 'a' && current.charAt(1) <= 'z')
|
(current.charAt(1) >= '0' && current.charAt(1) <= '9')))
|
||||||
|| (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 {
|
||||||
|
@ -168,9 +187,7 @@ public class FHIRLexer {
|
||||||
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 == '.' ) {
|
||||||
|
@ -181,8 +198,7 @@ public class FHIRLexer {
|
||||||
} 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++;
|
||||||
|
@ -191,9 +207,8 @@ public class FHIRLexer {
|
||||||
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) == ':'
|
|
||||||
|| 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) == '/')) {
|
||||||
// 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);
|
current = source.substring(currentStart, cursor);
|
||||||
|
} else {
|
||||||
|
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,7 +244,7 @@ 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) != '"')) {
|
||||||
|
@ -308,16 +324,23 @@ 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()) {
|
||||||
|
if (commentLocation == null) {
|
||||||
|
commentLocation = currentLocation.copy();
|
||||||
|
}
|
||||||
int start = cursor+2;
|
int start = cursor+2;
|
||||||
while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n')) {
|
while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n')) {
|
||||||
cursor++;
|
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))) {
|
||||||
|
if (commentLocation == null) {
|
||||||
|
commentLocation = currentLocation.copy();
|
||||||
|
}
|
||||||
int start = cursor+2;
|
int start = cursor+2;
|
||||||
while (cursor < source.length() - 1 && !"*/".equals(source.substring(cursor, cursor+2))) {
|
while (cursor < source.length() - 1 && !"*/".equals(source.substring(cursor, cursor+2))) {
|
||||||
last13 = currentLocation.checkChar(source.charAt(cursor), last13);
|
last13 = currentLocation.checkChar(source.charAt(cursor), last13);
|
||||||
|
@ -329,7 +352,7 @@ public class FHIRLexer {
|
||||||
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 {
|
||||||
|
@ -338,27 +361,25 @@ public class FHIRLexer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isMetadataStart() {
|
||||||
|
return metadataFormat && cursor < source.length() - 2 && "///".equals(source.substring(cursor, cursor+3));
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isDateChar(char ch,int start) {
|
private boolean isDateChar(char ch,int start) {
|
||||||
int eot = source.charAt(start+1) == 'T' ? 10 : 20;
|
int eot = source.charAt(start+1) == 'T' ? 10 : 20;
|
||||||
|
|
||||||
return ch == '-' || ch == ':' || ch == 'T' || ch == '+' || ch == 'Z' || Character.isDigit(ch)
|
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)));
|
||||||
|| (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;
|
||||||
}
|
}
|
||||||
|
@ -396,7 +417,6 @@ 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;
|
||||||
|
@ -468,7 +488,7 @@ public class FHIRLexer {
|
||||||
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);
|
||||||
|
@ -517,7 +537,7 @@ public class FHIRLexer {
|
||||||
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);
|
||||||
|
@ -532,7 +552,6 @@ public class FHIRLexer {
|
||||||
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());
|
||||||
|
@ -546,17 +565,39 @@ public class FHIRLexer {
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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) == '`';
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +132,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() {
|
||||||
}
|
}
|
||||||
|
@ -163,7 +175,6 @@ 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;
|
||||||
|
@ -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,7 +209,7 @@ 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,8 +218,7 @@ public class LiquidEngine implements IEvaluationContext {
|
||||||
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;
|
||||||
|
@ -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;
|
||||||
|
@ -350,7 +381,10 @@ public class LiquidEngine implements IEvaluationContext {
|
||||||
if (limit >= 0 && i == limit) {
|
if (limit >= 0 && i == limit) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
lctxt.vars.put(varName, o);
|
if (lctxt.globalVars.containsKey(varName)) {
|
||||||
|
throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_VARIABLE_ALREADY_ASSIGNED, varName));
|
||||||
|
}
|
||||||
|
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,12 +436,12 @@ 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;
|
||||||
|
|
|
@ -713,7 +713,7 @@ public class StructureMapUtilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
public StructureMap parse(String text, String srcName) throws FHIRException {
|
public StructureMap parse(String text, String srcName) throws FHIRException {
|
||||||
FHIRLexer lexer = new FHIRLexer(text, srcName);
|
FHIRLexer lexer = new FHIRLexer(text, srcName, true, true);
|
||||||
if (lexer.done())
|
if (lexer.done())
|
||||||
throw lexer.error("Map Input cannot be empty");
|
throw lexer.error("Map Input cannot be empty");
|
||||||
lexer.token("map");
|
lexer.token("map");
|
||||||
|
|
|
@ -359,6 +359,10 @@ public class ExpressionNode {
|
||||||
|
|
||||||
public enum CollectionStatus {
|
public enum CollectionStatus {
|
||||||
SINGLETON, ORDERED, UNORDERED;
|
SINGLETON, ORDERED, UNORDERED;
|
||||||
|
|
||||||
|
boolean isList() {
|
||||||
|
return this == ORDERED || this == UNORDERED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//the expression will have one of either name or constant
|
//the expression will have one of either name or constant
|
||||||
|
|
|
@ -285,7 +285,7 @@ public class TypeDetails {
|
||||||
public void update(TypeDetails source) {
|
public void update(TypeDetails source) {
|
||||||
for (ProfiledType pt : source.types)
|
for (ProfiledType pt : source.types)
|
||||||
addType(pt);
|
addType(pt);
|
||||||
if (collectionStatus == null)
|
if (collectionStatus == null || collectionStatus == CollectionStatus.SINGLETON)
|
||||||
collectionStatus = source.collectionStatus;
|
collectionStatus = source.collectionStatus;
|
||||||
else if (source.collectionStatus == CollectionStatus.UNORDERED)
|
else if (source.collectionStatus == CollectionStatus.UNORDERED)
|
||||||
collectionStatus = source.collectionStatus;
|
collectionStatus = source.collectionStatus;
|
||||||
|
@ -516,5 +516,8 @@ public class TypeDetails {
|
||||||
public static TypeDetails empty() {
|
public static TypeDetails empty() {
|
||||||
return new TypeDetails(CollectionStatus.SINGLETON);
|
return new TypeDetails(CollectionStatus.SINGLETON);
|
||||||
}
|
}
|
||||||
|
public boolean isList() {
|
||||||
|
return collectionStatus != null && collectionStatus.isList();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -643,7 +643,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
|
||||||
|
|
||||||
private boolean checkExpansion(Coding code, List<ValueSetExpansionContainsComponent> contains, VersionInfo vi) {
|
private boolean checkExpansion(Coding code, List<ValueSetExpansionContainsComponent> contains, VersionInfo vi) {
|
||||||
for (ValueSetExpansionContainsComponent containsComponent: contains) {
|
for (ValueSetExpansionContainsComponent containsComponent: contains) {
|
||||||
if (containsComponent.getSystem().equals(code.getSystem()) && containsComponent.getCode().equals(code.getCode())) {
|
if (containsComponent.hasSystem() && containsComponent.hasCode() && containsComponent.getSystem().equals(code.getSystem()) && containsComponent.getCode().equals(code.getCode())) {
|
||||||
vi.setExpansionVersion(containsComponent.getVersion());
|
vi.setExpansionVersion(containsComponent.getVersion());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -281,6 +281,7 @@ public class FHIRPathEngine {
|
||||||
private boolean doNotEnforceAsSingletonRule;
|
private boolean doNotEnforceAsSingletonRule;
|
||||||
private boolean doNotEnforceAsCaseSensitive;
|
private boolean doNotEnforceAsCaseSensitive;
|
||||||
private boolean allowDoubleQuotes;
|
private boolean allowDoubleQuotes;
|
||||||
|
private List<String> typeWarnings = new ArrayList<>();
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -656,7 +657,8 @@ public class FHIRPathEngine {
|
||||||
* @throws PathEngineException
|
* @throws PathEngineException
|
||||||
* @if the path is not valid
|
* @if the path is not valid
|
||||||
*/
|
*/
|
||||||
public TypeDetails checkOnTypes(Object appContext, String resourceType, List<String> typeList, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
|
public TypeDetails checkOnTypes(Object appContext, String resourceType, List<String> typeList, ExpressionNode expr, List<String> warnings) throws FHIRLexerException, PathEngineException, DefinitionException {
|
||||||
|
typeWarnings.clear();
|
||||||
|
|
||||||
// if context is a path that refers to a type, do that conversion now
|
// if context is a path that refers to a type, do that conversion now
|
||||||
TypeDetails types = new TypeDetails(CollectionStatus.SINGLETON);
|
TypeDetails types = new TypeDetails(CollectionStatus.SINGLETON);
|
||||||
|
@ -689,7 +691,9 @@ public class FHIRPathEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, null, true, false);
|
TypeDetails res = executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, null, true, false);
|
||||||
|
warnings.addAll(typeWarnings);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2017,18 +2021,54 @@ public class FHIRPathEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void checkCardinalityForComparabilitySame(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) {
|
||||||
|
if (left.isList() && !right.isList()) {
|
||||||
|
typeWarnings.add(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT, expr.toString()));
|
||||||
|
} else if (!left.isList() && right.isList()) {
|
||||||
|
typeWarnings.add(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT, expr.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCardinalityForSingle(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) {
|
||||||
|
if (left.isList()) {
|
||||||
|
typeWarnings.add(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT, expr.toString()));
|
||||||
|
}
|
||||||
|
if (right.isList()) {
|
||||||
|
typeWarnings.add(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT, expr.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) {
|
private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) {
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case Equals: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
case Equals:
|
||||||
case Equivalent: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
checkCardinalityForComparabilitySame(left, operation, right, expr);
|
||||||
case NotEquals: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
||||||
case NotEquivalent: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
case Equivalent:
|
||||||
case LessThan: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
checkCardinalityForComparabilitySame(left, operation, right, expr);
|
||||||
case Greater: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
||||||
case LessOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
case NotEquals:
|
||||||
case GreaterOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
checkCardinalityForComparabilitySame(left, operation, right, expr);
|
||||||
case Is: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
||||||
|
case NotEquivalent:
|
||||||
|
checkCardinalityForComparabilitySame(left, operation, right, expr);
|
||||||
|
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
||||||
|
case LessThan:
|
||||||
|
checkCardinalityForSingle(left, operation, right, expr);
|
||||||
|
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
||||||
|
case Greater:
|
||||||
|
checkCardinalityForSingle(left, operation, right, expr);
|
||||||
|
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
||||||
|
case LessOrEqual:
|
||||||
|
checkCardinalityForSingle(left, operation, right, expr);
|
||||||
|
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
||||||
|
case GreaterOrEqual:
|
||||||
|
checkCardinalityForSingle(left, operation, right, expr);
|
||||||
|
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
||||||
|
case Is:
|
||||||
|
checkCardinalityForSingle(left, operation, right, expr);
|
||||||
|
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
||||||
case As:
|
case As:
|
||||||
|
checkCardinalityForSingle(left, operation, right, expr);
|
||||||
TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, right.getTypes());
|
TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, right.getTypes());
|
||||||
if (td.typesHaveTargets()) {
|
if (td.typesHaveTargets()) {
|
||||||
td.addTargets(left.getTargets());
|
td.addTargets(left.getTargets());
|
||||||
|
@ -2040,6 +2080,7 @@ public class FHIRPathEngine {
|
||||||
case Xor: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
case Xor: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
||||||
case Implies : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
case Implies : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
|
||||||
case Times:
|
case Times:
|
||||||
|
checkCardinalityForSingle(left, operation, right, expr);
|
||||||
TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON);
|
TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON);
|
||||||
if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
|
if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
|
||||||
result.addType(TypeDetails.FP_Integer);
|
result.addType(TypeDetails.FP_Integer);
|
||||||
|
@ -2048,6 +2089,7 @@ public class FHIRPathEngine {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
case DivideBy:
|
case DivideBy:
|
||||||
|
checkCardinalityForSingle(left, operation, right, expr);
|
||||||
result = new TypeDetails(CollectionStatus.SINGLETON);
|
result = new TypeDetails(CollectionStatus.SINGLETON);
|
||||||
if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
|
if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
|
||||||
result.addType(TypeDetails.FP_Decimal);
|
result.addType(TypeDetails.FP_Decimal);
|
||||||
|
@ -2056,9 +2098,11 @@ public class FHIRPathEngine {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
case Concatenate:
|
case Concatenate:
|
||||||
|
checkCardinalityForSingle(left, operation, right, expr);
|
||||||
result = new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
|
result = new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
|
||||||
return result;
|
return result;
|
||||||
case Plus:
|
case Plus:
|
||||||
|
checkCardinalityForSingle(left, operation, right, expr);
|
||||||
result = new TypeDetails(CollectionStatus.SINGLETON);
|
result = new TypeDetails(CollectionStatus.SINGLETON);
|
||||||
if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
|
if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
|
||||||
result.addType(TypeDetails.FP_Integer);
|
result.addType(TypeDetails.FP_Integer);
|
||||||
|
@ -2075,6 +2119,7 @@ public class FHIRPathEngine {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
case Minus:
|
case Minus:
|
||||||
|
checkCardinalityForSingle(left, operation, right, expr);
|
||||||
result = new TypeDetails(CollectionStatus.SINGLETON);
|
result = new TypeDetails(CollectionStatus.SINGLETON);
|
||||||
if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
|
if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
|
||||||
result.addType(TypeDetails.FP_Integer);
|
result.addType(TypeDetails.FP_Integer);
|
||||||
|
@ -2092,6 +2137,7 @@ public class FHIRPathEngine {
|
||||||
return result;
|
return result;
|
||||||
case Div:
|
case Div:
|
||||||
case Mod:
|
case Mod:
|
||||||
|
checkCardinalityForSingle(left, operation, right, expr);
|
||||||
result = new TypeDetails(CollectionStatus.SINGLETON);
|
result = new TypeDetails(CollectionStatus.SINGLETON);
|
||||||
if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
|
if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
|
||||||
result.addType(TypeDetails.FP_Integer);
|
result.addType(TypeDetails.FP_Integer);
|
||||||
|
@ -3222,7 +3268,7 @@ public class FHIRPathEngine {
|
||||||
if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) { // special case for start up
|
if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) { // special case for start up
|
||||||
return new TypeDetails(CollectionStatus.SINGLETON, type);
|
return new TypeDetails(CollectionStatus.SINGLETON, type);
|
||||||
}
|
}
|
||||||
TypeDetails result = new TypeDetails(null);
|
TypeDetails result = new TypeDetails(focus.getCollectionStatus());
|
||||||
getChildTypesByName(type, exp.getName(), result, exp, focus, elementDependencies);
|
getChildTypesByName(type, exp.getName(), result, exp, focus, elementDependencies);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -3265,6 +3311,10 @@ public class FHIRPathEngine {
|
||||||
} while (changed);
|
} while (changed);
|
||||||
paramTypes.clear();
|
paramTypes.clear();
|
||||||
paramTypes.add(base);
|
paramTypes.add(base);
|
||||||
|
} else if (exp.getFunction() == Function.Where || exp.getFunction() == Function.Select || exp.getFunction() == Function.Exists ||
|
||||||
|
exp.getFunction() == Function.All || exp.getFunction() == Function.AllTrue || exp.getFunction() == Function.AnyTrue
|
||||||
|
|| exp.getFunction() == Function.AllFalse || exp.getFunction() == Function.AnyFalse) {
|
||||||
|
evaluateParameters(context, focus.toSingleton(), exp, elementDependencies, paramTypes, false);
|
||||||
} else {
|
} else {
|
||||||
evaluateParameters(context, focus, exp, elementDependencies, paramTypes, false);
|
evaluateParameters(context, focus, exp, elementDependencies, paramTypes, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ import org.hl7.fhir.r5.model.ValueSet;
|
||||||
import org.hl7.fhir.r5.utils.FHIRPathEngine.ExpressionNodeWithOffset;
|
import org.hl7.fhir.r5.utils.FHIRPathEngine.ExpressionNodeWithOffset;
|
||||||
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
|
import org.hl7.fhir.r5.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;
|
||||||
|
|
||||||
|
@ -67,17 +68,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +196,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(":")) {
|
||||||
|
@ -195,7 +206,7 @@ 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -305,6 +316,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;
|
||||||
|
@ -343,7 +378,10 @@ public class LiquidEngine implements IEvaluationContext {
|
||||||
if (limit >= 0 && i == limit) {
|
if (limit >= 0 && i == limit) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
lctxt.vars.put(varName, o);
|
if (lctxt.globalVars.containsKey(varName)) {
|
||||||
|
throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_VARIABLE_ALREADY_ASSIGNED, varName));
|
||||||
|
}
|
||||||
|
lctxt.loopVars.put(varName, o);
|
||||||
boolean wantBreak = false;
|
boolean wantBreak = false;
|
||||||
for (LiquidNode n : body) {
|
for (LiquidNode n : body) {
|
||||||
try {
|
try {
|
||||||
|
@ -372,7 +410,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;
|
||||||
|
@ -380,14 +418,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;
|
||||||
|
@ -395,12 +433,12 @@ 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -415,9 +453,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)));
|
||||||
}
|
}
|
||||||
|
@ -474,11 +512,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("Script " + name + ": Script " + name + ": Found " + cnt.charAt(0) + " expecting ',' parsing cycle");
|
throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_EXPECTING, name, cnt.charAt(0), ','));
|
||||||
}
|
}
|
||||||
cnt = cnt.substring(1).trim();
|
cnt = cnt.substring(1).trim();
|
||||||
if (!cnt.startsWith("\"")) {
|
if (!cnt.startsWith("\"")) {
|
||||||
throw new FHIRException("Script " + name + ": Script " + name + ": Found " + cnt.charAt(0) + " expecting '\"' parsing cycle");
|
throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_SYNTAX_EXPECTING, name, cnt.charAt(0), '"'));
|
||||||
}
|
}
|
||||||
cnt = cnt.substring(1);
|
cnt = cnt.substring(1);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
@ -486,7 +524,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();
|
||||||
|
@ -518,8 +556,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("Script " + name + ": Script " + name + ": Unknown flow control statement " + cnt);
|
throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_UNKNOWN_FLOW_STMT,name, cnt));
|
||||||
} else { // next2() == '{'
|
} else { // next2() == '{'
|
||||||
list.add(parseStatement());
|
list.add(parseStatement());
|
||||||
}
|
}
|
||||||
|
@ -533,7 +573,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("Script " + name + ": Script " + name + ": Found end of script looking for " + terminators);
|
throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_UNKNOWN_NOEND, name, terminators));
|
||||||
return close;
|
return close;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,7 +617,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)))
|
||||||
|
@ -587,10 +627,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();
|
||||||
|
@ -607,13 +647,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;
|
||||||
|
@ -625,13 +668,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)) {
|
||||||
|
@ -640,6 +686,20 @@ 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();
|
||||||
|
@ -649,7 +709,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();
|
||||||
|
@ -663,7 +723,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();
|
||||||
|
@ -676,8 +736,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);
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package org.hl7.fhir.r5.formats;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||||
|
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
|
||||||
|
import org.hl7.fhir.r5.model.Parameters;
|
||||||
|
import org.hl7.fhir.r5.test.utils.TestingUtilities;
|
||||||
|
import org.hl7.fhir.utilities.Utilities;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class UnicodeCharacterTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnicodeXml() throws FHIRFormatError, IOException {
|
||||||
|
XmlParser xml = new XmlParser();
|
||||||
|
xml.setOutputStyle(OutputStyle.PRETTY);
|
||||||
|
Parameters p = (Parameters) xml.parse(TestingUtilities.loadTestResource("r5", "unicode-problem.xml"));
|
||||||
|
Assertions.assertEquals("invalid: \u0013, not invalid: \r", p.getParameterFirstRep().getValue().primitiveValue());
|
||||||
|
FileOutputStream o = new FileOutputStream(Utilities.path("[tmp]", "unicode-problem.xml"));
|
||||||
|
xml.compose(o, p);
|
||||||
|
o.close();
|
||||||
|
p = (Parameters) xml.parse(new FileInputStream(Utilities.path("[tmp]", "unicode-problem.xml")));
|
||||||
|
Assertions.assertEquals("invalid: \u0013, not invalid: \r", p.getParameterFirstRep().getValue().primitiveValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnicodeJson() throws FHIRFormatError, IOException {
|
||||||
|
JsonParser json = new JsonParser();
|
||||||
|
json.setOutputStyle(OutputStyle.PRETTY);
|
||||||
|
Parameters p = (Parameters) json.parse(TestingUtilities.loadTestResource("r5", "unicode-problem.json"));
|
||||||
|
Assertions.assertEquals("invalid: \u0013, not invalid: \r", p.getParameterFirstRep().getValue().primitiveValue());
|
||||||
|
FileOutputStream o = new FileOutputStream(Utilities.path("[tmp]", "unicode-problem.json"));
|
||||||
|
json.compose(o, p);
|
||||||
|
o.close();
|
||||||
|
p = (Parameters) json.parse(new FileInputStream(Utilities.path("[tmp]", "unicode-problem.json")));
|
||||||
|
Assertions.assertEquals("invalid: \u0013, not invalid: \r", p.getParameterFirstRep().getValue().primitiveValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -984,7 +984,7 @@ public class Utilities {
|
||||||
else if (isWhitespace(c)) {
|
else if (isWhitespace(c)) {
|
||||||
b.append("\\u"+Utilities.padLeft(Integer.toHexString(c), '0', 4));
|
b.append("\\u"+Utilities.padLeft(Integer.toHexString(c), '0', 4));
|
||||||
} else if (((int) c) < 32)
|
} else if (((int) c) < 32)
|
||||||
b.append("\\u" + Utilities.padLeft(String.valueOf((int) c), '0', 4));
|
b.append("\\u" + Utilities.padLeft(Integer.toHexString(c), '0', 4));
|
||||||
else
|
else
|
||||||
b.append(c);
|
b.append(c);
|
||||||
}
|
}
|
||||||
|
|
|
@ -926,6 +926,28 @@ public class I18nConstants {
|
||||||
public static final String VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED = "VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED";
|
public static final String VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED = "VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED";
|
||||||
public static final String VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED = "VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED";
|
public static final String VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED = "VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED";
|
||||||
public static final String CS_SCT_IPS_NOT_IPS = "CS_SCT_IPS_NOT_IPS";
|
public static final String CS_SCT_IPS_NOT_IPS = "CS_SCT_IPS_NOT_IPS";
|
||||||
|
public static final String UNICODE_XML_BAD_CHARS = "UNICODE_XML_BAD_CHARS";
|
||||||
|
public static final String LIQUID_UNKNOWN_FILTER = "LIQUID_UNKNOWN_FILTER";
|
||||||
|
public static final String LIQUID_UNKNOWN_SYNTAX = "LIQUID_UNKNOWN_SYNTAX";
|
||||||
|
public static final String LIQUID_SYNTAX_EXPECTING = "LIQUID_SYNTAX_EXPECTING";
|
||||||
|
public static final String LIQUID_SYNTAX_UNTERMINATED = "LIQUID_SYNTAX_UNTERMINATED";
|
||||||
|
public static final String LIQUID_UNKNOWN_FLOW_STMT = "LIQUID_UNKNOWN_FLOW_STMT";
|
||||||
|
public static final String LIQUID_UNKNOWN_NOEND = "LIQUID_UNKNOWN_NOEND";
|
||||||
|
public static final String LIQUID_SYNTAX_INCLUDE = "LIQUID_SYNTAX_INCLUDE";
|
||||||
|
public static final String LIQUID_SYNTAX_LOOP = "LIQUID_SYNTAX_LOOP";
|
||||||
|
public static final String LIQUID_SYNTAX_NOTERM = "LIQUID_SYNTAX_NOTERM";
|
||||||
|
public static final String LIQUID_UNKNOWN_NOTERM = "LIQUID_UNKNOWN_NOTERM";
|
||||||
|
public static final String LIQUID_SYNTAX_COLON = "LIQUID_SYNTAX_COLON";
|
||||||
|
public static final String LIQUID_SYNTAX_NUMBER = "LIQUID_SYNTAX_NUMBER";
|
||||||
|
public static final String LIQUID_SYNTAX_UNEXPECTED = "LIQUID_SYNTAX_UNEXPECTED";
|
||||||
|
public static final String LIQUID_VARIABLE_ALREADY_ASSIGNED = "LIQUID_VARIABLE_ALREADY_ASSIGNED";
|
||||||
|
public static final String LIQUID_VARIABLE_ILLEGAL = "LIQUID_VARIABLE_ILLEGAL";
|
||||||
|
public static final String TERMINOLOGY_TX_SYSTEM_NOT_USABLE = "TERMINOLOGY_TX_SYSTEM_NOT_USABLE";
|
||||||
|
public static final String ED_INVARIANT_DIFF_NO_SOURCE = "ED_INVARIANT_DIFF_NO_SOURCE";
|
||||||
|
public static final String FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT = "FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT";
|
||||||
|
public static final String FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT = "FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -462,7 +462,7 @@ public class NpmPackage {
|
||||||
|
|
||||||
while ((entry = (TarArchiveEntry) tarIn.getNextEntry()) != null) {
|
while ((entry = (TarArchiveEntry) tarIn.getNextEntry()) != null) {
|
||||||
String n = entry.getName();
|
String n = entry.getName();
|
||||||
if (n.contains("..")) {
|
if (n.contains("/..") || n.contains("../")) {
|
||||||
throw new RuntimeException("Entry with an illegal name: " + n);
|
throw new RuntimeException("Entry with an illegal name: " + n);
|
||||||
}
|
}
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
|
|
|
@ -184,6 +184,7 @@ Terminology_TX_NoValid_8 = None of the codes provided are in the maximum value s
|
||||||
Terminology_TX_NoValid_9 = The code provided ({2}) could not be validated against the maximum value set {0}, (error = {1})
|
Terminology_TX_NoValid_9 = The code provided ({2}) could not be validated against the maximum value set {0}, (error = {1})
|
||||||
Terminology_TX_System_Invalid = Invalid System URI: {0}
|
Terminology_TX_System_Invalid = Invalid System URI: {0}
|
||||||
Terminology_TX_System_NotKnown = Code System URI ''{0}'' is unknown so the code cannot be validated
|
Terminology_TX_System_NotKnown = Code System URI ''{0}'' is unknown so the code cannot be validated
|
||||||
|
TERMINOLOGY_TX_SYSTEM_NOT_USABLE = The definition for the Code System with URI ''{0}'' doesn't provide any codes so the code cannot be validated
|
||||||
Terminology_TX_System_Relative = Coding.system must be an absolute reference, not a local reference
|
Terminology_TX_System_Relative = Coding.system must be an absolute reference, not a local reference
|
||||||
Terminology_TX_System_Unknown = Unknown Code System ''{0}''
|
Terminology_TX_System_Unknown = Unknown Code System ''{0}''
|
||||||
Terminology_TX_System_ValueSet = Invalid System URI: {0} - cannot use a value set URI as a system
|
Terminology_TX_System_ValueSet = Invalid System URI: {0} - cannot use a value set URI as a system
|
||||||
|
@ -981,3 +982,23 @@ SD_ED_TYPE_WRONG_TYPE_other = The element has a type {0} which is not in the typ
|
||||||
VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED = This include has some concepts with displays and some without - check that this is what is intended
|
VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED = This include has some concepts with displays and some without - check that this is what is intended
|
||||||
VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED = This SNOMED-CT based include has some concepts with semantic tags (FSN terms) and some without (preferred terms) - check that this is what is intended
|
VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED = This SNOMED-CT based include has some concepts with semantic tags (FSN terms) and some without (preferred terms) - check that this is what is intended
|
||||||
CS_SCT_IPS_NOT_IPS = The Snomed CT code {0} ({1}) is not a member of the IPS free set
|
CS_SCT_IPS_NOT_IPS = The Snomed CT code {0} ({1}) is not a member of the IPS free set
|
||||||
|
UNICODE_XML_BAD_CHARS_one = This content includes the character {1} (hex value). This character is illegal in the XML version of FHIR, and there is generally no valid use for such characters
|
||||||
|
UNICODE_XML_BAD_CHARS_other = This content includes the characters {1} (hex values). These characters are illegal in the XML version of FHIR, and there is generally no valid use for such characters
|
||||||
|
LIQUID_UNKNOWN_FILTER = Unknown Liquid filter '''{0}''
|
||||||
|
LIQUID_UNKNOWN_SYNTAX = Unexpected syntax parsing liquid statement
|
||||||
|
LIQUID_SYNTAX_EXPECTING = Script {0}: Found ''{1}'' expecting ''{2}'' parsing cycle
|
||||||
|
LIQUID_SYNTAX_UNTERMINATED = Script {0}: Found unterminated string parsing cycle
|
||||||
|
LIQUID_UNKNOWN_FLOW_STMT = Script {0}: Unknown flow control statement ''{1}''
|
||||||
|
LIQUID_UNKNOWN_NOEND = Script {0}: Found end of script looking for {1}
|
||||||
|
LIQUID_SYNTAX_INCLUDE = Script {0}: Error reading include: {1}
|
||||||
|
LIQUID_SYNTAX_LOOP = Script {0}: Error reading loop: {1}
|
||||||
|
LIQUID_SYNTAX_NOTERM = Script {0}: Unterminated Liquid statement {1}
|
||||||
|
LIQUID_UNKNOWN_NOTERM = Script {0}: Unterminated Liquid statement {1}
|
||||||
|
LIQUID_SYNTAX_COLON = Exception evaluating {0}: limit is not followed by '':''
|
||||||
|
LIQUID_SYNTAX_NUMBER = Exception evaluating {0}: limit is not followed by a number
|
||||||
|
LIQUID_SYNTAX_UNEXPECTED = Exception evaluating {0}: unexpected content at {1}
|
||||||
|
LIQUID_VARIABLE_ALREADY_ASSIGNED = Liquid Exception: The variable ''{0}'' already has an assigned value
|
||||||
|
LIQUID_VARIABLE_ILLEGAL = Liquid Exception: The variable name ''{0}'' cannot be used
|
||||||
|
ED_INVARIANT_DIFF_NO_SOURCE = The invariant {0} defined in the differential must have no source, or the source must be the same as the profile
|
||||||
|
FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT = The left side is inherently a collection, and so the expression ''{0}'' may fail or return false if there is more than one item in the content being evaluated
|
||||||
|
FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT = The right side is inherently a collection, and so this expression ''{0}'' may fail or return false if there is more than one item in the content being evaluated
|
||||||
|
|
|
@ -829,3 +829,6 @@ NO_VALID_DISPLAY_FOUND_other =
|
||||||
PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE_one =
|
PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE_one =
|
||||||
PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE_many =
|
PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE_many =
|
||||||
PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE_other =
|
PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE_other =
|
||||||
|
UNICODE_XML_BAD_CHARS_one =
|
||||||
|
UNICODE_XML_BAD_CHARS_many =
|
||||||
|
UNICODE_XML_BAD_CHARS_other =
|
|
@ -113,6 +113,7 @@ import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation;
|
||||||
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
|
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
|
||||||
import org.hl7.fhir.r5.model.Enumeration;
|
import org.hl7.fhir.r5.model.Enumeration;
|
||||||
import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
|
import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
|
||||||
|
import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
|
||||||
import org.hl7.fhir.r5.model.ExpressionNode;
|
import org.hl7.fhir.r5.model.ExpressionNode;
|
||||||
import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus;
|
import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus;
|
||||||
import org.hl7.fhir.r5.model.Extension;
|
import org.hl7.fhir.r5.model.Extension;
|
||||||
|
@ -1075,7 +1076,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isAllowExamples() || !Utilities.startsWithInList(system, "http://example.org", "https://example.org")) {
|
if (!isAllowExamples() || !Utilities.startsWithInList(system, "http://example.org", "https://example.org")) {
|
||||||
|
CodeSystem cs = context.fetchCodeSystem(system);
|
||||||
|
if (cs == null) {
|
||||||
hint(errors, NO_RULE_DATE, IssueType.UNKNOWN, element.line(), element.col(), path, done, I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, system);
|
hint(errors, NO_RULE_DATE, IssueType.UNKNOWN, element.line(), element.col(), path, done, I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, system);
|
||||||
|
} else {
|
||||||
|
if (hint(errors, NO_RULE_DATE, IssueType.UNKNOWN, element.line(), element.col(), path, cs.getContent() != CodeSystemContentMode.NOTPRESENT, I18nConstants.TERMINOLOGY_TX_SYSTEM_NOT_USABLE, system)) {
|
||||||
|
rule(errors, NO_RULE_DATE, IssueType.UNKNOWN, element.line(), element.col(), path, false, "Error - this should not happen? (Consult GG)");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -2379,6 +2387,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Set<String> badChars = new HashSet<>();
|
||||||
|
for (char ch : e.primitiveValue().toCharArray()) {
|
||||||
|
if (ch < 32 && !(ch == '\r' || ch == '\n' || ch == '\t')) {
|
||||||
|
// can't get to here with xml - the parser fails if you try
|
||||||
|
badChars.add(Integer.toHexString(ch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
warningPlural(errors, "2023-07-26", IssueType.INVALID, e.line(), e.col(), path, badChars.isEmpty(), badChars.size(), I18nConstants.UNICODE_XML_BAD_CHARS, badChars.toString());
|
||||||
}
|
}
|
||||||
String regex = context.getExtensionString(ToolingExtensions.EXT_REGEX);
|
String regex = context.getExtensionString(ToolingExtensions.EXT_REGEX);
|
||||||
// there's a messy history here - this extension snhould only be on the element definition itself, but for historical reasons
|
// there's a messy history here - this extension snhould only be on the element definition itself, but for historical reasons
|
||||||
|
|
|
@ -469,27 +469,26 @@ public class StructureDefinitionValidator extends BaseValidator {
|
||||||
}
|
}
|
||||||
// if we see fixed[x] or pattern[x] applied to a repeating element, we'll give the user a hint
|
// if we see fixed[x] or pattern[x] applied to a repeating element, we'll give the user a hint
|
||||||
}
|
}
|
||||||
if (snapshot) { // we just don't have enough information to figure out the context in a differential
|
|
||||||
List<Element> constraints = element.getChildrenByName("constraint");
|
List<Element> constraints = element.getChildrenByName("constraint");
|
||||||
int cc = 0;
|
int cc = 0;
|
||||||
for (Element invariant : constraints) {
|
for (Element invariant : constraints) {
|
||||||
ok = validateElementDefinitionInvariant(errors, invariant, stack.push(invariant, cc, null, null), invariantMap, elements, element, element.getNamedChildValue("path"), rootPath, profileUrl) && ok;
|
ok = validateElementDefinitionInvariant(errors, invariant, stack.push(invariant, cc, null, null), invariantMap, elements, element, element.getNamedChildValue("path"), rootPath, profileUrl, snapshot) && ok;
|
||||||
cc++;
|
cc++;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean validateElementDefinitionInvariant(List<ValidationMessage> errors, Element invariant, NodeStack stack, Map<String, String> invariantMap, List<Element> elements, Element element, String path, String rootPath, String profileUrl) {
|
private boolean validateElementDefinitionInvariant(List<ValidationMessage> errors, Element invariant, NodeStack stack, Map<String, String> invariantMap, List<Element> elements, Element element, String path, String rootPath, String profileUrl, boolean snapshot) {
|
||||||
boolean ok = true;
|
boolean ok = true;
|
||||||
String key = invariant.getNamedChildValue("key");
|
String key = invariant.getNamedChildValue("key");
|
||||||
String expression = invariant.getNamedChildValue("expression");
|
String expression = invariant.getNamedChildValue("expression");
|
||||||
String source = invariant.getNamedChildValue("source");
|
String source = invariant.getNamedChildValue("source");
|
||||||
if (warning(errors, "2023-06-19", IssueType.INFORMATIONAL, stack, !Utilities.noString(key), I18nConstants.ED_INVARIANT_NO_KEY)) {
|
if (warning(errors, "2023-06-19", IssueType.INFORMATIONAL, stack, !Utilities.noString(key), I18nConstants.ED_INVARIANT_NO_KEY)) {
|
||||||
if (hint(errors, "2023-06-19", IssueType.INFORMATIONAL, stack, !Utilities.noString(expression) || VersionUtilities.isR5Plus(context.getVersion()), I18nConstants.ED_INVARIANT_NO_EXPRESSION, key)) { // not for R5 - there's an invariant
|
if (hint(errors, "2023-06-19", IssueType.INFORMATIONAL, stack, !Utilities.noString(expression) || VersionUtilities.isR5Plus(context.getVersion()), I18nConstants.ED_INVARIANT_NO_EXPRESSION, key)) { // not for R5 - there's an invariant
|
||||||
|
if (snapshot) {// we just don't have enough information to figure out the context in a differential
|
||||||
if (!Utilities.noString(expression)) {
|
if (!Utilities.noString(expression)) {
|
||||||
if (invariantMap.containsKey(key)) {
|
if (invariantMap.containsKey(key)) {
|
||||||
// it's legal - and common - for a list of elemnts to contain the same invariant more than once, but it's not valid if it's not always the same
|
// it's legal - and common - for a list of elements to contain the same invariant more than once, but it's not valid if it's not always the same
|
||||||
ok = rule(errors, "2023-06-19", IssueType.INVALID, stack, expression.equals(invariantMap.get(key)) || "ele-1".equals(key), I18nConstants.ED_INVARIANT_EXPRESSION_CONFLICT, key, expression, invariantMap.get(key));
|
ok = rule(errors, "2023-06-19", IssueType.INVALID, stack, expression.equals(invariantMap.get(key)) || "ele-1".equals(key), I18nConstants.ED_INVARIANT_EXPRESSION_CONFLICT, key, expression, invariantMap.get(key));
|
||||||
} else {
|
} else {
|
||||||
invariantMap.put(key, expression);
|
invariantMap.put(key, expression);
|
||||||
|
@ -512,7 +511,11 @@ public class StructureDefinitionValidator extends BaseValidator {
|
||||||
// we got to the root before finding anything typed
|
// we got to the root before finding anything typed
|
||||||
types.add(elements.get(0).getNamedChildValue("path"));
|
types.add(elements.get(0).getNamedChildValue("path"));
|
||||||
}
|
}
|
||||||
fpe.checkOnTypes(invariant, rootPath, types, fpe.parse(exp));
|
List<String> warnings = new ArrayList<>();
|
||||||
|
fpe.checkOnTypes(invariant, rootPath, types, fpe.parse(exp), warnings);
|
||||||
|
for (String s : warnings) {
|
||||||
|
warning(errors, "2023-07-27", IssueType.BUSINESSRULE, stack, false, key+": "+s);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (debug) {
|
if (debug) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -521,6 +524,9 @@ public class StructureDefinitionValidator extends BaseValidator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ok = rule(errors, "2023-07-27", IssueType.INVALID, stack, source == null || source.equals(profileUrl), I18nConstants.ED_INVARIANT_DIFF_NO_SOURCE, key, source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ok;
|
return ok;
|
||||||
|
@ -552,6 +558,9 @@ public class StructureDefinitionValidator extends BaseValidator {
|
||||||
} else {
|
} else {
|
||||||
for (Element tr : element.getChildrenByName("type")) {
|
for (Element tr : element.getChildrenByName("type")) {
|
||||||
String t = tr.getNamedChildValue("code");
|
String t = tr.getNamedChildValue("code");
|
||||||
|
if (t.startsWith("http://hl7.org/fhirpath")) {
|
||||||
|
t = tr.getExtensionValue("http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type").primitiveValue();
|
||||||
|
}
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
if (isAbstractType(t) && hasChildren(element, elements) ) {
|
if (isAbstractType(t) && hasChildren(element, elements) ) {
|
||||||
types.add(element.getNamedChildValue("path"));
|
types.add(element.getNamedChildValue("path"));
|
||||||
|
|
Loading…
Reference in New Issue