From 59bd0d012cb5a5e5af33438c452a2c22d9a72346 Mon Sep 17 00:00:00 2001 From: Brian Postlethwaite Date: Wed, 17 Apr 2024 09:56:33 +1000 Subject: [PATCH 1/3] Remove the alias/aliasAs custom functions from R5 (use standard defineVariable now) --- .../hl7/fhir/r5/fhirpath/ExpressionNode.java | 6 +- .../hl7/fhir/r5/fhirpath/FHIRPathEngine.java | 66 ++++--------------- 2 files changed, 14 insertions(+), 58 deletions(-) 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 1571c7407..2b5a33033 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 @@ -59,7 +59,7 @@ public class ExpressionNode { Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision, // Local extensions to FHIRPath - HtmlChecks1, HtmlChecks2, AliasAs, Alias, Comparable, hasTemplateIdOf; + HtmlChecks1, HtmlChecks2, Comparable, hasTemplateIdOf; public static Function fromCode(String name) { if (name.equals("empty")) return Function.Empty; @@ -117,8 +117,6 @@ public class ExpressionNode { if (name.equals("allTrue")) return Function.AllTrue; if (name.equals("anyTrue")) return Function.AnyTrue; 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; // support change of care from R3 if (name.equals("htmlChecks2")) return Function.HtmlChecks2; @@ -223,8 +221,6 @@ public class ExpressionNode { case AllTrue : return "allTrue"; case AnyTrue : return "anyTrue"; case HasValue : return "hasValue"; - case Alias : return "alias"; - case AliasAs : return "aliasAs"; case Encode : return "encode"; case Decode : return "decode"; case Escape : return "escape"; 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 28506b2d3..2f257d58c 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 @@ -770,7 +770,7 @@ public class FHIRPathEngine { list.add(base); } 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); } 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); } 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); } 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); } 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 thisItem; 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) { + public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Base thisItem) { this.appInfo = appInfo; this.context = context; this.focusResource = resource; this.rootResource = rootResource; - this.aliases = aliases; this.thisItem = thisItem; this.index = 0; } @@ -1042,20 +1040,6 @@ public class FHIRPathEngine { return new IntegerType(index); } - public void addAlias(String name, List focus) throws FHIRException { - if (aliases == null) { - aliases = new HashMap(); - } else { - aliases = new HashMap(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) { index = i; return this; @@ -1064,9 +1048,11 @@ public class FHIRPathEngine { 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); @@ -1110,9 +1096,11 @@ public class FHIRPathEngine { 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); @@ -1478,8 +1466,6 @@ public class FHIRPathEngine { case AllTrue: return checkParamCount(lexer, location, exp, 0); case AnyTrue: 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 Decode: return checkParamCount(lexer, location, exp, 1); case Escape: return checkParamCount(lexer, location, exp, 1); @@ -1552,7 +1538,7 @@ public class FHIRPathEngine { } break; case Function: - List work2 = evaluateFunction("aliasAs".equals(exp.getName()) ? inContext : context, focus, exp); + List work2 = evaluateFunction(context, focus, exp); work.addAll(work2); break; case Constant: @@ -3635,12 +3621,6 @@ public class FHIRPathEngine { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); case Comparable : 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: checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); @@ -3988,7 +3968,6 @@ public class FHIRPathEngine { case AnyTrue: return funcAnyTrue(context, focus, exp); case AllTrue: return funcAllTrue(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 Decode : return funcDecode(context, focus, exp); case Escape : return funcEscape(context, focus, exp); @@ -3996,7 +3975,6 @@ public class FHIRPathEngine { case Trim : return funcTrim(context, focus, exp); case Split : return funcSplit(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 HtmlChecks2 : return funcHtmlChecks2(context, focus, exp); case Comparable : return funcComparable(context, focus, exp); @@ -4567,24 +4545,6 @@ public class FHIRPathEngine { return result; } - private List funcAliasAs(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { - List nl = execute(context, focus, exp.getParameters().get(0), true); - String name = nl.get(0).primitiveValue(); - context.addAlias(name, focus); - return focus; - } - - private List funcAlias(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 res = new ArrayList(); - Base b = context.getAlias(name); - if (b != null) { - res.add(b); - } - return res; - } - private List funcHtmlChecks1(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { // todo: actually check the HTML if (focus.size() != 1) { @@ -4743,7 +4703,7 @@ public class FHIRPathEngine { 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 if (context.definedVariables != null) { for (String s : context.definedVariables.keySet()) { @@ -4754,7 +4714,7 @@ public class FHIRPathEngine { } 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.index = context.index; // append all of the defined variables from the context into the new context From 0664e5601d92e178dc9047bfac2bbc8457b0ff10 Mon Sep 17 00:00:00 2001 From: Brian Postlethwaite Date: Wed, 17 Apr 2024 10:38:25 +1000 Subject: [PATCH 2/3] Remove the alias/aliasAs custom functions from R4/r4b (use standard defineVariable now) --- .../hl7/fhir/r4/fhirpath/ExpressionNode.java | 10 +-- .../hl7/fhir/r4/fhirpath/FHIRPathEngine.java | 68 ++---------------- .../hl7/fhir/r4b/fhirpath/ExpressionNode.java | 10 +-- .../hl7/fhir/r4b/fhirpath/FHIRPathEngine.java | 70 +++---------------- 4 files changed, 18 insertions(+), 140 deletions(-) diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/ExpressionNode.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/ExpressionNode.java index 66977c737..e2cacc528 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/ExpressionNode.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/ExpressionNode.java @@ -60,7 +60,7 @@ public class ExpressionNode { Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision, // Local extensions to FHIRPath - HtmlChecks1, HtmlChecks2, AliasAs, Alias, Comparable; + HtmlChecks1, HtmlChecks2, Comparable; public static Function fromCode(String name) { if (name.equals("empty")) @@ -171,10 +171,6 @@ public class ExpressionNode { return Function.AnyTrue; 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")) @@ -373,10 +369,6 @@ public class ExpressionNode { return "anyTrue"; case HasValue: return "hasValue"; - case Alias: - return "alias"; - case AliasAs: - return "aliasAs"; case Encode: return "encode"; case Decode: diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRPathEngine.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRPathEngine.java index c91f19dae..91c3f4624 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRPathEngine.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRPathEngine.java @@ -604,7 +604,7 @@ public class FHIRPathEngine { } 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); + base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true); } /** @@ -623,7 +623,7 @@ public class FHIRPathEngine { } log = new StringBuilder(); 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); } @@ -642,7 +642,7 @@ public class FHIRPathEngine { list.add(base); } 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); } @@ -661,7 +661,7 @@ public class FHIRPathEngine { list.add(base); } 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); } @@ -681,7 +681,7 @@ public class FHIRPathEngine { list.add(base); } 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 +848,13 @@ public class FHIRPathEngine { private Base context; private Base thisItem; private List total; - private Map aliases; private int index; - public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Map aliases, - Base thisItem) { + public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Base thisItem) { this.appInfo = appInfo; this.context = context; this.focusResource = resource; this.rootResource = rootResource; - this.aliases = aliases; this.thisItem = thisItem; this.index = 0; } @@ -886,22 +883,6 @@ public class FHIRPathEngine { return new IntegerType(index); } - public void addAlias(String name, List focus) throws FHIRException { - if (aliases == null) { - aliases = new HashMap(); - } else { - aliases = new HashMap(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) { index = i; return this; @@ -1353,10 +1334,6 @@ public class FHIRPathEngine { 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 Decode: @@ -3466,14 +3443,6 @@ public class FHIRPathEngine { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); case Comparable: 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: checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); @@ -3851,8 +3820,6 @@ public class FHIRPathEngine { return funcAllTrue(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 Decode: @@ -3867,8 +3834,6 @@ public class FHIRPathEngine { return funcSplit(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 HtmlChecks2: @@ -4417,24 +4382,6 @@ public class FHIRPathEngine { return result; } - private List funcAliasAs(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { - List nl = execute(context, focus, exp.getParameters().get(0), true); - String name = nl.get(0).primitiveValue(); - context.addAlias(name, focus); - return focus; - } - - private List funcAlias(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 res = new ArrayList(); - Base b = context.getAlias(name); - if (b != null) { - res.add(b); - } - return res; - } - private List funcHtmlChecks1(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { // todo: actually check the HTML @@ -4585,8 +4532,7 @@ public class FHIRPathEngine { } private ExecutionContext changeThis(ExecutionContext context, Base newThis) { - return new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, - context.aliases, newThis); + return new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, newThis); } private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) { diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/ExpressionNode.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/ExpressionNode.java index d79e026ce..fe5d54c2e 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/ExpressionNode.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/ExpressionNode.java @@ -60,7 +60,7 @@ public class ExpressionNode { Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision, // Local extensions to FHIRPath - HtmlChecks1, HtmlChecks2, AliasAs, Alias, Comparable; + HtmlChecks1, HtmlChecks2, Comparable; public static Function fromCode(String name) { if (name.equals("empty")) @@ -171,10 +171,6 @@ public class ExpressionNode { return Function.AnyTrue; 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")) @@ -373,10 +369,6 @@ public class ExpressionNode { return "anyTrue"; case HasValue: return "hasValue"; - case Alias: - return "alias"; - case AliasAs: - return "aliasAs"; case Encode: return "encode"; case Decode: diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/FHIRPathEngine.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/FHIRPathEngine.java index 81fe330f8..589753c7a 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/FHIRPathEngine.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/FHIRPathEngine.java @@ -605,7 +605,7 @@ public class FHIRPathEngine { } 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); + base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true); } /** @@ -624,7 +624,7 @@ public class FHIRPathEngine { } log = new StringBuilder(); 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); } @@ -643,7 +643,7 @@ public class FHIRPathEngine { list.add(base); } 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); } @@ -662,7 +662,7 @@ public class FHIRPathEngine { list.add(base); } 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); } @@ -682,7 +682,7 @@ public class FHIRPathEngine { list.add(base); } 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 +849,13 @@ public class FHIRPathEngine { private Base context; private Base thisItem; private List total; - private Map aliases; private int index; - public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Map aliases, - Base thisItem) { + public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Base thisItem) { this.appInfo = appInfo; this.context = context; this.focusResource = resource; this.rootResource = rootResource; - this.aliases = aliases; this.thisItem = thisItem; this.index = 0; } @@ -887,22 +884,6 @@ public class FHIRPathEngine { return new IntegerType(index); } - public void addAlias(String name, List focus) throws FHIRException { - if (aliases == null) { - aliases = new HashMap(); - } else { - aliases = new HashMap(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) { index = i; return this; @@ -1353,10 +1334,6 @@ public class FHIRPathEngine { 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 Decode: @@ -3468,14 +3445,6 @@ public class FHIRPathEngine { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); case Comparable: 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: checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); @@ -3853,8 +3822,6 @@ public class FHIRPathEngine { return funcAllTrue(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 Decode: @@ -3869,8 +3836,6 @@ public class FHIRPathEngine { return funcSplit(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 HtmlChecks2: @@ -4427,24 +4392,6 @@ public class FHIRPathEngine { return result; } - private List funcAliasAs(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { - List nl = execute(context, focus, exp.getParameters().get(0), true); - String name = nl.get(0).primitiveValue(); - context.addAlias(name, focus); - return focus; - } - - private List funcAlias(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 res = new ArrayList(); - Base b = context.getAlias(name); - if (b != null) { - res.add(b); - } - return res; - } - private List funcHtmlChecks1(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { // todo: actually check the HTML @@ -4587,8 +4534,9 @@ 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, + newThis); + return newContext; } private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) { From d56404afd6b912039d9498657833c78bbde6ca53 Mon Sep 17 00:00:00 2001 From: Brian Postlethwaite Date: Wed, 17 Apr 2024 13:09:12 +1000 Subject: [PATCH 3/3] Backport the defineVariable code to the R4 and R4B fhirpath implementations --- .../hl7/fhir/r4/fhirpath/ExpressionNode.java | 6 +- .../hl7/fhir/r4/fhirpath/FHIRPathEngine.java | 168 +++++++++++++++++- .../hl7/fhir/r4b/fhirpath/ExpressionNode.java | 6 +- .../hl7/fhir/r4b/fhirpath/FHIRPathEngine.java | 162 ++++++++++++++++- .../hl7/fhir/r5/fhirpath/FHIRPathEngine.java | 6 +- 5 files changed, 334 insertions(+), 14 deletions(-) diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/ExpressionNode.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/ExpressionNode.java index e2cacc528..a2bf898b7 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/ExpressionNode.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/ExpressionNode.java @@ -50,7 +50,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, + Contains, Replace, Length, 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, @@ -151,6 +151,8 @@ public class ExpressionNode { 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")) @@ -349,6 +351,8 @@ public class ExpressionNode { return "memberOf"; case Trace: return "trace"; + case DefineVariable : + return "defineVariable"; case Check: return "check"; case Today: diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRPathEngine.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRPathEngine.java index 91c3f4624..a23cb6c79 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRPathEngine.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRPathEngine.java @@ -139,6 +139,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 @@ -849,6 +850,7 @@ public class FHIRPathEngine { private Base thisItem; private List total; private int index; + private Map> definedVariables; public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Base thisItem) { this.appInfo = appInfo; @@ -887,6 +889,30 @@ 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 class ExecutionTypeContext { @@ -895,6 +921,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(); @@ -913,6 +940,29 @@ 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 { @@ -1314,6 +1364,8 @@ public class FHIRPathEngine { 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: @@ -1419,9 +1471,10 @@ public class FHIRPathEngine { return false; } - private List execute(ExecutionContext context, List focus, ExpressionNode exp, boolean atEntry) + 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: @@ -1465,6 +1518,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; @@ -1528,9 +1582,10 @@ public class FHIRPathEngine { 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 { - TypeDetails result = new TypeDetails(null); + ExecutionTypeContext context = contextForParameter(inContext); + TypeDetails result = new TypeDetails(null); switch (exp.getKind()) { case Name: if (atEntry && exp.getName().equals("$this")) { @@ -1574,6 +1629,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); @@ -1598,6 +1654,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, explicitConstant); } else if (c.getValue().startsWith("@")) { return new ArrayList(Arrays.asList(processDateConstant(context.appInfo, c.getValue().substring(1), expr))); @@ -1668,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 resolveConstant(ExecutionContext context, String s, boolean beforeContext, ExpressionNode expr, boolean explicitConstant) throws PathEngineException { if (s.equals("%sct")) { @@ -3111,8 +3187,14 @@ public class FHIRPathEngine { } else if (s.startsWith("%`ext-")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); } 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); } else { + String varName = s.substring(1); + if (context.hasDefinedVariable(varName)) + return context.getDefinedVariable(varName); return hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant); } } @@ -3409,6 +3491,25 @@ public class FHIRPathEngine { 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)); @@ -3592,7 +3693,7 @@ public class FHIRPathEngine { || 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; } @@ -3800,6 +3901,8 @@ public class FHIRPathEngine { 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: @@ -4532,11 +4635,50 @@ public class FHIRPathEngine { } private ExecutionContext changeThis(ExecutionContext context, Base newThis) { - return new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, 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 + 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) { - 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) { @@ -5363,6 +5505,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.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/ExpressionNode.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/ExpressionNode.java index fe5d54c2e..c9731541a 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/ExpressionNode.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/ExpressionNode.java @@ -50,7 +50,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, + Contains, Replace, Length, 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, @@ -151,6 +151,8 @@ public class ExpressionNode { 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")) @@ -349,6 +351,8 @@ public class ExpressionNode { return "memberOf"; case Trace: return "trace"; + case DefineVariable : + return "defineVariable"; case Check: return "check"; case Today: diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/FHIRPathEngine.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/FHIRPathEngine.java index 589753c7a..15c9b4c7a 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/FHIRPathEngine.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/FHIRPathEngine.java @@ -140,6 +140,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 @@ -850,6 +851,7 @@ public class FHIRPathEngine { private Base thisItem; private List total; private int index; + private Map> definedVariables; public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Base thisItem) { this.appInfo = appInfo; @@ -888,6 +890,30 @@ 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 class ExecutionTypeContext { @@ -896,6 +922,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(); @@ -914,6 +941,29 @@ 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 { @@ -1314,6 +1364,8 @@ public class FHIRPathEngine { 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: @@ -1419,9 +1471,10 @@ public class FHIRPathEngine { return false; } - private List execute(ExecutionContext context, List focus, ExpressionNode exp, boolean atEntry) + 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: @@ -1465,6 +1518,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; @@ -1528,8 +1582,9 @@ public class FHIRPathEngine { 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 { + ExecutionTypeContext context = contextForParameter(inContext); TypeDetails result = new TypeDetails(null); switch (exp.getKind()) { case Name: @@ -1574,6 +1629,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); @@ -1599,6 +1655,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, explicitConstant); } else if (c.getValue().startsWith("@")) { return new ArrayList(Arrays.asList(processDateConstant(context.appInfo, c.getValue().substring(1), expr))); @@ -1669,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 resolveConstant(ExecutionContext context, String s, boolean beforeContext, ExpressionNode expr, boolean explicitConstant) throws PathEngineException { if (s.equals("%sct")) { @@ -3113,8 +3189,14 @@ public class FHIRPathEngine { } else if (s.startsWith("%`ext-")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); } 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); } else { + String varName = s.substring(1); + if (context.hasDefinedVariable(varName)) + return context.getDefinedVariable(varName); return hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant); } } @@ -3411,6 +3493,25 @@ public class FHIRPathEngine { 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)); @@ -3594,7 +3695,7 @@ public class FHIRPathEngine { || 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; } @@ -3802,6 +3903,8 @@ public class FHIRPathEngine { 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: @@ -4536,11 +4639,48 @@ public class FHIRPathEngine { private ExecutionContext changeThis(ExecutionContext context, Base 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 + 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) { - 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) { @@ -5368,6 +5508,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/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 2f257d58c..7630b8f06 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 @@ -3202,12 +3202,14 @@ public class FHIRPathEngine { } else if (s.startsWith("%`ext-")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); } 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); } else { String varName = s.substring(1); - if (context.hasDefinedVariable(varName)) { + 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);