This commit is contained in:
Grahame Grieve 2024-04-21 21:57:05 +10:00
commit 5134aa187c
7 changed files with 366 additions and 212 deletions

View File

@ -50,7 +50,7 @@ public class ExpressionNode {
Empty, Not, Exists, SubsetOf, SupersetOf, IsDistinct, Distinct, Count, Where, Select, All, Repeat, Aggregate, 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, 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, Iif, Upper, Lower, ToChars, IndexOf, Substring, StartsWith, EndsWith, Matches, MatchesFull, ReplaceMatches,
Contains, Replace, Length, Children, Descendants, MemberOf, Trace, Check, Today, Now, Resolve, Extension, AllFalse, Contains, Replace, Length, Children, Descendants, MemberOf, Trace, DefineVariable, Check, Today, Now, Resolve, Extension, AllFalse,
AnyFalse, AllTrue, AnyTrue, HasValue, OfType, Type, ConvertsToBoolean, ConvertsToInteger, ConvertsToString, AnyFalse, AllTrue, AnyTrue, HasValue, OfType, Type, ConvertsToBoolean, ConvertsToInteger, ConvertsToString,
ConvertsToDecimal, ConvertsToQuantity, ConvertsToDateTime, ConvertsToDate, ConvertsToTime, ToBoolean, ToInteger, ConvertsToDecimal, ConvertsToQuantity, ConvertsToDateTime, ConvertsToDate, ConvertsToTime, ToBoolean, ToInteger,
ToString, ToDecimal, ToQuantity, ToDateTime, ToTime, ConformsTo, Round, Sqrt, Abs, Ceiling, Exp, Floor, Ln, Log, ToString, ToDecimal, ToQuantity, ToDateTime, ToTime, ConformsTo, Round, Sqrt, Abs, Ceiling, Exp, Floor, Ln, Log,
@ -60,7 +60,7 @@ public class ExpressionNode {
Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision, Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision,
// Local extensions to FHIRPath // Local extensions to FHIRPath
HtmlChecks1, HtmlChecks2, AliasAs, Alias, Comparable; HtmlChecks1, HtmlChecks2, Comparable;
public static Function fromCode(String name) { public static Function fromCode(String name) {
if (name.equals("empty")) if (name.equals("empty"))
@ -151,6 +151,8 @@ public class ExpressionNode {
return Function.MemberOf; return Function.MemberOf;
if (name.equals("trace")) if (name.equals("trace"))
return Function.Trace; return Function.Trace;
if (name.equals("defineVariable"))
return Function.DefineVariable;
if (name.equals("check")) if (name.equals("check"))
return Function.Check; return Function.Check;
if (name.equals("today")) if (name.equals("today"))
@ -171,10 +173,6 @@ public class ExpressionNode {
return Function.AnyTrue; return Function.AnyTrue;
if (name.equals("hasValue")) if (name.equals("hasValue"))
return Function.HasValue; return Function.HasValue;
if (name.equals("alias"))
return Function.Alias;
if (name.equals("aliasAs"))
return Function.AliasAs;
if (name.equals("htmlChecks")) if (name.equals("htmlChecks"))
return Function.HtmlChecks1; return Function.HtmlChecks1;
if (name.equals("htmlchecks")) if (name.equals("htmlchecks"))
@ -353,6 +351,8 @@ public class ExpressionNode {
return "memberOf"; return "memberOf";
case Trace: case Trace:
return "trace"; return "trace";
case DefineVariable :
return "defineVariable";
case Check: case Check:
return "check"; return "check";
case Today: case Today:
@ -373,10 +373,6 @@ public class ExpressionNode {
return "anyTrue"; return "anyTrue";
case HasValue: case HasValue:
return "hasValue"; return "hasValue";
case Alias:
return "alias";
case AliasAs:
return "aliasAs";
case Encode: case Encode:
return "encode"; return "encode";
case Decode: case Decode:

View File

@ -139,6 +139,7 @@ public class FHIRPathEngine {
/** /**
* A constant reference - e.g. a reference to a name that must be resolved in * 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. * 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 * This will also be called if the host invokes the FluentPath engine with a
* context of null * context of null
@ -604,7 +605,7 @@ public class FHIRPathEngine {
} }
log = new StringBuilder(); log = new StringBuilder();
return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null,
base != null && base.isResource() ? base : null, base, null, base), list, ExpressionNode, true); base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true);
} }
/** /**
@ -623,7 +624,7 @@ public class FHIRPathEngine {
} }
log = new StringBuilder(); log = new StringBuilder();
return execute( return execute(
new ExecutionContext(null, base.isResource() ? base : null, base.isResource() ? base : null, base, null, base), new ExecutionContext(null, base.isResource() ? base : null, base.isResource() ? base : null, base, base),
list, exp, true); list, exp, true);
} }
@ -642,7 +643,7 @@ public class FHIRPathEngine {
list.add(base); list.add(base);
} }
log = new StringBuilder(); log = new StringBuilder();
return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list,
ExpressionNode, true); ExpressionNode, true);
} }
@ -661,7 +662,7 @@ public class FHIRPathEngine {
list.add(base); list.add(base);
} }
log = new StringBuilder(); log = new StringBuilder();
return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list,
expressionNode, true); expressionNode, true);
} }
@ -681,7 +682,7 @@ public class FHIRPathEngine {
list.add(base); list.add(base);
} }
log = new StringBuilder(); log = new StringBuilder();
return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, exp, true); return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, exp, true);
} }
/** /**
@ -848,16 +849,14 @@ public class FHIRPathEngine {
private Base context; private Base context;
private Base thisItem; private Base thisItem;
private List<Base> total; private List<Base> total;
private Map<String, Base> aliases;
private int index; private int index;
private Map<String, List<Base>> definedVariables;
public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Map<String, Base> aliases, public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Base thisItem) {
Base thisItem) {
this.appInfo = appInfo; this.appInfo = appInfo;
this.context = context; this.context = context;
this.focusResource = resource; this.focusResource = resource;
this.rootResource = rootResource; this.rootResource = rootResource;
this.aliases = aliases;
this.thisItem = thisItem; this.thisItem = thisItem;
this.index = 0; this.index = 0;
} }
@ -886,26 +885,34 @@ public class FHIRPathEngine {
return new IntegerType(index); return new IntegerType(index);
} }
public void addAlias(String name, List<Base> focus) throws FHIRException {
if (aliases == null) {
aliases = new HashMap<String, Base>();
} else {
aliases = new HashMap<String, Base>(aliases); // clone it, since it's going to change
}
if (focus.size() > 1) {
throw makeException(null, I18nConstants.FHIRPATH_ALIAS_COLLECTION);
}
aliases.put(name, focus.size() == 0 ? null : focus.get(0));
}
public Base getAlias(String name) {
return aliases == null ? null : aliases.get(name);
}
public ExecutionContext setIndex(int i) { public ExecutionContext setIndex(int i) {
index = i; index = i;
return this; return this;
} }
public boolean hasDefinedVariable(String name) {
return definedVariables != null && definedVariables.containsKey(name);
}
public List<Base> getDefinedVariable(String name) {
return definedVariables == null ? makeNull() : definedVariables.get(name);
}
public void setDefinedVariable(String name, List<Base> value) {
if (isSystemVariable(name))
throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_REDEFINE_VARIABLE, name), I18nConstants.FHIRPATH_REDEFINE_VARIABLE);
if (definedVariables == null) {
definedVariables = new HashMap<String, List<Base>>();
} 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 class ExecutionTypeContext { private class ExecutionTypeContext {
@ -914,6 +921,7 @@ public class FHIRPathEngine {
private TypeDetails context; private TypeDetails context;
private TypeDetails thisItem; private TypeDetails thisItem;
private TypeDetails total; private TypeDetails total;
private Map<String, TypeDetails> definedVariables;
public ExecutionTypeContext(Object appInfo, String resource, TypeDetails context, TypeDetails thisItem) { public ExecutionTypeContext(Object appInfo, String resource, TypeDetails context, TypeDetails thisItem) {
super(); super();
@ -932,6 +940,29 @@ public class FHIRPathEngine {
return thisItem; 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<String, TypeDetails>();
} 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 { private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException {
@ -1333,6 +1364,8 @@ public class FHIRPathEngine {
return checkParamCount(lexer, location, exp, 1); return checkParamCount(lexer, location, exp, 1);
case Trace: case Trace:
return checkParamCount(lexer, location, exp, 1, 2); return checkParamCount(lexer, location, exp, 1, 2);
case DefineVariable:
return checkParamCount(lexer, location, exp, 1, 2);
case Check: case Check:
return checkParamCount(lexer, location, exp, 2); return checkParamCount(lexer, location, exp, 2);
case Today: case Today:
@ -1353,10 +1386,6 @@ public class FHIRPathEngine {
return checkParamCount(lexer, location, exp, 0); return checkParamCount(lexer, location, exp, 0);
case HasValue: case HasValue:
return checkParamCount(lexer, location, exp, 0); return checkParamCount(lexer, location, exp, 0);
case Alias:
return checkParamCount(lexer, location, exp, 1);
case AliasAs:
return checkParamCount(lexer, location, exp, 1);
case Encode: case Encode:
return checkParamCount(lexer, location, exp, 1); return checkParamCount(lexer, location, exp, 1);
case Decode: case Decode:
@ -1442,9 +1471,10 @@ public class FHIRPathEngine {
return false; return false;
} }
private List<Base> execute(ExecutionContext context, List<Base> focus, ExpressionNode exp, boolean atEntry) private List<Base> execute(ExecutionContext inContext, List<Base> focus, ExpressionNode exp, boolean atEntry)
throws FHIRException { throws FHIRException {
// System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString()); // System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString());
ExecutionContext context = contextForParameter(inContext);
List<Base> work = new ArrayList<Base>(); List<Base> work = new ArrayList<Base>();
switch (exp.getKind()) { switch (exp.getKind()) {
case Unary: case Unary:
@ -1488,6 +1518,7 @@ public class FHIRPathEngine {
ExpressionNode next = exp.getOpNext(); ExpressionNode next = exp.getOpNext();
ExpressionNode last = exp; ExpressionNode last = exp;
while (next != null) { while (next != null) {
context = contextForParameter(inContext);
List<Base> work2 = preOperate(work, last.getOperation(), exp); List<Base> work2 = preOperate(work, last.getOperation(), exp);
if (work2 != null) { if (work2 != null) {
work = work2; work = work2;
@ -1551,9 +1582,10 @@ public class FHIRPathEngine {
return new TypeDetails(CollectionStatus.SINGLETON, exp.getName()); return new TypeDetails(CollectionStatus.SINGLETON, exp.getName());
} }
private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) private TypeDetails executeType(ExecutionTypeContext inContext, TypeDetails focus, ExpressionNode exp, boolean atEntry)
throws PathEngineException, DefinitionException { throws PathEngineException, DefinitionException {
TypeDetails result = new TypeDetails(null); ExecutionTypeContext context = contextForParameter(inContext);
TypeDetails result = new TypeDetails(null);
switch (exp.getKind()) { switch (exp.getKind()) {
case Name: case Name:
if (atEntry && exp.getName().equals("$this")) { if (atEntry && exp.getName().equals("$this")) {
@ -1597,6 +1629,7 @@ public class FHIRPathEngine {
ExpressionNode next = exp.getOpNext(); ExpressionNode next = exp.getOpNext();
ExpressionNode last = exp; ExpressionNode last = exp;
while (next != null) { while (next != null) {
context = contextForParameter(inContext);
TypeDetails work; TypeDetails work;
if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) { if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) {
work = executeTypeName(context, focus, next, atEntry); work = executeTypeName(context, focus, next, atEntry);
@ -1621,6 +1654,10 @@ public class FHIRPathEngine {
} }
FHIRConstant c = (FHIRConstant) constant; FHIRConstant c = (FHIRConstant) constant;
if (c.getValue().startsWith("%")) { 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, explicitConstant); return resolveConstant(context, c.getValue(), beforeContext, expr, explicitConstant);
} else if (c.getValue().startsWith("@")) { } else if (c.getValue().startsWith("@")) {
return new ArrayList<Base>(Arrays.asList(processDateConstant(context.appInfo, c.getValue().substring(1), expr))); return new ArrayList<Base>(Arrays.asList(processDateConstant(context.appInfo, c.getValue().substring(1), expr)));
@ -1691,6 +1728,22 @@ 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<Base> resolveConstant(ExecutionContext context, String s, boolean beforeContext, ExpressionNode expr, boolean explicitConstant) private List<Base> resolveConstant(ExecutionContext context, String s, boolean beforeContext, ExpressionNode expr, boolean explicitConstant)
throws PathEngineException { throws PathEngineException {
if (s.equals("%sct")) { if (s.equals("%sct")) {
@ -3134,8 +3187,14 @@ public class FHIRPathEngine {
} else if (s.startsWith("%`ext-")) { } else if (s.startsWith("%`ext-")) {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
} else if (hostServices == null) { } else if (hostServices == null) {
String varName = s.substring(1);
if (context.hasDefinedVariable(varName))
return context.getDefinedVariable(varName);
throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s);
} else { } else {
String varName = s.substring(1);
if (context.hasDefinedVariable(varName))
return context.getDefinedVariable(varName);
return hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant); return hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant);
} }
} }
@ -3432,6 +3491,25 @@ public class FHIRPathEngine {
new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return focus; 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: { case Check: {
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, checkParamTypes(exp, exp.getFunction().toCode(), paramTypes,
new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
@ -3466,14 +3544,6 @@ public class FHIRPathEngine {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
case Comparable: case Comparable:
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
case Alias:
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes,
new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return anything(CollectionStatus.SINGLETON);
case AliasAs:
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes,
new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return focus;
case Encode: case Encode:
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, checkParamTypes(exp, exp.getFunction().toCode(), paramTypes,
new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
@ -3623,7 +3693,7 @@ public class FHIRPathEngine {
|| exp.getFunction() == Function.All || exp.getFunction() == Function.Select || exp.getFunction() == Function.All || exp.getFunction() == Function.Select
|| exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate; || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate;
case 1: case 1:
return exp.getFunction() == Function.Trace; return exp.getFunction() == Function.Trace || exp.getFunction() == Function.DefineVariable;
default: default:
return false; return false;
} }
@ -3831,6 +3901,8 @@ public class FHIRPathEngine {
return funcMemberOf(context, focus, exp); return funcMemberOf(context, focus, exp);
case Trace: case Trace:
return funcTrace(context, focus, exp); return funcTrace(context, focus, exp);
case DefineVariable:
return funcDefineVariable(context, focus, exp);
case Check: case Check:
return funcCheck(context, focus, exp); return funcCheck(context, focus, exp);
case Today: case Today:
@ -3851,8 +3923,6 @@ public class FHIRPathEngine {
return funcAllTrue(context, focus, exp); return funcAllTrue(context, focus, exp);
case HasValue: case HasValue:
return funcHasValue(context, focus, exp); return funcHasValue(context, focus, exp);
case AliasAs:
return funcAliasAs(context, focus, exp);
case Encode: case Encode:
return funcEncode(context, focus, exp); return funcEncode(context, focus, exp);
case Decode: case Decode:
@ -3867,8 +3937,6 @@ public class FHIRPathEngine {
return funcSplit(context, focus, exp); return funcSplit(context, focus, exp);
case Join: case Join:
return funcJoin(context, focus, exp); return funcJoin(context, focus, exp);
case Alias:
return funcAlias(context, focus, exp);
case HtmlChecks1: case HtmlChecks1:
return funcHtmlChecks1(context, focus, exp); return funcHtmlChecks1(context, focus, exp);
case HtmlChecks2: case HtmlChecks2:
@ -4417,24 +4485,6 @@ public class FHIRPathEngine {
return result; return result;
} }
private List<Base> funcAliasAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
String name = nl.get(0).primitiveValue();
context.addAlias(name, focus);
return focus;
}
private List<Base> funcAlias(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
String name = nl.get(0).primitiveValue();
List<Base> res = new ArrayList<Base>();
Base b = context.getAlias(name);
if (b != null) {
res.add(b);
}
return res;
}
private List<Base> funcHtmlChecks1(ExecutionContext context, List<Base> focus, ExpressionNode exp) private List<Base> funcHtmlChecks1(ExecutionContext context, List<Base> focus, ExpressionNode exp)
throws FHIRException { throws FHIRException {
// todo: actually check the HTML // todo: actually check the HTML
@ -4585,12 +4635,50 @@ public class FHIRPathEngine {
} }
private ExecutionContext changeThis(ExecutionContext context, Base newThis) { private ExecutionContext changeThis(ExecutionContext context, Base newThis) {
return new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context,
context.aliases, newThis); 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.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) { 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<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) { private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
@ -5417,6 +5505,20 @@ public class FHIRPathEngine {
return focus; return focus;
} }
private List<Base> funcDefineVariable(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
String name = nl.get(0).primitiveValue();
List<Base> 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<Base> funcCheck(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException { private List<Base> funcCheck(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException {
List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
if (!convertToBoolean(n1)) { if (!convertToBoolean(n1)) {

View File

@ -50,7 +50,7 @@ public class ExpressionNode {
Empty, Not, Exists, SubsetOf, SupersetOf, IsDistinct, Distinct, Count, Where, Select, All, Repeat, Aggregate, 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, 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, Iif, Upper, Lower, ToChars, IndexOf, Substring, StartsWith, EndsWith, Matches, MatchesFull, ReplaceMatches,
Contains, Replace, Length, Children, Descendants, MemberOf, Trace, Check, Today, Now, Resolve, Extension, AllFalse, Contains, Replace, Length, Children, Descendants, MemberOf, Trace, DefineVariable, Check, Today, Now, Resolve, Extension, AllFalse,
AnyFalse, AllTrue, AnyTrue, HasValue, OfType, Type, ConvertsToBoolean, ConvertsToInteger, ConvertsToString, AnyFalse, AllTrue, AnyTrue, HasValue, OfType, Type, ConvertsToBoolean, ConvertsToInteger, ConvertsToString,
ConvertsToDecimal, ConvertsToQuantity, ConvertsToDateTime, ConvertsToDate, ConvertsToTime, ToBoolean, ToInteger, ConvertsToDecimal, ConvertsToQuantity, ConvertsToDateTime, ConvertsToDate, ConvertsToTime, ToBoolean, ToInteger,
ToString, ToDecimal, ToQuantity, ToDateTime, ToTime, ConformsTo, Round, Sqrt, Abs, Ceiling, Exp, Floor, Ln, Log, ToString, ToDecimal, ToQuantity, ToDateTime, ToTime, ConformsTo, Round, Sqrt, Abs, Ceiling, Exp, Floor, Ln, Log,
@ -60,7 +60,7 @@ public class ExpressionNode {
Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision, Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision,
// Local extensions to FHIRPath // Local extensions to FHIRPath
HtmlChecks1, HtmlChecks2, AliasAs, Alias, Comparable; HtmlChecks1, HtmlChecks2, Comparable;
public static Function fromCode(String name) { public static Function fromCode(String name) {
if (name.equals("empty")) if (name.equals("empty"))
@ -151,6 +151,8 @@ public class ExpressionNode {
return Function.MemberOf; return Function.MemberOf;
if (name.equals("trace")) if (name.equals("trace"))
return Function.Trace; return Function.Trace;
if (name.equals("defineVariable"))
return Function.DefineVariable;
if (name.equals("check")) if (name.equals("check"))
return Function.Check; return Function.Check;
if (name.equals("today")) if (name.equals("today"))
@ -171,10 +173,6 @@ public class ExpressionNode {
return Function.AnyTrue; return Function.AnyTrue;
if (name.equals("hasValue")) if (name.equals("hasValue"))
return Function.HasValue; return Function.HasValue;
if (name.equals("alias"))
return Function.Alias;
if (name.equals("aliasAs"))
return Function.AliasAs;
if (name.equals("htmlChecks")) if (name.equals("htmlChecks"))
return Function.HtmlChecks1; return Function.HtmlChecks1;
if (name.equals("htmlchecks")) if (name.equals("htmlchecks"))
@ -353,6 +351,8 @@ public class ExpressionNode {
return "memberOf"; return "memberOf";
case Trace: case Trace:
return "trace"; return "trace";
case DefineVariable :
return "defineVariable";
case Check: case Check:
return "check"; return "check";
case Today: case Today:
@ -373,10 +373,6 @@ public class ExpressionNode {
return "anyTrue"; return "anyTrue";
case HasValue: case HasValue:
return "hasValue"; return "hasValue";
case Alias:
return "alias";
case AliasAs:
return "aliasAs";
case Encode: case Encode:
return "encode"; return "encode";
case Decode: case Decode:

View File

@ -140,6 +140,7 @@ public class FHIRPathEngine {
/** /**
* A constant reference - e.g. a reference to a name that must be resolved in * 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. * 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 * This will also be called if the host invokes the FluentPath engine with a
* context of null * context of null
@ -605,7 +606,7 @@ public class FHIRPathEngine {
} }
log = new StringBuilder(); log = new StringBuilder();
return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null,
base != null && base.isResource() ? base : null, base, null, base), list, ExpressionNode, true); base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true);
} }
/** /**
@ -624,7 +625,7 @@ public class FHIRPathEngine {
} }
log = new StringBuilder(); log = new StringBuilder();
return execute( return execute(
new ExecutionContext(null, base.isResource() ? base : null, base.isResource() ? base : null, base, null, base), new ExecutionContext(null, base.isResource() ? base : null, base.isResource() ? base : null, base, base),
list, exp, true); list, exp, true);
} }
@ -643,7 +644,7 @@ public class FHIRPathEngine {
list.add(base); list.add(base);
} }
log = new StringBuilder(); log = new StringBuilder();
return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list,
ExpressionNode, true); ExpressionNode, true);
} }
@ -662,7 +663,7 @@ public class FHIRPathEngine {
list.add(base); list.add(base);
} }
log = new StringBuilder(); log = new StringBuilder();
return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list,
expressionNode, true); expressionNode, true);
} }
@ -682,7 +683,7 @@ public class FHIRPathEngine {
list.add(base); list.add(base);
} }
log = new StringBuilder(); log = new StringBuilder();
return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, exp, true); return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, exp, true);
} }
/** /**
@ -849,16 +850,14 @@ public class FHIRPathEngine {
private Base context; private Base context;
private Base thisItem; private Base thisItem;
private List<Base> total; private List<Base> total;
private Map<String, Base> aliases;
private int index; private int index;
private Map<String, List<Base>> definedVariables;
public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Map<String, Base> aliases, public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Base thisItem) {
Base thisItem) {
this.appInfo = appInfo; this.appInfo = appInfo;
this.context = context; this.context = context;
this.focusResource = resource; this.focusResource = resource;
this.rootResource = rootResource; this.rootResource = rootResource;
this.aliases = aliases;
this.thisItem = thisItem; this.thisItem = thisItem;
this.index = 0; this.index = 0;
} }
@ -887,26 +886,34 @@ public class FHIRPathEngine {
return new IntegerType(index); return new IntegerType(index);
} }
public void addAlias(String name, List<Base> focus) throws FHIRException {
if (aliases == null) {
aliases = new HashMap<String, Base>();
} else {
aliases = new HashMap<String, Base>(aliases); // clone it, since it's going to change
}
if (focus.size() > 1) {
throw makeException(null, I18nConstants.FHIRPATH_ALIAS_COLLECTION);
}
aliases.put(name, focus.size() == 0 ? null : focus.get(0));
}
public Base getAlias(String name) {
return aliases == null ? null : aliases.get(name);
}
public ExecutionContext setIndex(int i) { public ExecutionContext setIndex(int i) {
index = i; index = i;
return this; return this;
} }
public boolean hasDefinedVariable(String name) {
return definedVariables != null && definedVariables.containsKey(name);
}
public List<Base> getDefinedVariable(String name) {
return definedVariables == null ? makeNull() : definedVariables.get(name);
}
public void setDefinedVariable(String name, List<Base> value) {
if (isSystemVariable(name))
throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_REDEFINE_VARIABLE, name), I18nConstants.FHIRPATH_REDEFINE_VARIABLE);
if (definedVariables == null) {
definedVariables = new HashMap<String, List<Base>>();
} 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 class ExecutionTypeContext { private class ExecutionTypeContext {
@ -915,6 +922,7 @@ public class FHIRPathEngine {
private TypeDetails context; private TypeDetails context;
private TypeDetails thisItem; private TypeDetails thisItem;
private TypeDetails total; private TypeDetails total;
private Map<String, TypeDetails> definedVariables;
public ExecutionTypeContext(Object appInfo, String resource, TypeDetails context, TypeDetails thisItem) { public ExecutionTypeContext(Object appInfo, String resource, TypeDetails context, TypeDetails thisItem) {
super(); super();
@ -933,6 +941,29 @@ public class FHIRPathEngine {
return thisItem; 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<String, TypeDetails>();
} 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 { private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException {
@ -1333,6 +1364,8 @@ public class FHIRPathEngine {
return checkParamCount(lexer, location, exp, 1); return checkParamCount(lexer, location, exp, 1);
case Trace: case Trace:
return checkParamCount(lexer, location, exp, 1, 2); return checkParamCount(lexer, location, exp, 1, 2);
case DefineVariable:
return checkParamCount(lexer, location, exp, 1, 2);
case Check: case Check:
return checkParamCount(lexer, location, exp, 2); return checkParamCount(lexer, location, exp, 2);
case Today: case Today:
@ -1353,10 +1386,6 @@ public class FHIRPathEngine {
return checkParamCount(lexer, location, exp, 0); return checkParamCount(lexer, location, exp, 0);
case HasValue: case HasValue:
return checkParamCount(lexer, location, exp, 0); return checkParamCount(lexer, location, exp, 0);
case Alias:
return checkParamCount(lexer, location, exp, 1);
case AliasAs:
return checkParamCount(lexer, location, exp, 1);
case Encode: case Encode:
return checkParamCount(lexer, location, exp, 1); return checkParamCount(lexer, location, exp, 1);
case Decode: case Decode:
@ -1442,9 +1471,10 @@ public class FHIRPathEngine {
return false; return false;
} }
private List<Base> execute(ExecutionContext context, List<Base> focus, ExpressionNode exp, boolean atEntry) private List<Base> execute(ExecutionContext inContext, List<Base> focus, ExpressionNode exp, boolean atEntry)
throws FHIRException { throws FHIRException {
// System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString()); // System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString());
ExecutionContext context = contextForParameter(inContext);
List<Base> work = new ArrayList<Base>(); List<Base> work = new ArrayList<Base>();
switch (exp.getKind()) { switch (exp.getKind()) {
case Unary: case Unary:
@ -1488,6 +1518,7 @@ public class FHIRPathEngine {
ExpressionNode next = exp.getOpNext(); ExpressionNode next = exp.getOpNext();
ExpressionNode last = exp; ExpressionNode last = exp;
while (next != null) { while (next != null) {
context = contextForParameter(inContext);
List<Base> work2 = preOperate(work, last.getOperation(), exp); List<Base> work2 = preOperate(work, last.getOperation(), exp);
if (work2 != null) { if (work2 != null) {
work = work2; work = work2;
@ -1551,8 +1582,9 @@ public class FHIRPathEngine {
return new TypeDetails(CollectionStatus.SINGLETON, exp.getName()); return new TypeDetails(CollectionStatus.SINGLETON, exp.getName());
} }
private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) private TypeDetails executeType(ExecutionTypeContext inContext, TypeDetails focus, ExpressionNode exp, boolean atEntry)
throws PathEngineException, DefinitionException { throws PathEngineException, DefinitionException {
ExecutionTypeContext context = contextForParameter(inContext);
TypeDetails result = new TypeDetails(null); TypeDetails result = new TypeDetails(null);
switch (exp.getKind()) { switch (exp.getKind()) {
case Name: case Name:
@ -1597,6 +1629,7 @@ public class FHIRPathEngine {
ExpressionNode next = exp.getOpNext(); ExpressionNode next = exp.getOpNext();
ExpressionNode last = exp; ExpressionNode last = exp;
while (next != null) { while (next != null) {
context = contextForParameter(inContext);
TypeDetails work; TypeDetails work;
if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) { if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) {
work = executeTypeName(context, focus, next, atEntry); work = executeTypeName(context, focus, next, atEntry);
@ -1622,6 +1655,10 @@ public class FHIRPathEngine {
} }
FHIRConstant c = (FHIRConstant) constant; FHIRConstant c = (FHIRConstant) constant;
if (c.getValue().startsWith("%")) { 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, explicitConstant); return resolveConstant(context, c.getValue(), beforeContext, expr, explicitConstant);
} else if (c.getValue().startsWith("@")) { } else if (c.getValue().startsWith("@")) {
return new ArrayList<Base>(Arrays.asList(processDateConstant(context.appInfo, c.getValue().substring(1), expr))); return new ArrayList<Base>(Arrays.asList(processDateConstant(context.appInfo, c.getValue().substring(1), expr)));
@ -1692,6 +1729,22 @@ 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<Base> resolveConstant(ExecutionContext context, String s, boolean beforeContext, ExpressionNode expr, boolean explicitConstant) private List<Base> resolveConstant(ExecutionContext context, String s, boolean beforeContext, ExpressionNode expr, boolean explicitConstant)
throws PathEngineException { throws PathEngineException {
if (s.equals("%sct")) { if (s.equals("%sct")) {
@ -3136,8 +3189,14 @@ public class FHIRPathEngine {
} else if (s.startsWith("%`ext-")) { } else if (s.startsWith("%`ext-")) {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
} else if (hostServices == null) { } else if (hostServices == null) {
String varName = s.substring(1);
if (context.hasDefinedVariable(varName))
return context.getDefinedVariable(varName);
throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s);
} else { } else {
String varName = s.substring(1);
if (context.hasDefinedVariable(varName))
return context.getDefinedVariable(varName);
return hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant); return hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant);
} }
} }
@ -3434,6 +3493,25 @@ public class FHIRPathEngine {
new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return focus; 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: { case Check: {
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, checkParamTypes(exp, exp.getFunction().toCode(), paramTypes,
new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
@ -3468,14 +3546,6 @@ public class FHIRPathEngine {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
case Comparable: case Comparable:
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
case Alias:
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes,
new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return anything(CollectionStatus.SINGLETON);
case AliasAs:
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes,
new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return focus;
case Encode: case Encode:
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, checkParamTypes(exp, exp.getFunction().toCode(), paramTypes,
new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
@ -3625,7 +3695,7 @@ public class FHIRPathEngine {
|| exp.getFunction() == Function.All || exp.getFunction() == Function.Select || exp.getFunction() == Function.All || exp.getFunction() == Function.Select
|| exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate; || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate;
case 1: case 1:
return exp.getFunction() == Function.Trace; return exp.getFunction() == Function.Trace || exp.getFunction() == Function.DefineVariable;
default: default:
return false; return false;
} }
@ -3833,6 +3903,8 @@ public class FHIRPathEngine {
return funcMemberOf(context, focus, exp); return funcMemberOf(context, focus, exp);
case Trace: case Trace:
return funcTrace(context, focus, exp); return funcTrace(context, focus, exp);
case DefineVariable:
return funcDefineVariable(context, focus, exp);
case Check: case Check:
return funcCheck(context, focus, exp); return funcCheck(context, focus, exp);
case Today: case Today:
@ -3853,8 +3925,6 @@ public class FHIRPathEngine {
return funcAllTrue(context, focus, exp); return funcAllTrue(context, focus, exp);
case HasValue: case HasValue:
return funcHasValue(context, focus, exp); return funcHasValue(context, focus, exp);
case AliasAs:
return funcAliasAs(context, focus, exp);
case Encode: case Encode:
return funcEncode(context, focus, exp); return funcEncode(context, focus, exp);
case Decode: case Decode:
@ -3869,8 +3939,6 @@ public class FHIRPathEngine {
return funcSplit(context, focus, exp); return funcSplit(context, focus, exp);
case Join: case Join:
return funcJoin(context, focus, exp); return funcJoin(context, focus, exp);
case Alias:
return funcAlias(context, focus, exp);
case HtmlChecks1: case HtmlChecks1:
return funcHtmlChecks1(context, focus, exp); return funcHtmlChecks1(context, focus, exp);
case HtmlChecks2: case HtmlChecks2:
@ -4427,24 +4495,6 @@ public class FHIRPathEngine {
return result; return result;
} }
private List<Base> funcAliasAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
String name = nl.get(0).primitiveValue();
context.addAlias(name, focus);
return focus;
}
private List<Base> funcAlias(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
String name = nl.get(0).primitiveValue();
List<Base> res = new ArrayList<Base>();
Base b = context.getAlias(name);
if (b != null) {
res.add(b);
}
return res;
}
private List<Base> funcHtmlChecks1(ExecutionContext context, List<Base> focus, ExpressionNode exp) private List<Base> funcHtmlChecks1(ExecutionContext context, List<Base> focus, ExpressionNode exp)
throws FHIRException { throws FHIRException {
// todo: actually check the HTML // todo: actually check the HTML
@ -4587,12 +4637,50 @@ public class FHIRPathEngine {
} }
private ExecutionContext changeThis(ExecutionContext context, Base newThis) { private ExecutionContext changeThis(ExecutionContext context, Base newThis) {
return new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context,
context.aliases, newThis); 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.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) { 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<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) { private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
@ -5420,6 +5508,20 @@ public class FHIRPathEngine {
return focus; return focus;
} }
private List<Base> funcDefineVariable(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
String name = nl.get(0).primitiveValue();
List<Base> 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<Base> funcCheck(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException { private List<Base> funcCheck(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException {
List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
if (!convertToBoolean(n1)) { if (!convertToBoolean(n1)) {

View File

@ -59,7 +59,7 @@ public class ExpressionNode {
Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision, Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision,
// Local extensions to FHIRPath // Local extensions to FHIRPath
HtmlChecks1, HtmlChecks2, AliasAs, Alias, Comparable, hasTemplateIdOf; HtmlChecks1, HtmlChecks2, Comparable, hasTemplateIdOf;
public static Function fromCode(String name) { public static Function fromCode(String name) {
if (name.equals("empty")) return Function.Empty; if (name.equals("empty")) return Function.Empty;
@ -117,8 +117,6 @@ public class ExpressionNode {
if (name.equals("allTrue")) return Function.AllTrue; if (name.equals("allTrue")) return Function.AllTrue;
if (name.equals("anyTrue")) return Function.AnyTrue; if (name.equals("anyTrue")) return Function.AnyTrue;
if (name.equals("hasValue")) return Function.HasValue; if (name.equals("hasValue")) return Function.HasValue;
if (name.equals("alias")) return Function.Alias;
if (name.equals("aliasAs")) return Function.AliasAs;
if (name.equals("htmlChecks")) return Function.HtmlChecks1; if (name.equals("htmlChecks")) return Function.HtmlChecks1;
if (name.equals("htmlchecks")) return Function.HtmlChecks1; // support change of care from R3 if (name.equals("htmlchecks")) return Function.HtmlChecks1; // support change of care from R3
if (name.equals("htmlChecks2")) return Function.HtmlChecks2; if (name.equals("htmlChecks2")) return Function.HtmlChecks2;
@ -223,8 +221,6 @@ public class ExpressionNode {
case AllTrue : return "allTrue"; case AllTrue : return "allTrue";
case AnyTrue : return "anyTrue"; case AnyTrue : return "anyTrue";
case HasValue : return "hasValue"; case HasValue : return "hasValue";
case Alias : return "alias";
case AliasAs : return "aliasAs";
case Encode : return "encode"; case Encode : return "encode";
case Decode : return "decode"; case Decode : return "decode";
case Escape : return "escape"; case Escape : return "escape";

View File

@ -770,7 +770,7 @@ public class FHIRPathEngine {
list.add(base); list.add(base);
} }
log = new StringBuilder(); log = new StringBuilder();
return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, null, base), list, ExpressionNode, true); return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true);
} }
/** /**
@ -789,7 +789,7 @@ public class FHIRPathEngine {
list.add(base); list.add(base);
} }
log = new StringBuilder(); log = new StringBuilder();
return execute(new ExecutionContext(null, base.isResource() ? base : null, base.isResource() ? base : null, base, null, base), list, exp, true); return execute(new ExecutionContext(null, base.isResource() ? base : null, base.isResource() ? base : null, base, base), list, exp, true);
} }
/** /**
@ -807,7 +807,7 @@ public class FHIRPathEngine {
list.add(base); list.add(base);
} }
log = new StringBuilder(); log = new StringBuilder();
return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, ExpressionNode, true); return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, ExpressionNode, true);
} }
/** /**
@ -825,7 +825,7 @@ public class FHIRPathEngine {
list.add(base); list.add(base);
} }
log = new StringBuilder(); log = new StringBuilder();
return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, expressionNode, true); return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, expressionNode, true);
} }
/** /**
@ -844,7 +844,7 @@ public class FHIRPathEngine {
list.add(base); list.add(base);
} }
log = new StringBuilder(); log = new StringBuilder();
return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, exp, true); return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, exp, true);
} }
/** /**
@ -1009,16 +1009,14 @@ public class FHIRPathEngine {
private Base context; private Base context;
private Base thisItem; private Base thisItem;
private List<Base> total; private List<Base> total;
private Map<String, Base> aliases;
private int index; private int index;
private Map<String, List<Base>> definedVariables; private Map<String, List<Base>> definedVariables;
public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Map<String, Base> aliases, Base thisItem) { public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Base thisItem) {
this.appInfo = appInfo; this.appInfo = appInfo;
this.context = context; this.context = context;
this.focusResource = resource; this.focusResource = resource;
this.rootResource = rootResource; this.rootResource = rootResource;
this.aliases = aliases;
this.thisItem = thisItem; this.thisItem = thisItem;
this.index = 0; this.index = 0;
} }
@ -1042,20 +1040,6 @@ public class FHIRPathEngine {
return new IntegerType(index); return new IntegerType(index);
} }
public void addAlias(String name, List<Base> focus) throws FHIRException {
if (aliases == null) {
aliases = new HashMap<String, Base>();
} else {
aliases = new HashMap<String, Base>(aliases); // clone it, since it's going to change
}
if (focus.size() > 1) {
throw makeException(null, I18nConstants.FHIRPATH_ALIAS_COLLECTION);
}
aliases.put(name, focus.size() == 0 ? null : focus.get(0));
}
public Base getAlias(String name) {
return aliases == null ? null : aliases.get(name);
}
public ExecutionContext setIndex(int i) { public ExecutionContext setIndex(int i) {
index = i; index = i;
return this; return this;
@ -1064,9 +1048,11 @@ public class FHIRPathEngine {
public boolean hasDefinedVariable(String name) { public boolean hasDefinedVariable(String name) {
return definedVariables != null && definedVariables.containsKey(name); return definedVariables != null && definedVariables.containsKey(name);
} }
public List<Base> getDefinedVariable(String name) { public List<Base> getDefinedVariable(String name) {
return definedVariables == null ? makeNull() : definedVariables.get(name); return definedVariables == null ? makeNull() : definedVariables.get(name);
} }
public void setDefinedVariable(String name, List<Base> value) { public void setDefinedVariable(String name, List<Base> value) {
if (isSystemVariable(name)) if (isSystemVariable(name))
throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_REDEFINE_VARIABLE, name), I18nConstants.FHIRPATH_REDEFINE_VARIABLE); throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_REDEFINE_VARIABLE, name), I18nConstants.FHIRPATH_REDEFINE_VARIABLE);
@ -1110,9 +1096,11 @@ public class FHIRPathEngine {
public boolean hasDefinedVariable(String name) { public boolean hasDefinedVariable(String name) {
return definedVariables != null && definedVariables.containsKey(name); return definedVariables != null && definedVariables.containsKey(name);
} }
public TypeDetails getDefinedVariable(String name) { public TypeDetails getDefinedVariable(String name) {
return definedVariables == null ? null : definedVariables.get(name); return definedVariables == null ? null : definedVariables.get(name);
} }
public void setDefinedVariable(String name, TypeDetails value) { public void setDefinedVariable(String name, TypeDetails value) {
if (isSystemVariable(name)) if (isSystemVariable(name))
throw new PathEngineException("Redefine of variable "+name, I18nConstants.FHIRPATH_REDEFINE_VARIABLE); throw new PathEngineException("Redefine of variable "+name, I18nConstants.FHIRPATH_REDEFINE_VARIABLE);
@ -1478,8 +1466,6 @@ public class FHIRPathEngine {
case AllTrue: return checkParamCount(lexer, location, exp, 0); case AllTrue: return checkParamCount(lexer, location, exp, 0);
case AnyTrue: return checkParamCount(lexer, location, exp, 0); case AnyTrue: return checkParamCount(lexer, location, exp, 0);
case HasValue: return checkParamCount(lexer, location, exp, 0); case HasValue: return checkParamCount(lexer, location, exp, 0);
case Alias: return checkParamCount(lexer, location, exp, 1);
case AliasAs: return checkParamCount(lexer, location, exp, 1);
case Encode: return checkParamCount(lexer, location, exp, 1); case Encode: return checkParamCount(lexer, location, exp, 1);
case Decode: return checkParamCount(lexer, location, exp, 1); case Decode: return checkParamCount(lexer, location, exp, 1);
case Escape: return checkParamCount(lexer, location, exp, 1); case Escape: return checkParamCount(lexer, location, exp, 1);
@ -1552,7 +1538,7 @@ public class FHIRPathEngine {
} }
break; break;
case Function: case Function:
List<Base> work2 = evaluateFunction("aliasAs".equals(exp.getName()) ? inContext : context, focus, exp); List<Base> work2 = evaluateFunction(context, focus, exp);
work.addAll(work2); work.addAll(work2);
break; break;
case Constant: case Constant:
@ -3216,12 +3202,14 @@ public class FHIRPathEngine {
} else if (s.startsWith("%`ext-")) { } else if (s.startsWith("%`ext-")) {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
} else if (hostServices == null) { } else if (hostServices == null) {
String varName = s.substring(1);
if (context.hasDefinedVariable(varName))
return context.getDefinedVariable(varName);
throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s);
} else { } else {
String varName = s.substring(1); String varName = s.substring(1);
if (context.hasDefinedVariable(varName)) { if (context.hasDefinedVariable(varName))
return context.getDefinedVariable(varName); return context.getDefinedVariable(varName);
}
TypeDetails v = hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant); TypeDetails v = hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant);
if (v == null) { if (v == null) {
throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s);
@ -3635,12 +3623,6 @@ public class FHIRPathEngine {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
case Comparable : case Comparable :
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
case Alias :
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return anything(CollectionStatus.SINGLETON);
case AliasAs :
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return focus;
case Encode: case Encode:
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
@ -3988,7 +3970,6 @@ public class FHIRPathEngine {
case AnyTrue: return funcAnyTrue(context, focus, exp); case AnyTrue: return funcAnyTrue(context, focus, exp);
case AllTrue: return funcAllTrue(context, focus, exp); case AllTrue: return funcAllTrue(context, focus, exp);
case HasValue : return funcHasValue(context, focus, exp); case HasValue : return funcHasValue(context, focus, exp);
case AliasAs : return funcAliasAs(context, focus, exp);
case Encode : return funcEncode(context, focus, exp); case Encode : return funcEncode(context, focus, exp);
case Decode : return funcDecode(context, focus, exp); case Decode : return funcDecode(context, focus, exp);
case Escape : return funcEscape(context, focus, exp); case Escape : return funcEscape(context, focus, exp);
@ -3996,7 +3977,6 @@ public class FHIRPathEngine {
case Trim : return funcTrim(context, focus, exp); case Trim : return funcTrim(context, focus, exp);
case Split : return funcSplit(context, focus, exp); case Split : return funcSplit(context, focus, exp);
case Join : return funcJoin(context, focus, exp); case Join : return funcJoin(context, focus, exp);
case Alias : return funcAlias(context, focus, exp);
case HtmlChecks1 : return funcHtmlChecks1(context, focus, exp); case HtmlChecks1 : return funcHtmlChecks1(context, focus, exp);
case HtmlChecks2 : return funcHtmlChecks2(context, focus, exp); case HtmlChecks2 : return funcHtmlChecks2(context, focus, exp);
case Comparable : return funcComparable(context, focus, exp); case Comparable : return funcComparable(context, focus, exp);
@ -4567,24 +4547,6 @@ public class FHIRPathEngine {
return result; return result;
} }
private List<Base> funcAliasAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
String name = nl.get(0).primitiveValue();
context.addAlias(name, focus);
return focus;
}
private List<Base> funcAlias(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
String name = nl.get(0).primitiveValue();
List<Base> res = new ArrayList<Base>();
Base b = context.getAlias(name);
if (b != null) {
res.add(b);
}
return res;
}
private List<Base> funcHtmlChecks1(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { private List<Base> funcHtmlChecks1(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
// todo: actually check the HTML // todo: actually check the HTML
if (focus.size() != 1) { if (focus.size() != 1) {
@ -4743,7 +4705,7 @@ public class FHIRPathEngine {
private ExecutionContext changeThis(ExecutionContext context, Base newThis) { private ExecutionContext changeThis(ExecutionContext context, Base newThis) {
ExecutionContext newContext = 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, newThis);
// append all of the defined variables from the context into the new context // append all of the defined variables from the context into the new context
if (context.definedVariables != null) { if (context.definedVariables != null) {
for (String s : context.definedVariables.keySet()) { for (String s : context.definedVariables.keySet()) {
@ -4754,7 +4716,7 @@ public class FHIRPathEngine {
} }
private ExecutionContext contextForParameter(ExecutionContext context) { private ExecutionContext contextForParameter(ExecutionContext context) {
ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, context.aliases, context.thisItem); ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, context.thisItem);
newContext.total = context.total; newContext.total = context.total;
newContext.index = context.index; newContext.index = context.index;
// append all of the defined variables from the context into the new context // append all of the defined variables from the context into the new context

View File

@ -29,7 +29,7 @@
<maven_clean_version>3.1.0</maven_clean_version> <maven_clean_version>3.1.0</maven_clean_version>
<okhttp.version>4.11.0</okhttp.version> <okhttp.version>4.11.0</okhttp.version>
<jacoco_version>0.8.9</jacoco_version> <jacoco_version>0.8.9</jacoco_version>
<lombok_version>1.18.30</lombok_version> <lombok_version>1.18.32</lombok_version>
<byte_buddy_version>1.14.8</byte_buddy_version> <byte_buddy_version>1.14.8</byte_buddy_version>
<apache_poi_version>5.2.1</apache_poi_version> <apache_poi_version>5.2.1</apache_poi_version>
<saxon_he_version>9.8.0-15</saxon_he_version> <saxon_he_version>9.8.0-15</saxon_he_version>