Merge pull request #886 from hapifhir/gg-202207-fhirpath

Gg 202207 fhirpath
This commit is contained in:
Grahame Grieve 2022-07-28 09:14:17 +10:00 committed by GitHub
commit f3fa8cae24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 310 additions and 195 deletions

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.convertors.misc;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r5.model.ValueSet;
@ -36,7 +37,7 @@ public class PhinVadsImporter extends OIDBasedValueSetImporter {
System.out.println("Process " + f.getName());
ValueSet vs = importValueSet(TextFile.fileToBytes(f));
if (vs.getId() != null) {
new JsonParser().compose(new FileOutputStream(Utilities.path(dest, "ValueSet-" + vs.getId() + ".json")), vs);
new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "ValueSet-" + vs.getId() + ".json")), vs);
}
} catch (Exception e) {
e.printStackTrace();

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

@ -2903,6 +2903,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);
}
@ -3140,60 +3142,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 : {
@ -3427,14 +3429,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());
@ -3443,21 +3446,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());
}
}
@ -3672,7 +3676,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);
@ -4218,15 +4225,19 @@ 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));
}
@ -4240,10 +4251,14 @@ 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());
}
@ -4256,10 +4271,13 @@ 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 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
@ -4929,9 +4947,12 @@ 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)) {
@ -4973,10 +4994,13 @@ 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 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
@ -5018,10 +5042,13 @@ 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 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
@ -5071,9 +5098,12 @@ 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 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {

View File

@ -170,6 +170,6 @@ public class TypesUtilities {
}
public static boolean isPrimitive(String code) {
return Utilities.existsInList(code, "boolean", "integer", "integer64", "string", "decimal", "uri", "url", "canonical", "base64Binary", "instant", "date", "dateTime", "time", "code", "oid", "id", "markdown", "unsignedInt", "positiveInt", "xhtml");
return Utilities.existsInList(code, "boolean", "integer", "integer64", "string", "decimal", "uri", "url", "canonical", "base64Binary", "instant", "date", "dateTime", "time", "code", "oid", "id", "uuid", "markdown", "unsignedInt", "positiveInt", "xhtml");
}
}

View File

@ -173,7 +173,8 @@ public class I18nConstants {
public static final String FHIRPATH_RESOLVE_DISCRIMINATOR_NO_TARGET = "FHIRPATH_RESOLVE_DISCRIMINATOR_NO_TARGET";
public static final String FHIRPATH_RIGHT_VALUE_PLURAL = "FHIRPATH_RIGHT_VALUE_PLURAL";
public static final String FHIRPATH_RIGHT_VALUE_WRONG_TYPE = "FHIRPATH_RIGHT_VALUE_WRONG_TYPE";
public static final String FHIRPATH_STRING_ONLY = "FHIRPATH_STRING_ONLY";
public static final String FHIRPATH_STRING_ORD_ONLY = "FHIRPATH_STRING_ORD_ONLY";
public static final String FHIRPATH_STRING_SING_ONLY = "FHIRPATH_STRING_SING_ONLY";
public static final String FHIRPATH_UNABLE_BOOLEAN = "FHIRPATH_UNABLE_BOOLEAN";
public static final String FHIRPATH_UNKNOWN_CONSTANT = "FHIRPATH_UNKNOWN_CONSTANT";
public static final String FHIRPATH_UNKNOWN_CONTEXT = "FHIRPATH_UNKNOWN_CONTEXT";

View File

@ -244,7 +244,7 @@ public class PackageClient {
public void findDependents(Set<String> list, String id) {
CommaSeparatedStringBuilder params = new CommaSeparatedStringBuilder("&");
params.append("dependency="+id);
params.append("dependency="+id.replace("#", "|"));
try {
JsonArray json = fetchJsonArray(Utilities.pathURL(address, "catalog?")+params.toString());
for (JsonElement e : json) {

View File

@ -557,7 +557,8 @@ FHIRPATH_WRONG_PARAM_TYPE = Error evaluating FHIRPath expression: The parameter
FHIRPATH_ORDERED_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on ordered collections
FHIRPATH_REFERENCE_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on ordered string, uri, canonical or Reference but found {1}
FHIRPATH_CODED_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on ordered string, code, uri, Coding, CodeableConcept but found {1}
FHIRPATH_STRING_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on ordered string, uri, code, id but found {1}
FHIRPATH_STRING_ORD_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on ordered collection of string, uri, code, id but found {1}
FHIRPATH_STRING_SING_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on string, uri, code, id but found {1}
FHIRPATH_NO_COLLECTION = Error evaluating FHIRPath expression: The function {0} can only be used on a singleton value but found {1}
FHIRPATH_NOT_IMPLEMENTED = Error evaluating FHIRPath expression: The function {0} is not implemented
FHIRPATH_PARAM_WRONG = Error evaluating FHIRPath expression: The expression type {0} is not supported for parameter {1} on function {2}

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;
}
}