diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/ExpressionNode.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/ExpressionNode.java
index c1850437c..1571c7407 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/ExpressionNode.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/ExpressionNode.java
@@ -51,7 +51,7 @@ public class ExpressionNode {
Empty, Not, Exists, SubsetOf, SupersetOf, IsDistinct, Distinct, Count, Where, Select, All, Repeat, Aggregate, Item /*implicit from name[]*/, As, Is, Single,
First, Last, Tail, Skip, Take, Union, Combine, Intersect, Exclude, Iif, Upper, Lower, ToChars, IndexOf, Substring, StartsWith, EndsWith, Matches, MatchesFull, ReplaceMatches, Contains, Replace, Length,
- Children, Descendants, MemberOf, Trace, Check, Today, Now, Resolve, Extension, AllFalse, AnyFalse, AllTrue, AnyTrue,
+ Children, Descendants, MemberOf, Trace, DefineVariable, Check, Today, Now, Resolve, Extension, AllFalse, AnyFalse, AllTrue, AnyTrue,
HasValue, OfType, Type, ConvertsToBoolean, ConvertsToInteger, ConvertsToString, ConvertsToDecimal, ConvertsToQuantity, ConvertsToDateTime, ConvertsToDate, ConvertsToTime, ToBoolean, ToInteger, ToString, ToDecimal, ToQuantity, ToDateTime, ToTime, ConformsTo,
Round, Sqrt, Abs, Ceiling, Exp, Floor, Ln, Log, Power, Truncate,
@@ -106,6 +106,7 @@ public class ExpressionNode {
if (name.equals("descendants")) return Function.Descendants;
if (name.equals("memberOf")) return Function.MemberOf;
if (name.equals("trace")) return Function.Trace;
+ if (name.equals("defineVariable")) return Function.DefineVariable;
if (name.equals("check")) return Function.Check;
if (name.equals("today")) return Function.Today;
if (name.equals("now")) return Function.Now;
@@ -211,6 +212,7 @@ public class ExpressionNode {
case Descendants : return "descendants";
case MemberOf : return "memberOf";
case Trace : return "trace";
+ case DefineVariable : return "defineVariable";
case Check : return "check";
case Today : return "today";
case Now : return "now";
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java
index e176a84ae..28506b2d3 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java
@@ -193,6 +193,7 @@ public class FHIRPathEngine {
/**
* A constant reference - e.g. a reference to a name that must be resolved in context.
* The % will be removed from the constant name before this is invoked.
+ * Variables created with defineVariable will not be processed by resolveConstant (or resolveConstantType)
*
* This will also be called if the host invokes the FluentPath engine with a context of null
*
@@ -1010,6 +1011,7 @@ public class FHIRPathEngine {
private List total;
private Map aliases;
private int index;
+ private Map> definedVariables;
public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Map aliases, Base thisItem) {
this.appInfo = appInfo;
@@ -1058,6 +1060,28 @@ public class FHIRPathEngine {
index = i;
return this;
}
+
+ public boolean hasDefinedVariable(String name) {
+ return definedVariables != null && definedVariables.containsKey(name);
+ }
+ public List getDefinedVariable(String name) {
+ return definedVariables == null ? makeNull() : definedVariables.get(name);
+ }
+ public void setDefinedVariable(String name, List value) {
+ if (isSystemVariable(name))
+ throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_REDEFINE_VARIABLE, name), I18nConstants.FHIRPATH_REDEFINE_VARIABLE);
+
+ if (definedVariables == null) {
+ definedVariables = new HashMap>();
+ } else {
+ if (definedVariables.containsKey(name)) {
+ // Can't do this, so throw an error
+ throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_REDEFINE_VARIABLE, name), I18nConstants.FHIRPATH_REDEFINE_VARIABLE);
+ }
+ }
+
+ definedVariables.put(name, value);
+ }
}
private static class ExecutionTypeContext {
@@ -1066,7 +1090,7 @@ public class FHIRPathEngine {
private TypeDetails context;
private TypeDetails thisItem;
private TypeDetails total;
-
+ private Map definedVariables;
public ExecutionTypeContext(Object appInfo, String resource, TypeDetails context, TypeDetails thisItem) {
super();
@@ -1083,7 +1107,27 @@ public class FHIRPathEngine {
return thisItem;
}
+ public boolean hasDefinedVariable(String name) {
+ return definedVariables != null && definedVariables.containsKey(name);
+ }
+ public TypeDetails getDefinedVariable(String name) {
+ return definedVariables == null ? null : definedVariables.get(name);
+ }
+ public void setDefinedVariable(String name, TypeDetails value) {
+ if (isSystemVariable(name))
+ throw new PathEngineException("Redefine of variable "+name, I18nConstants.FHIRPATH_REDEFINE_VARIABLE);
+ if (definedVariables == null) {
+ definedVariables = new HashMap();
+ } else {
+ if (definedVariables.containsKey(name)) {
+ // Can't do this, so throw an error
+ throw new PathEngineException("Redefine of variable "+name, I18nConstants.FHIRPATH_REDEFINE_VARIABLE);
+ }
+ }
+
+ definedVariables.put(name, value);
+ }
}
private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException {
@@ -1423,6 +1467,7 @@ public class FHIRPathEngine {
case Descendants: return checkParamCount(lexer, location, exp, 0);
case MemberOf: return checkParamCount(lexer, location, exp, 1);
case Trace: return checkParamCount(lexer, location, exp, 1, 2);
+ case DefineVariable: return checkParamCount(lexer, location, exp, 1, 2);
case Check: return checkParamCount(lexer, location, exp, 2);
case Today: return checkParamCount(lexer, location, exp, 0);
case Now: return checkParamCount(lexer, location, exp, 0);
@@ -1480,8 +1525,9 @@ public class FHIRPathEngine {
return false;
}
- private List execute(ExecutionContext context, List focus, ExpressionNode exp, boolean atEntry) throws FHIRException {
+ private List execute(ExecutionContext inContext, List focus, ExpressionNode exp, boolean atEntry) throws FHIRException {
// System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString());
+ ExecutionContext context = contextForParameter(inContext);
List work = new ArrayList();
switch (exp.getKind()) {
case Unary:
@@ -1506,7 +1552,7 @@ public class FHIRPathEngine {
}
break;
case Function:
- List work2 = evaluateFunction(context, focus, exp);
+ List work2 = evaluateFunction("aliasAs".equals(exp.getName()) ? inContext : context, focus, exp);
work.addAll(work2);
break;
case Constant:
@@ -1525,6 +1571,7 @@ public class FHIRPathEngine {
ExpressionNode next = exp.getOpNext();
ExpressionNode last = exp;
while (next != null) {
+ context = contextForParameter(inContext);
List work2 = preOperate(work, last.getOperation(), exp);
if (work2 != null) {
work = work2;
@@ -1588,8 +1635,8 @@ public class FHIRPathEngine {
return new TypeDetails(CollectionStatus.SINGLETON, exp.getName());
}
- private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, Set elementDependencies, boolean atEntry, boolean canBeNone, ExpressionNode container) throws PathEngineException, DefinitionException {
-
+ private TypeDetails executeType(ExecutionTypeContext inContext, TypeDetails focus, ExpressionNode exp, Set elementDependencies, boolean atEntry, boolean canBeNone, ExpressionNode container) throws PathEngineException, DefinitionException {
+ ExecutionTypeContext context = contextForParameter(inContext);
TypeDetails result = new TypeDetails(null);
switch (exp.getKind()) {
case Name:
@@ -1639,6 +1686,7 @@ public class FHIRPathEngine {
ExpressionNode next = exp.getOpNext();
ExpressionNode last = exp;
while (next != null) {
+ context = contextForParameter(inContext);
TypeDetails work;
if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) {
work = executeTypeName(context, focus, next, atEntry);
@@ -1676,6 +1724,10 @@ public class FHIRPathEngine {
}
FHIRConstant c = (FHIRConstant) constant;
if (c.getValue().startsWith("%")) {
+ String varName = c.getValue().substring(1);
+ if (context.hasDefinedVariable(varName)) {
+ return context.getDefinedVariable(varName);
+ }
return resolveConstant(context, c.getValue(), beforeContext, expr, true);
} else if (c.getValue().startsWith("@")) {
return new ArrayList(Arrays.asList(processDateConstant(context.appInfo, c.getValue().substring(1), expr)));
@@ -1746,6 +1798,21 @@ public class FHIRPathEngine {
}
}
+ static boolean isSystemVariable(String name){
+ if (name.equals("sct"))
+ return true;
+ if (name.equals("loinc"))
+ return true;
+ if (name.equals("ucum"))
+ return true;
+ if (name.equals("resource"))
+ return true;
+ if (name.equals("rootResource"))
+ return true;
+ if (name.equals("context"))
+ return true;
+ return false;
+ }
private List resolveConstant(ExecutionContext context, String s, boolean beforeContext, ExpressionNode expr, boolean explicitConstant) throws PathEngineException {
if (s.equals("%sct")) {
@@ -3151,6 +3218,10 @@ public class FHIRPathEngine {
} else if (hostServices == null) {
throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s);
} else {
+ String varName = s.substring(1);
+ if (context.hasDefinedVariable(varName)) {
+ return context.getDefinedVariable(varName);
+ }
TypeDetails v = hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant);
if (v == null) {
throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s);
@@ -3503,6 +3574,25 @@ public class FHIRPathEngine {
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return focus;
}
+ case DefineVariable : {
+ checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.UNORDERED, TypeDetails.FP_String));
+ // set the type of the variable
+ // Actually evaluate the value of the first parameter (to get the name of the variable if possible)
+ // and if have that, set it into the context
+ ExpressionNode p = exp.getParameters().get(0);
+ if (p.getKind() == Kind.Constant && p.getConstant() != null) {
+ String varName = exp.getParameters().get(0).getConstant().primitiveValue();
+ if (varName != null) {
+ if (paramTypes.size() > 1)
+ context.setDefinedVariable(varName, paramTypes.get(1));
+ else
+ context.setDefinedVariable(varName, focus);
+ }
+ } else {
+ // this variable is not a constant, so we can't analyze what name it could have
+ }
+ return focus;
+ }
case Check : {
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return focus;
@@ -3734,7 +3824,7 @@ public class FHIRPathEngine {
case 0:
return exp.getFunction() == Function.Where || exp.getFunction() == Function.Exists || exp.getFunction() == Function.All || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate;
case 1:
- return exp.getFunction() == Function.Trace;
+ return exp.getFunction() == Function.Trace || exp.getFunction() == Function.DefineVariable;
default:
return false;
}
@@ -3887,6 +3977,7 @@ public class FHIRPathEngine {
case Descendants : return funcDescendants(context, focus, exp);
case MemberOf : return funcMemberOf(context, focus, exp);
case Trace : return funcTrace(context, focus, exp);
+ case DefineVariable : return funcDefineVariable(context, focus, exp);
case Check : return funcCheck(context, focus, exp);
case Today : return funcToday(context, focus, exp);
case Now : return funcNow(context, focus, exp);
@@ -4652,13 +4743,50 @@ public class FHIRPathEngine {
private ExecutionContext changeThis(ExecutionContext context, Base newThis) {
- return new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, context.aliases, newThis);
+ ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, context.aliases, newThis);
+ // append all of the defined variables from the context into the new context
+ if (context.definedVariables != null) {
+ for (String s : context.definedVariables.keySet()) {
+ newContext.setDefinedVariable(s, context.definedVariables.get(s));
+ }
+ }
+ return newContext;
+ }
+
+ private ExecutionContext contextForParameter(ExecutionContext context) {
+ ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, context.aliases, context.thisItem);
+ newContext.total = context.total;
+ newContext.index = context.index;
+ // append all of the defined variables from the context into the new context
+ if (context.definedVariables != null) {
+ for (String s : context.definedVariables.keySet()) {
+ newContext.setDefinedVariable(s, context.definedVariables.get(s));
+ }
+ }
+ return newContext;
}
private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) {
- return new ExecutionTypeContext(context.appInfo, context.resource, context.context, newThis);
+ ExecutionTypeContext newContext = new ExecutionTypeContext(context.appInfo, context.resource, context.context, newThis);
+ // append all of the defined variables from the context into the new context
+ if (context.definedVariables != null) {
+ for (String s : context.definedVariables.keySet()) {
+ newContext.setDefinedVariable(s, context.definedVariables.get(s));
+ }
+ }
+ return newContext;
}
+ private ExecutionTypeContext contextForParameter(ExecutionTypeContext context) {
+ ExecutionTypeContext newContext = new ExecutionTypeContext(context.appInfo, context.resource, context.context, context.thisItem);
+ // append all of the defined variables from the context into the new context
+ if (context.definedVariables != null) {
+ for (String s : context.definedVariables.keySet()) {
+ newContext.setDefinedVariable(s, context.definedVariables.get(s));
+ }
+ }
+ return newContext;
+ }
private List funcNow(ExecutionContext context, List focus, ExpressionNode exp) {
List result = new ArrayList();
@@ -5496,6 +5624,20 @@ public class FHIRPathEngine {
return focus;
}
+ private List funcDefineVariable(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException {
+ List nl = execute(context, focus, exp.getParameters().get(0), true);
+ String name = nl.get(0).primitiveValue();
+ List value;
+ if (exp.getParameters().size() == 2) {
+ value = execute(context, focus, exp.getParameters().get(1), true);
+ } else {
+ value = focus;
+ }
+ // stash the variable into the context
+ context.setDefinedVariable(name, value);
+ return focus;
+ }
+
private List funcCheck(ExecutionContext context, List focus, ExpressionNode expr) throws FHIRException {
List n1 = execute(context, focus, expr.getParameters().get(0), true);
if (!convertToBoolean(n1)) {
diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRPathTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRPathTests.java
index aefedf7f6..0cdf20a34 100644
--- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRPathTests.java
+++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRPathTests.java
@@ -180,6 +180,9 @@ public class FHIRPathTests {
fail = TestResultType.EXECUTION;
};
fp.setAllowPolymorphicNames("lenient/polymorphics".equals(test.getAttribute("mode")));
+ boolean skipStaticCheck = false;
+ if ("true".equals(test.getAttribute("skipStaticCheck")))
+ skipStaticCheck = true;
Base res = null;
List outcome = new ArrayList();
@@ -210,17 +213,19 @@ public class FHIRPathTests {
}
}
- try {
- if (Utilities.noString(input)) {
- fp.check(null, null, node);
- } else {
- fp.check(res, res.fhirType(), res.fhirType(), node);
+ if (!skipStaticCheck) {
+ try {
+ if (Utilities.noString(input)) {
+ fp.check(null, null, node);
+ } else {
+ fp.check(res, res.fhirType(), res.fhirType(), node);
+ }
+ Assertions.assertTrue(fail != TestResultType.SEMANTICS, String.format("Expected exception didn't occur checking %s", expression));
+ } catch (Exception e) {
+ System.out.println("Checking Error: "+e.getMessage());
+ Assertions.assertTrue(fail == TestResultType.SEMANTICS, String.format("Unexpected exception checking %s: " + e.getMessage(), expression));
+ node = null;
}
- Assertions.assertTrue(fail != TestResultType.SEMANTICS, String.format("Expected exception didn't occur checking %s", expression));
- } catch (Exception e) {
- System.out.println("Checking Error: "+e.getMessage());
- Assertions.assertTrue(fail == TestResultType.SEMANTICS, String.format("Unexpected exception checking %s: " + e.getMessage(), expression));
- node = null;
}
}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java
index 7968cb2e7..9e481a680 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java
@@ -1102,6 +1102,7 @@ public class I18nConstants {
public static final String SD_ELEMENT_FIXED_WRONG_TYPE = "SD_ELEMENT_FIXED_WRONG_TYPE";
public static final String SD_ELEMENT_REASON_DERIVED = "SD_ELEMENT_REASON_DERIVED";
public static final String SD_ELEMENT_PATTERN_WRONG_TYPE = "SD_ELEMENT_PATTERN_WRONG_TYPE";
+ public static final String FHIRPATH_REDEFINE_VARIABLE = "FHIRPATH_REDEFINE_VARIABLE";
}
diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties
index 7002b84bd..5dabcf717 100644
--- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties
+++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties
@@ -1084,6 +1084,7 @@ TX_GENERAL_CC_ERROR_MESSAGE = No valid coding was found for the value set ''{0}'
Validation_VAL_Profile_Minimum_SLICE_one = Slice ''{3}'': a matching slice is required, but not found (from {1}). Note that other slices are allowed in addition to this required slice
Validation_VAL_Profile_Minimum_SLICE_other = Slice ''{3}'': minimum required = {0}, but only found {7} (from {1})
FHIRPATH_UNKNOWN_EXTENSION = Reference to an unknown extension - double check that the URL ''{0}'' is correct
+FHIRPATH_REDEFINE_VARIABLE = The variable ''{0}'' cannot be redefined
Type_Specific_Checks_DT_XHTML_Resolve = Hyperlink ''{0}'' at ''{1}'' for ''{2}''' does not resolve
Type_Specific_Checks_DT_XHTML_Resolve_Img = Image source ''{0}'' at ''{1}'' does not resolve
TYPE_SPECIFIC_CHECKS_DT_XHTML_MULTIPLE_MATCHES = Hyperlink ''{0}'' at ''{1}'' for ''{2}'' resolves to multiple targets ({3})