fixes to validator for FHIRPath engine fixes

This commit is contained in:
Grahame Grieve 2022-07-28 08:48:33 +10:00
parent 5c76af8dcd
commit cc35c23c7d
3 changed files with 231 additions and 149 deletions

View File

@ -261,6 +261,7 @@ public class FHIRPathEngine {
private ProfileUtilities profileUtilities;
private String location; // for error messages
private boolean allowPolymorphicNames;
private boolean doImplicitStringConversion;
// 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
@ -446,6 +447,14 @@ public class FHIRPathEngine {
}
public boolean isDoImplicitStringConversion() {
return doImplicitStringConversion;
}
public void setDoImplicitStringConversion(boolean doImplicitStringConversion) {
this.doImplicitStringConversion = doImplicitStringConversion;
}
// --- public API -------------------------------------------------------
/**
* Parse a path for later use using execute
@ -2893,6 +2902,8 @@ public class FHIRPathEngine {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity);
} else if (constant instanceof FHIRConstant) {
return resolveConstantType(context, ((FHIRConstant) constant).getValue(), expr);
} else if (constant == null) {
return new TypeDetails(CollectionStatus.SINGLETON);
} else {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
}
@ -3130,60 +3141,60 @@ public class FHIRPathEngine {
return types;
}
case Lower : {
checkContextString(focus, "lower", exp);
checkContextString(focus, "lower", exp, true);
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
}
case Upper : {
checkContextString(focus, "upper", exp);
checkContextString(focus, "upper", exp, true);
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
}
case ToChars : {
checkContextString(focus, "toChars", exp);
checkContextString(focus, "toChars", exp, true);
return new TypeDetails(CollectionStatus.ORDERED, TypeDetails.FP_String);
}
case IndexOf : {
checkContextString(focus, "indexOf", exp);
checkContextString(focus, "indexOf", exp, true);
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
}
case Substring : {
checkContextString(focus, "subString", exp);
checkContextString(focus, "subString", exp, true);
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer));
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
}
case StartsWith : {
checkContextString(focus, "startsWith", exp);
checkContextString(focus, "startsWith", exp, true);
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
}
case EndsWith : {
checkContextString(focus, "endsWith", exp);
checkContextString(focus, "endsWith", exp, true);
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
}
case Matches : {
checkContextString(focus, "matches", exp);
checkContextString(focus, "matches", exp, true);
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
}
case MatchesFull : {
checkContextString(focus, "matches", exp);
checkContextString(focus, "matches", exp, true);
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
}
case ReplaceMatches : {
checkContextString(focus, "replaceMatches", exp);
checkContextString(focus, "replaceMatches", exp, true);
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
}
case Contains : {
checkContextString(focus, "contains", exp);
checkContextString(focus, "contains", exp, true);
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
}
case Replace : {
checkContextString(focus, "replace", exp);
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
checkContextString(focus, "replace", exp, true);
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
}
case Length : {
@ -3417,14 +3428,15 @@ public class FHIRPathEngine {
}
private void checkContextString(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id")) {
throw makeException(expr, I18nConstants.FHIRPATH_STRING_ONLY, name, focus.describe());
private void checkContextString(TypeDetails focus, String name, ExpressionNode expr, boolean sing) throws PathEngineException {
if (!focus.hasNoTypes() && !focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id")) {
throw makeException(expr, sing ? I18nConstants.FHIRPATH_STRING_SING_ONLY : I18nConstants.FHIRPATH_STRING_ORD_ONLY, name, focus.describe());
}
}
private void checkContextPrimitive(TypeDetails focus, String name, boolean canQty, ExpressionNode expr) throws PathEngineException {
if (!focus.hasNoTypes()) {
if (canQty) {
if (!focus.hasType(primitiveTypes) && !focus.hasType("Quantity")) {
throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), "Quantity, "+primitiveTypes.toString());
@ -3433,21 +3445,22 @@ public class FHIRPathEngine {
throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), primitiveTypes.toString());
}
}
}
private void checkContextNumerical(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
if (!focus.hasType("integer") && !focus.hasType("decimal") && !focus.hasType("Quantity")) {
if (!focus.hasNoTypes() && !focus.hasType("integer") && !focus.hasType("decimal") && !focus.hasType("Quantity")) {
throw makeException(expr, I18nConstants.FHIRPATH_NUMERICAL_ONLY, name, focus.describe());
}
}
private void checkContextDecimal(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
if (!focus.hasType("decimal") && !focus.hasType("integer")) {
if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("integer")) {
throw makeException(expr, I18nConstants.FHIRPATH_DECIMAL_ONLY, name, focus.describe());
}
}
private void checkContextContinuous(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
if (!focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") && !focus.hasType("time")) {
if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") && !focus.hasType("time")) {
throw makeException(expr, I18nConstants.FHIRPATH_CONTINUOUS_ONLY, name, focus.describe());
}
}
@ -3662,7 +3675,10 @@ public class FHIRPathEngine {
private List<Base> funcExp(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
if (focus.size() != 1) {
if (focus.size() == 0) {
return new ArrayList<Base>();
}
if (focus.size() > 1) {
throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "exp", focus.size());
}
Base base = focus.get(0);
@ -4208,17 +4224,23 @@ public class FHIRPathEngine {
private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException, PathEngineException {
List<Base> result = new ArrayList<Base>();
List<Base> tB = execute(context, focus, expr.getParameters().get(0), true);
String t = convertToString(tB);
List<Base> rB = execute(context, focus, expr.getParameters().get(1), true);
String r = convertToString(rB);
if (focus.size() == 1) {
if (focus.size() == 0 || tB.size() == 0 || rB.size() == 0) {
//
} else if (focus.size() == 1) {
if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
String f = convertToString(focus.get(0));
if (Utilities.noString(f)) {
result.add(new StringType(""));
} else {
String t = convertToString(execute(context, focus, expr.getParameters().get(0), true));
String r = convertToString(execute(context, focus, expr.getParameters().get(1), true));
String n = f.replace(t, r);
result.add(new StringType(n));
}
}
} else {
throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "replace", focus.size());
}
@ -4228,11 +4250,17 @@ public class FHIRPathEngine {
private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> result = new ArrayList<Base>();
String regex = convertToString(execute(context, focus, exp.getParameters().get(0), true));
String repl = convertToString(execute(context, focus, exp.getParameters().get(1), true));
List<Base> regexB = execute(context, focus, exp.getParameters().get(0), true);
String regex = convertToString(regexB);
List<Base> replB = execute(context, focus, exp.getParameters().get(1), true);
String repl = convertToString(replB);
if (focus.size() == 1 && !Utilities.noString(regex)) {
if (focus.size() == 0 || regexB.size() == 0 || replB.size() == 0) {
//
} else if (focus.size() == 1 && !Utilities.noString(regex)) {
if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)).noExtensions());
}
} else {
result.add(new StringType(convertToString(focus.get(0))).noExtensions());
}
@ -4242,13 +4270,16 @@ public class FHIRPathEngine {
private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> result = new ArrayList<Base>();
String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
String sw = convertToString(swb);
if (focus.size() == 0) {
result.add(new BooleanType(false).noExtensions());
//
} else if (swb.size() == 0) {
//
} else if (Utilities.noString(sw)) {
result.add(new BooleanType(true).noExtensions());
} else {
} else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
if (focus.size() == 1 && !Utilities.noString(sw)) {
result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)).noExtensions());
} else {
@ -4915,9 +4946,13 @@ public class FHIRPathEngine {
private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> result = new ArrayList<Base>();
String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
String sw = convertToString(swb);
if (focus.size() == 1 && !Utilities.noString(sw)) {
if (focus.size() == 0 || swb.size() == 0) {
//
} else if (focus.size() == 1 && !Utilities.noString(sw)) {
if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
String st = convertToString(focus.get(0));
if (Utilities.noString(st)) {
result.add(new BooleanType(false).noExtensions());
@ -4927,6 +4962,7 @@ public class FHIRPathEngine {
boolean ok = m.find();
result.add(new BooleanType(ok).noExtensions());
}
}
} else {
result.add(new BooleanType(false).noExtensions());
}
@ -4938,6 +4974,7 @@ public class FHIRPathEngine {
String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
if (focus.size() == 1 && !Utilities.noString(sw)) {
if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
String st = convertToString(focus.get(0));
if (Utilities.noString(st)) {
result.add(new BooleanType(false).noExtensions());
@ -4947,6 +4984,7 @@ public class FHIRPathEngine {
boolean ok = m.matches();
result.add(new BooleanType(ok).noExtensions());
}
}
} else {
result.add(new BooleanType(false).noExtensions());
}
@ -4955,13 +4993,16 @@ public class FHIRPathEngine {
private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> result = new ArrayList<Base>();
String sw = convertToString(execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true));
List<Base> swb = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true);
String sw = convertToString(swb);
if (focus.size() != 1) {
result.add(new BooleanType(false).noExtensions());
//
} else if (swb.size() != 1) {
//
} else if (Utilities.noString(sw)) {
result.add(new BooleanType(true).noExtensions());
} else {
} else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
String st = convertToString(focus.get(0));
if (Utilities.noString(st)) {
result.add(new BooleanType(false).noExtensions());
@ -4980,7 +5021,7 @@ public class FHIRPathEngine {
private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
List<Base> result = new ArrayList<Base>();
if (focus.size() == 1) {
if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
String s = convertToString(focus.get(0));
result.add(new IntegerType(s.length()).noExtensions());
}
@ -5000,13 +5041,16 @@ public class FHIRPathEngine {
private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> result = new ArrayList<Base>();
String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
String sw = convertToString(swb);
if (focus.size() == 0) {
result.add(new BooleanType(false).noExtensions());
// no result
} else if (swb.size() == 0) {
// no result
} else if (Utilities.noString(sw)) {
result.add(new BooleanType(true).noExtensions());
} else {
} else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
String s = convertToString(focus.get(0));
if (s == null) {
result.add(new BooleanType(false).noExtensions());
@ -5019,7 +5063,7 @@ public class FHIRPathEngine {
private List<Base> funcLower(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> result = new ArrayList<Base>();
if (focus.size() == 1) {
if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
String s = convertToString(focus.get(0));
if (!Utilities.noString(s)) {
result.add(new StringType(s.toLowerCase()).noExtensions());
@ -5030,7 +5074,7 @@ public class FHIRPathEngine {
private List<Base> funcUpper(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> result = new ArrayList<Base>();
if (focus.size() == 1) {
if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
String s = convertToString(focus.get(0));
if (!Utilities.noString(s)) {
result.add(new StringType(s.toUpperCase()).noExtensions());
@ -5041,7 +5085,7 @@ public class FHIRPathEngine {
private List<Base> funcToChars(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> result = new ArrayList<Base>();
if (focus.size() == 1) {
if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
String s = convertToString(focus.get(0));
for (char c : s.toCharArray()) {
result.add(new StringType(String.valueOf(c)).noExtensions());
@ -5053,12 +5097,15 @@ public class FHIRPathEngine {
private List<Base> funcIndexOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> result = new ArrayList<Base>();
String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
String sw = convertToString(swb);
if (focus.size() == 0) {
result.add(new IntegerType(0).noExtensions());
// no result
} else if (swb.size() == 0) {
// no result
} else if (Utilities.noString(sw)) {
result.add(new IntegerType(0).noExtensions());
} else {
} else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
String s = convertToString(focus.get(0));
if (s == null) {
result.add(new IntegerType(0).noExtensions());
@ -5076,10 +5123,13 @@ public class FHIRPathEngine {
int i2 = -1;
if (exp.parameterCount() == 2) {
List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true);
if (n2.isEmpty()|| !n2.get(0).isPrimitive() || !Utilities.isInteger(n2.get(0).primitiveValue())) {
return new ArrayList<Base>();
}
i2 = Integer.parseInt(n2.get(0).primitiveValue());
}
if (focus.size() == 1) {
if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
String sw = convertToString(focus.get(0));
String s;
if (i1 < 0 || i1 >= sw.length()) {

View File

@ -171,6 +171,7 @@ import org.hl7.fhir.validation.instance.type.StructureDefinitionValidator;
import org.hl7.fhir.validation.instance.type.ValueSetValidator;
import org.hl7.fhir.validation.instance.utils.ChildIterator;
import org.hl7.fhir.validation.instance.utils.ElementInfo;
import org.hl7.fhir.validation.instance.utils.FHIRPathExpressionFixer;
import org.hl7.fhir.validation.instance.utils.IndexedElement;
import org.hl7.fhir.validation.instance.utils.NodeStack;
import org.hl7.fhir.validation.instance.utils.ResolvedReference;
@ -210,8 +211,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
public CanonicalResourceLookupResult(String error) {
this.error = error;
}
}
private static final String EXECUTED_CONSTRAINT_LIST = "validator.executed.invariant.list";
private static final String EXECUTION_ID = "validator.execution.id";
private static final String HTML_FRAGMENT_REGEX = "[a-zA-Z]\\w*(((\\s+)(\\S)*)*)";
@ -3461,7 +3462,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
TypedElementDefinition ted = null;
String fp = fixExpr(discriminator, null);
String fp = FHIRPathExpressionFixer.fixExpr(discriminator, null);
ExpressionNode expr = null;
try {
expr = fpe.parse(fp);
@ -4068,7 +4069,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
try {
n = fpe.parse(fixExpr(expression.toString(), null));
n = fpe.parse(FHIRPathExpressionFixer.fixExpr(expression.toString(), null));
} catch (FHIRLexerException e) {
if (STACK_TRACE) e.printStackTrace();
throw new FHIRException(context.formatMessage(I18nConstants.PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__, expression, profile.getUrl(), path, e.getMessage()));
@ -5590,7 +5591,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
List<ValidationMessage> invErrors = null;
// We key based on inv.expression rather than inv.key because expressions can change in derived profiles and aren't guaranteed to be consistent across profiles.
String key = fixExpr(inv.getExpression(), inv.getKey());
String key = FHIRPathExpressionFixer.fixExpr(inv.getExpression(), inv.getKey());
if (!invMap.keySet().contains(key)) {
invErrors = new ArrayList<ValidationMessage>();
invMap.put(key, invErrors);
@ -5643,7 +5644,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (n == null) {
long t = System.nanoTime();
try {
n = fpe.parse(fixExpr(inv.getExpression(), inv.getKey()));
n = fpe.parse(FHIRPathExpressionFixer.fixExpr(inv.getExpression(), inv.getKey()));
} catch (FHIRLexerException e) {
rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, false, I18nConstants.PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__, inv.getExpression(), profile.getUrl(), path, e.getMessage());
return;
@ -5665,25 +5666,26 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ex.printStackTrace();
}
if (!ok) {
if (!Utilities.noString(msg)) {
msg = "'" + inv.getHuman()+"' (" + msg + ")";
} else if (wantInvariantInMessage) {
msg = "'" + inv.getHuman()+"' [" + n.toString() + "]";
} else {
msg = context.formatMessage(I18nConstants.INV_FAILED, "'" + inv.getHuman()+"'");
if (wantInvariantInMessage) {
msg = msg + " (inv = " + n.toString() + ")";
}
if (!Utilities.noString(msg)) {
msg = msg + " (log: " + msg + ")";
}
msg = context.formatMessage(I18nConstants.INV_FAILED, inv.getKey() + ": '" + inv.getHuman()+"'")+msg;
if (inv.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice") &&
ToolingExtensions.readBooleanExtension(inv, "http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice")) {
if (bpWarnings == BestPracticeWarningLevel.Hint)
hint(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": " + msg);
hint(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg);
else if (bpWarnings == BestPracticeWarningLevel.Warning)
warning(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg);
warning(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg);
else if (bpWarnings == BestPracticeWarningLevel.Error)
rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg);
rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg);
} else if (inv.getSeverity() == ConstraintSeverity.ERROR) {
rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg);
rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg);
} else if (inv.getSeverity() == ConstraintSeverity.WARNING) {
warning(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg);
warning(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg);
}
}
}
@ -5838,7 +5840,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
try {
ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache");
if (n == null) {
n = fpe.parse(fixExpr(inv.getExpression(), inv.getKey()));
n = fpe.parse(FHIRPathExpressionFixer.fixExpr(inv.getExpression(), inv.getKey()));
inv.setUserData("validator.expression.cache", n);
}
fpe.check(null, sd.getKind() == StructureDefinitionKind.RESOURCE ? sd.getType() : "DomainResource", ed.getPath(), n);
@ -5852,93 +5854,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
private String fixExpr(String expr, String key) {
// this is a hack work around for past publication of wrong FHIRPath expressions
// R4
// waiting for 4.0.2
//TODO is this expression below correct? @grahamegrieve
if ("probability is decimal implies (probability as decimal) <= 100".equals(expr)) {
return "probability.empty() or ((probability is decimal) implies ((probability as decimal) <= 100))";
}
if ("enableWhen.count() > 2 implies enableBehavior.exists()".equals(expr)) {
return "enableWhen.count() >= 2 implies enableBehavior.exists()";
}
if ("txt-2".equals(key)) {
return "htmlChecks2()";
}
// clarification in FHIRPath spec
if (expr.equals("name.matches('[A-Z]([A-Za-z0-9_]){0,254}')")) {
return "name.matches('^[A-Z]([A-Za-z0-9_]){0,254}$')";
}
if ("eld-19".equals(key)) {
return "path.matches('^[^\\\\s\\\\.,:;\\\\\\'\"\\\\/|?!@#$%&*()\\\\[\\\\]{}]{1,64}(\\\\.[^\\\\s\\\\.,:;\\\\\\'\"\\\\/|?!@#$%&*()\\\\[\\\\]{}]{1,64}(\\\\[x\\\\])?(\\\\:[^\\\\s\\\\.]+)?)*$')";
}
if ("eld-20".equals(key)) {
return "path.matches('^[A-Za-z][A-Za-z0-9]*(\\\\.[a-z][A-Za-z0-9]*(\\\\[x])?)*$')";
}
// handled in 4.0.1
if ("(component.empty() and hasMember.empty()) implies (dataAbsentReason or value)".equals(expr)) {
return "(component.empty() and hasMember.empty()) implies (dataAbsentReason.exists() or value.exists())";
}
if ("isModifier implies isModifierReason.exists()".equals(expr)) {
return "(isModifier.exists() and isModifier) implies isModifierReason.exists()";
}
if ("(%resource.kind = 'logical' or element.first().path.startsWith(%resource.type)) and (element.tail().not() or element.tail().all(path.startsWith(%resource.differential.element.first().path.replaceMatches('\\\\..*','')&'.')))".equals(expr)) {
return "(%resource.kind = 'logical' or element.first().path.startsWith(%resource.type)) and (element.tail().empty() or element.tail().all(path.startsWith(%resource.differential.element.first().path.replaceMatches('\\\\..*','')&'.')))";
}
if ("differential.element.all(id) and differential.element.id.trace('ids').isDistinct()".equals(expr)) {
return "differential.element.all(id.exists()) and differential.element.id.trace('ids').isDistinct()";
}
if ("snapshot.element.all(id) and snapshot.element.id.trace('ids').isDistinct()".equals(expr)) {
return "snapshot.element.all(id.exists()) and snapshot.element.id.trace('ids').isDistinct()";
}
// R3
if ("(code or value.empty()) and (system.empty() or system = 'urn:iso:std:iso:4217')".equals(expr)) {
return "(code.exists() or value.empty()) and (system.empty() or system = 'urn:iso:std:iso:4217')";
}
if ("value.empty() or code!=component.code".equals(expr)) {
return "value.empty() or (code in component.code).not()";
}
if ("(code or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)".equals(expr)) {
return "(code.exists() or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)";
}
if ("element.all(definition and min and max)".equals(expr)) {
return "element.all(definition.exists() and min.exists() and max.exists())";
}
if ("telecom or endpoint".equals(expr)) {
return "telecom.exists() or endpoint.exists()";
}
if ("(code or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)".equals(expr)) {
return "(code.exists() or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)";
}
if ("searchType implies type = 'string'".equals(expr)) {
return "searchType.exists() implies type = 'string'";
}
if ("abatement.empty() or (abatement as boolean).not() or clinicalStatus='resolved' or clinicalStatus='remission' or clinicalStatus='inactive'".equals(expr)) {
return "abatement.empty() or (abatement is boolean).not() or (abatement as boolean).not() or (clinicalStatus = 'resolved') or (clinicalStatus = 'remission') or (clinicalStatus = 'inactive')";
}
if ("(component.empty() and related.empty()) implies (dataAbsentReason or value)".equals(expr)) {
return "(component.empty() and related.empty()) implies (dataAbsentReason.exists() or value.exists())";
}
if ("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))".equals(expr)) {
return "(reference = '#') or reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))";
}
if ("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %resource.contained.id.trace('ids'))".equals(expr)) {
return "(reference = '#') or reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))";
}
if ("probability is decimal implies probability.as(decimal) <= 100".equals(expr)) {
if (key.equals("ras-2")) {
return "probability.empty() or (probability is decimal implies probability.as(decimal) <= 100)";
}
}
if ("".equals(expr)) {
return "";
}
return expr;
}
public IEvaluationContext getExternalHostServices() {
return externalHostServices;

View File

@ -0,0 +1,117 @@
package org.hl7.fhir.validation.instance.utils;
import org.hl7.fhir.utilities.Utilities;
public class FHIRPathExpressionFixer {
public static String fixExpr(String expr, String key) {
// this is a hack work around for past publication of wrong FHIRPath expressions
// R4
// waiting for 4.0.2
//TODO is this expression below correct? @grahamegrieve
if ("probability is decimal implies (probability as decimal) <= 100".equals(expr)) {
return "probability.empty() or ((probability is decimal) implies ((probability as decimal) <= 100))";
}
if ("enableWhen.count() > 2 implies enableBehavior.exists()".equals(expr)) {
return "enableWhen.count() >= 2 implies enableBehavior.exists()";
}
if ("txt-2".equals(key)) {
return "htmlChecks2()";
}
// fixes to string functions in FHIRPath
// ref-1
if (expr.equals("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource)")) { // R5
return "reference.exists() implies ("+expr+")";
}
if (expr.equals("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))")) { // R4/R4B
return "reference.exists() implies (reference = '#' or ("+expr+"))";
}
if (expr.equals("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %resource.contained.id.trace('ids'))")) { // STU3
return "reference.exists() implies (reference = '#' or (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))))";
}
// bld-8
if (expr.equals("fullUrl.contains('/_history/').not()")) { // R4
return "fullUrl.exists() implies fullUrl.contains('/_history/').not()";
}
if (expr.equals("name.matches('[A-Z]([A-Za-z0-9_]){0,254}')")) {
return "name.exists() implies name.matches('^[A-Z]([A-Za-z0-9_]){0,254}$')";
}
// canonical
if (expr.equals("name.matches('[A-Z]([A-Za-z0-9_]){0,254}')")) {
return ("name.exists() implies name.matches('[A-Z]([A-Za-z0-9_]){0,254}')");
}
// clarification in FHIRPath spec
if ("eld-19".equals(key)) {
return "path.matches('^[^\\\\s\\\\.,:;\\\\\\'\"\\\\/|?!@#$%&*()\\\\[\\\\]{}]{1,64}(\\\\.[^\\\\s\\\\.,:;\\\\\\'\"\\\\/|?!@#$%&*()\\\\[\\\\]{}]{1,64}(\\\\[x\\\\])?(\\\\:[^\\\\s\\\\.]+)?)*$')";
}
if ("eld-20".equals(key)) {
return "path.matches('^[A-Za-z][A-Za-z0-9]*(\\\\.[a-z][A-Za-z0-9]*(\\\\[x])?)*$')";
}
// handled in 4.0.1
if ("(component.empty() and hasMember.empty()) implies (dataAbsentReason or value)".equals(expr)) {
return "(component.empty() and hasMember.empty()) implies (dataAbsentReason.exists() or value.exists())";
}
if ("isModifier implies isModifierReason.exists()".equals(expr)) {
return "(isModifier.exists() and isModifier) implies isModifierReason.exists()";
}
if ("(%resource.kind = 'logical' or element.first().path.startsWith(%resource.type)) and (element.tail().not() or element.tail().all(path.startsWith(%resource.differential.element.first().path.replaceMatches('\\\\..*','')&'.')))".equals(expr)) {
return "(%resource.kind = 'logical' or element.first().path.startsWith(%resource.type)) and (element.tail().empty() or element.tail().all(path.startsWith(%resource.differential.element.first().path.replaceMatches('\\\\..*','')&'.')))";
}
if ("differential.element.all(id) and differential.element.id.trace('ids').isDistinct()".equals(expr)) {
return "differential.element.all(id.exists()) and differential.element.id.trace('ids').isDistinct()";
}
if ("snapshot.element.all(id) and snapshot.element.id.trace('ids').isDistinct()".equals(expr)) {
return "snapshot.element.all(id.exists()) and snapshot.element.id.trace('ids').isDistinct()";
}
// R3
if ("(code or value.empty()) and (system.empty() or system = 'urn:iso:std:iso:4217')".equals(expr)) {
return "(code.exists() or value.empty()) and (system.empty() or system = 'urn:iso:std:iso:4217')";
}
if ("value.empty() or code!=component.code".equals(expr)) {
return "value.empty() or (code in component.code).not()";
}
if ("(code or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)".equals(expr)) {
return "(code.exists() or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)";
}
if ("element.all(definition and min and max)".equals(expr)) {
return "element.all(definition.exists() and min.exists() and max.exists())";
}
if ("telecom or endpoint".equals(expr)) {
return "telecom.exists() or endpoint.exists()";
}
if ("(code or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)".equals(expr)) {
return "(code.exists() or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)";
}
if ("searchType implies type = 'string'".equals(expr)) {
return "searchType.exists() implies type = 'string'";
}
if ("abatement.empty() or (abatement as boolean).not() or clinicalStatus='resolved' or clinicalStatus='remission' or clinicalStatus='inactive'".equals(expr)) {
return "abatement.empty() or (abatement is boolean).not() or (abatement as boolean).not() or (clinicalStatus = 'resolved') or (clinicalStatus = 'remission') or (clinicalStatus = 'inactive')";
}
if ("(component.empty() and related.empty()) implies (dataAbsentReason or value)".equals(expr)) {
return "(component.empty() and related.empty()) implies (dataAbsentReason.exists() or value.exists())";
}
if ("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))".equals(expr)) {
return "(reference = '#') or reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))";
}
if ("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %resource.contained.id.trace('ids'))".equals(expr)) {
return "(reference = '#') or reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))";
}
if ("probability is decimal implies probability.as(decimal) <= 100".equals(expr)) {
if (key.equals("ras-2")) {
return "probability.empty() or (probability is decimal implies probability.as(decimal) <= 100)";
}
}
if ("".equals(expr)) {
return "";
}
return expr;
}
}