diff --git a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml index beb25c62eb..2fa777b8c6 100644 --- a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml @@ -36,6 +36,20 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + F:Hl7.FhirPath.Functions.EqualityOperators.TypedElementEqualityComparer + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.ElementModel.ElementNodeExtensions.ToScopedNode(Hl7.Fhir.ElementModel.ITypedElement) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.Fhir.ElementModel.ScopedNode.Children(System.String) @@ -106,6 +120,62 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.Fhir.FhirPath.ElementNavFhirExtensions.HasValue(Hl7.Fhir.ElementModel.ITypedElement) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.FhirPath.ElementNavFhirExtensions.HtmlChecks(Hl7.Fhir.ElementModel.ITypedElement) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.FhirPath.ElementNavFhirExtensions.ToFhirValues(System.Collections.Generic.IEnumerable{Hl7.Fhir.ElementModel.ITypedElement}) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.FhirPath.FhirEvaluationContext.#ctor(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.ElementModel.ITypedElement) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.FhirPath.FhirEvaluationContext.#ctor(Hl7.Fhir.ElementModel.ITypedElement,System.Collections.Generic.IDictionary{System.String,System.Collections.Generic.IEnumerable{Hl7.Fhir.ElementModel.ITypedElement}}) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.FhirPath.FhirEvaluationContext.#ctor(Hl7.Fhir.ElementModel.ITypedElement) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.FhirPath.FhirEvaluationContext.#ctor(Hl7.Fhir.ElementModel.ScopedNode) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.FhirPath.FhirEvaluationContext.CreateDefault + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.Fhir.FhirPath.FhirEvaluationContext.get_ElementResolver @@ -260,6 +330,104 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.FhirPath.CompiledExpression.BeginInvoke(Hl7.Fhir.ElementModel.ITypedElement,Hl7.FhirPath.EvaluationContext,System.AsyncCallback,System.Object) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.CompiledExpression.EndInvoke(System.IAsyncResult) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.CompiledExpression.Invoke(Hl7.Fhir.ElementModel.ITypedElement,Hl7.FhirPath.EvaluationContext) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.CompiledExpressionExtensions.IsBoolean(Hl7.FhirPath.CompiledExpression,System.Boolean,Hl7.Fhir.ElementModel.ITypedElement,Hl7.FhirPath.EvaluationContext) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.CompiledExpressionExtensions.IsTrue(Hl7.FhirPath.CompiledExpression,Hl7.Fhir.ElementModel.ITypedElement,Hl7.FhirPath.EvaluationContext) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.CompiledExpressionExtensions.Predicate(Hl7.FhirPath.CompiledExpression,Hl7.Fhir.ElementModel.ITypedElement,Hl7.FhirPath.EvaluationContext) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.CompiledExpressionExtensions.Scalar(Hl7.FhirPath.CompiledExpression,Hl7.Fhir.ElementModel.ITypedElement,Hl7.FhirPath.EvaluationContext) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.EvaluationContext.#ctor(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.ElementModel.ITypedElement,System.Collections.Generic.IDictionary{System.String,System.Collections.Generic.IEnumerable{Hl7.Fhir.ElementModel.ITypedElement}}) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.EvaluationContext.#ctor(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.ElementModel.ITypedElement) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.EvaluationContext.#ctor(Hl7.Fhir.ElementModel.ITypedElement) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.EvaluationContext.get_Environment + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.EvaluationContext.get_Resource + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.EvaluationContext.get_RootResource + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.EvaluationContext.get_Tracer + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.FhirPath.EvaluationContext.WithResourceOverrides(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.ElementModel.ITypedElement) @@ -267,6 +435,76 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.FhirPath.Expressions.SymbolTableExtensions.AddVar(Hl7.FhirPath.Expressions.SymbolTable,System.String,Hl7.Fhir.ElementModel.ITypedElement) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.FhirPathCompilerCache.IsBoolean(Hl7.Fhir.ElementModel.ITypedElement,System.String,System.Boolean,Hl7.FhirPath.EvaluationContext) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.FhirPathCompilerCache.IsTrue(Hl7.Fhir.ElementModel.ITypedElement,System.String,Hl7.FhirPath.EvaluationContext) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.FhirPathCompilerCache.Predicate(Hl7.Fhir.ElementModel.ITypedElement,System.String,Hl7.FhirPath.EvaluationContext) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.FhirPathCompilerCache.Scalar(Hl7.Fhir.ElementModel.ITypedElement,System.String,Hl7.FhirPath.EvaluationContext) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.FhirPathCompilerCache.Select(Hl7.Fhir.ElementModel.ITypedElement,System.String,Hl7.FhirPath.EvaluationContext) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.Functions.EqualityOperators.IsEqualTo(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.ElementModel.ITypedElement,System.Boolean) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.Functions.EqualityOperators.IsEqualTo(System.Collections.Generic.IEnumerable{Hl7.Fhir.ElementModel.ITypedElement},System.Collections.Generic.IEnumerable{Hl7.Fhir.ElementModel.ITypedElement},System.Boolean) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.Functions.EqualityOperators.IsEquivalentTo(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.ElementModel.ITypedElement,System.Boolean) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.FhirPath.Functions.EqualityOperators.IsEquivalentTo(System.Collections.Generic.IEnumerable{Hl7.Fhir.ElementModel.ITypedElement},System.Collections.Generic.IEnumerable{Hl7.Fhir.ElementModel.ITypedElement},System.Boolean) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0006 M:Hl7.Fhir.ElementModel.ITypedElement.Children(System.String) diff --git a/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs index 88a994434a..181fd3d80d 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs @@ -9,6 +9,7 @@ #nullable enable using Hl7.Fhir.ElementModel.Adapters; +using Hl7.Fhir.Model; using Hl7.Fhir.Specification; using Hl7.Fhir.Utility; using System; @@ -110,8 +111,11 @@ public static IReadOnlyCollection ChildDefinitions(th return new List(); } - public static ScopedNode ToScopedNode(this ITypedElement node) => - node as ScopedNode ?? new ScopedNode(node); + public static IScopedNode ToScopedNode(this ITypedElement node) => + node as IScopedNode ?? new ScopedNode(node); + + internal static IEnumerable ToScopedNodes(this IEnumerable nodes) => + nodes.Select(n => n.ToScopedNode()); } } diff --git a/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs b/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs index 54072a9c50..aac1586cba 100644 --- a/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs +++ b/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs @@ -1,13 +1,14 @@ #nullable enable using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.FhirPath.Functions; using System.Collections.Generic; using System.Linq; namespace Hl7.FhirPath { - public delegate IEnumerable CompiledExpression(ITypedElement root, EvaluationContext ctx); + public delegate IEnumerable CompiledExpression(IScopedNode root, EvaluationContext ctx); public static class CompiledExpressionExtensions { @@ -18,7 +19,7 @@ public static class CompiledExpressionExtensions /// Input at which the expression is evaluated /// Context of the evaluation /// The single result of an expression - public static object? Scalar(this CompiledExpression evaluator, ITypedElement input, EvaluationContext ctx) + public static object? Scalar(this CompiledExpression evaluator, IScopedNode input, EvaluationContext ctx) { var result = evaluator(input, ctx).Take(2).ToArray(); return result.Any() ? result.Single().Value : null; @@ -31,7 +32,7 @@ public static class CompiledExpressionExtensions /// Input at which the expression is evaluated /// Context of the evaluation /// True if expression returns true of empty, otheriwse false - public static bool Predicate(this CompiledExpression evaluator, ITypedElement input, EvaluationContext ctx) + public static bool Predicate(this CompiledExpression evaluator, IScopedNode input, EvaluationContext ctx) { var result = evaluator(input, ctx).BooleanEval(); return result is null || result.Value; @@ -44,7 +45,7 @@ public static bool Predicate(this CompiledExpression evaluator, ITypedElement in /// Input at which the expression is evaluated /// Context of the evaluation /// True if expression returns true , and false if expression returns empty of false. - public static bool IsTrue(this CompiledExpression evaluator, ITypedElement input, EvaluationContext ctx) + public static bool IsTrue(this CompiledExpression evaluator, IScopedNode input, EvaluationContext ctx) { var result = evaluator(input, ctx).BooleanEval(); return result is not null && result.Value; @@ -59,7 +60,7 @@ public static bool IsTrue(this CompiledExpression evaluator, ITypedElement input /// Input at which the expression is evaluated /// Context of the evaluation /// True if the result of an expression is equal to a given boolean, otherwise false - public static bool IsBoolean(this CompiledExpression evaluator, bool value, ITypedElement input, EvaluationContext ctx) + public static bool IsBoolean(this CompiledExpression evaluator, bool value, IScopedNode input, EvaluationContext ctx) { var result = evaluator(input, ctx).BooleanEval(); return result is not null && result.Value == value; diff --git a/src/Hl7.Fhir.Base/FhirPath/ElementNavFhirExtensions.cs b/src/Hl7.Fhir.Base/FhirPath/ElementNavFhirExtensions.cs index 5126d3ad2e..f2317b2c18 100644 --- a/src/Hl7.Fhir.Base/FhirPath/ElementNavFhirExtensions.cs +++ b/src/Hl7.Fhir.Base/FhirPath/ElementNavFhirExtensions.cs @@ -39,16 +39,16 @@ public static void PrepareFhirSymbolTableFunctions() public static SymbolTable AddFhirExtensions(this SymbolTable t) { - t.Add("hasValue", (ITypedElement f) => f.HasValue(), doNullProp: false); + t.Add("hasValue", (IScopedNode f) => f.HasValue(), doNullProp: false); t.Add("resolve", (IScopedNode f, EvaluationContext ctx) => resolver(f, ctx), doNullProp: false); t.Add("resolve", (IEnumerable f, EvaluationContext ctx) => f.Select(fi => resolver(fi, ctx)), doNullProp: false); - t.Add("memberOf", (ITypedElement input, string valueset, EvaluationContext ctx) => MemberOf(input, valueset, ctx), doNullProp: false); + t.Add("memberOf", (IScopedNode input, string valueset, EvaluationContext ctx) => MemberOf(input, valueset, ctx), doNullProp: false); // Pre-normative this function was called htmlchecks, normative is htmlChecks // lets keep both to keep everyone happy. - t.Add("htmlchecks", (ITypedElement f) => f.HtmlChecks(), doNullProp: false); - t.Add("htmlChecks", (ITypedElement f) => f.HtmlChecks(), doNullProp: false); + t.Add("htmlchecks", (IScopedNode f) => f.HtmlChecks(), doNullProp: false); + t.Add("htmlChecks", (IScopedNode f) => f.HtmlChecks(), doNullProp: false); t.Add("lowBoundary", (decimal d, long precision) => AdjustBoundaryDecimal(d, precision, substract), doNullProp: false); t.Add("lowBoundary", (decimal d) => AdjustBoundaryDecimal(d, null, substract), doNullProp: false); @@ -81,14 +81,14 @@ public static SymbolTable AddFhirExtensions(this SymbolTable t) /// /// /// - public static bool HasValue(this ITypedElement focus) => focus?.Value is not null; + public static bool HasValue(this IScopedNode focus) => focus?.Value is not null; /// /// Check if the node has a value, and not just extensions. /// /// /// - public static bool HtmlChecks(this ITypedElement focus) + public static bool HtmlChecks(this IScopedNode focus) { if (focus?.Value is null) return false; @@ -96,7 +96,7 @@ public static bool HtmlChecks(this ITypedElement focus) return XHtml.IsValidNarrativeXhtml(focus.Value.ToString()!); } - public static IEnumerable ToFhirValues(this IEnumerable results) + public static IEnumerable ToFhirValues(this IEnumerable results) { return results.Select(r => { @@ -268,7 +268,7 @@ internal static P.Time BoundaryTime(P.Time time, long? precision, int minutes, i /// EvaluationContext of the FhirPath compiler /// See summary /// - internal static bool? MemberOf(ITypedElement input, string valueset, EvaluationContext ctx) + internal static bool? MemberOf(IScopedNode input, string valueset, EvaluationContext ctx) { var service = (ctx is FhirEvaluationContext fctx ? fctx.TerminologyService : null) ?? throw new ArgumentNullException(nameof(ctx), "The 'memberOf' function cannot be executed because the FhirEvaluationContext does not include a TerminologyService."); @@ -278,7 +278,7 @@ internal static P.Time BoundaryTime(P.Time time, long? precision, int minutes, i inParams = input.InstanceType switch { - "code" when input is ScopedNode sn => inParams.WithCode(code: sn.Value as string, context: sn.LocalLocation), + "code" => inParams.WithCode(code: input.Value as string, context: input.GetLocalLocation()), "Coding" => inParams.WithCoding(input.ParseCoding()), "CodeableConcept" => inParams.WithCodeableConcept(input.ParseCodeableConcept()), "string" or "System.String" => inParams.WithCode(code: input.Value as string, context: "No context available"), diff --git a/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs b/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs index 9b71ec64f2..b0d0a5ab05 100644 --- a/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs +++ b/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs @@ -1,4 +1,5 @@ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using System; using System.Collections.Generic; @@ -22,7 +23,7 @@ public EvaluationContext() /// /// The data that will be represented by %resource [Obsolete("%resource and %rootResource are inferred from scoped nodes by the evaluator. If you do not have access to a scoped node, or if you wish to explicitly override this behaviour, use the EvaluationContext.WithResourceOverrides() method.")] - public EvaluationContext(ITypedElement? resource) : this(resource, null) { } + public EvaluationContext(IScopedNode? resource) : this(resource, null) { } /// /// Create an EvaluationContext with the given value for %resource and %rootResource. @@ -30,14 +31,14 @@ public EvaluationContext(ITypedElement? resource) : this(resource, null) { } /// The data that will be represented by %resource. /// The data that will be represented by %rootResource. [Obsolete("%resource and %rootResource are inferred from scoped nodes by the evaluator. If you do not have access to a scoped node, or if you wish to explicitly override this behaviour, use the EvaluationContext.WithResourceOverrides() method.")] - public EvaluationContext(ITypedElement? resource, ITypedElement? rootResource) + public EvaluationContext(IScopedNode? resource, IScopedNode? rootResource) { Resource = resource; RootResource = rootResource ?? resource; } [Obsolete("%resource and %rootResource are inferred from scoped nodes by the evaluator. If you do not have access to a scoped node, or if you wish to explicitly override this behaviour, use the EvaluationContext.WithResourceOverrides() method. Environment can be set explicitly after construction of the base context")] - public EvaluationContext(ITypedElement? resource, ITypedElement? rootResource, IDictionary> environment) : this(resource, rootResource) + public EvaluationContext(IScopedNode? resource, IScopedNode? rootResource, IDictionary> environment) : this(resource, rootResource) { Environment = environment; } @@ -45,27 +46,27 @@ public EvaluationContext(ITypedElement? resource, ITypedElement? rootResource, I /// /// The data represented by %rootResource. /// - public ITypedElement? RootResource { get; set; } + public IScopedNode? RootResource { get; set; } /// /// The data represented by %resource. /// - public ITypedElement? Resource { get; set; } + public IScopedNode? Resource { get; set; } /// /// The environment variables that are available to the FHIRPath expressions. /// - public IDictionary> Environment { get; set; } = new Dictionary>(); + public IDictionary> Environment { get; set; } = new Dictionary>(); /// /// A delegate that handles the output for the trace() function. /// - public Action>? Tracer { get; set; } + public Action>? Tracer { get; set; } } public static class EvaluationContextExtensions { - public static T WithResourceOverrides(this T context, ITypedElement? resource, ITypedElement? rootResource = null) where T : EvaluationContext + public static T WithResourceOverrides(this T context, IScopedNode? resource, IScopedNode? rootResource = null) where T : EvaluationContext { context.Resource = resource; context.RootResource = rootResource ?? resource; diff --git a/src/Hl7.Fhir.Base/FhirPath/Expressions/Closure.cs b/src/Hl7.Fhir.Base/FhirPath/Expressions/Closure.cs index f6d789aa73..a81220b239 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Expressions/Closure.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Expressions/Closure.cs @@ -8,6 +8,7 @@ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using System.Collections.Generic; namespace Hl7.FhirPath.Expressions @@ -20,23 +21,14 @@ public Closure() public EvaluationContext EvaluationContext { get; private set; } - public static Closure Root(ITypedElement root, EvaluationContext ctx = null) + public static Closure Root(IScopedNode root, EvaluationContext ctx = null) { var newContext = ctx ?? new EvaluationContext(); - - var node = root as ScopedNode; - - newContext.Resource ??= node != null // if the value has been manually set, we do nothing. Otherwise, if the root is a scoped node: - ? getResourceFromNode(node) // we infer the resource from the scoped node - : (root?.Definition?.IsResource is true // if we do not have a scoped node, we see if this is even a resource to begin with - ? root // if it is, we use the root as the resource - : null // if not, this breaks the spec in every way (but we will still continue, hopefully we do not need %resource or %rootResource) - ); + + newContext.Resource ??= root.GetResourceContext(); // Same thing, but we copy the resource into the root resource if we cannot infer it from the node. - newContext.RootResource ??= node != null - ? getRootResourceFromNode(node) - : newContext.Resource; + newContext.RootResource ??= root.GetRootResourceContext(); var newClosure = new Closure() { EvaluationContext = ctx ?? new EvaluationContext() }; @@ -49,7 +41,7 @@ public static Closure Root(ITypedElement root, EvaluationContext ctx = null) newClosure.SetThis(input); newClosure.SetThat(input); - newClosure.SetIndex(ElementNode.CreateList(0)); + newClosure.SetIndex([new Integer(0)]); newClosure.SetOriginalContext(input); if (newContext.Resource != null) newClosure.SetResource(new[] { newContext.Resource }); @@ -58,9 +50,9 @@ public static Closure Root(ITypedElement root, EvaluationContext ctx = null) return newClosure; } - private Dictionary> _namedValues = new Dictionary>(); + private Dictionary> _namedValues = new (); - public virtual void SetValue(string name, IEnumerable value) + public virtual void SetValue(string name, IEnumerable value) { _namedValues.Remove(name); _namedValues.Add(name, value); @@ -79,10 +71,10 @@ public virtual Closure Nest() } - public virtual IEnumerable ResolveValue(string name) + public virtual IEnumerable ResolveValue(string name) { // First, try to directly get "normal" values - _namedValues.TryGetValue(name, out IEnumerable result); + _namedValues.TryGetValue(name, out IEnumerable result); if (result != null) return result; diff --git a/src/Hl7.Fhir.Base/FhirPath/Expressions/ClosureExtensions.cs b/src/Hl7.Fhir.Base/FhirPath/Expressions/ClosureExtensions.cs index 1854b16ff3..b4fdc02353 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Expressions/ClosureExtensions.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Expressions/ClosureExtensions.cs @@ -7,41 +7,42 @@ */ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using System.Collections.Generic; namespace Hl7.FhirPath.Expressions { internal static class ClosureExtensions { - public static IEnumerable GetThis(this Closure ctx) + public static IEnumerable GetThis(this Closure ctx) { return ctx.ResolveValue("builtin.this"); } - public static void SetThis(this Closure ctx, IEnumerable value) + public static void SetThis(this Closure ctx, IEnumerable value) { ctx.SetValue("builtin.this", value); } - public static IEnumerable GetTotal(this Closure ctx) + public static IEnumerable GetTotal(this Closure ctx) { return ctx.ResolveValue("builtin.total"); } - public static void SetTotal(this Closure ctx, IEnumerable value) + public static void SetTotal(this Closure ctx, IEnumerable value) { ctx.SetValue("builtin.total", value); } - public static IEnumerable GetThat(this Closure ctx) + public static IEnumerable GetThat(this Closure ctx) { return ctx.ResolveValue("builtin.that"); } - public static void SetThat(this Closure ctx, IEnumerable value) + public static void SetThat(this Closure ctx, IEnumerable value) { ctx.SetValue("builtin.that", value); } @@ -49,7 +50,7 @@ public static void SetThat(this Closure ctx, IEnumerable value) /// /// The original node that was passed to the evaluation engine before starting evaluation. /// - public static void SetOriginalContext(this Closure ctx, IEnumerable value) + public static void SetOriginalContext(this Closure ctx, IEnumerable value) { ctx.SetValue("context", value); } @@ -59,7 +60,7 @@ public static void SetOriginalContext(this Closure ctx, IEnumerable - public static void SetResource(this Closure ctx, IEnumerable value) + public static void SetResource(this Closure ctx, IEnumerable value) { ctx.SetValue("resource", value); } @@ -68,29 +69,29 @@ public static void SetResource(this Closure ctx, IEnumerable valu /// When a DomainResource contains another resource, and that contained resource is the focus (%resource) /// then %rootResource refers to the container resource. /// - public static void SetRootResource(this Closure ctx, IEnumerable value) + public static void SetRootResource(this Closure ctx, IEnumerable value) { ctx.SetValue("rootResource", value); } - public static IEnumerable GetOriginalContext(this Closure ctx) + public static IEnumerable GetOriginalContext(this Closure ctx) { return ctx.ResolveValue("context"); } - public static IEnumerable GetResource(this Closure ctx) + public static IEnumerable GetResource(this Closure ctx) { return ctx.ResolveValue("resource"); } - public static IEnumerable GetRootResource(this Closure ctx) + public static IEnumerable GetRootResource(this Closure ctx) { return ctx.ResolveValue("rootResource"); } - public static Closure Nest(this Closure ctx, IEnumerable input) + public static Closure Nest(this Closure ctx, IEnumerable input) { var nested = ctx.Nest(); nested.SetThat(input); @@ -98,12 +99,12 @@ public static Closure Nest(this Closure ctx, IEnumerable input) return nested; } - public static void SetIndex(this Closure ctx, IEnumerable value) + public static void SetIndex(this Closure ctx, IEnumerable value) { ctx.SetValue("builtin.index", value); } - public static IEnumerable GetIndex(this Closure ctx) + public static IEnumerable GetIndex(this Closure ctx) { return ctx.ResolveValue("builtin.index"); } diff --git a/src/Hl7.Fhir.Base/FhirPath/Expressions/DynaDispatcher.cs b/src/Hl7.Fhir.Base/FhirPath/Expressions/DynaDispatcher.cs index 5548c9a249..f25112f166 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Expressions/DynaDispatcher.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Expressions/DynaDispatcher.cs @@ -6,6 +6,7 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Utility; using System; using System.Collections.Generic; @@ -25,18 +26,18 @@ public DynaDispatcher(string name, SymbolTable scope) private readonly string _name; private readonly SymbolTable _scope; - public IEnumerable Dispatcher(Closure context, IEnumerable args) + public IEnumerable Dispatcher(Closure context, IEnumerable args) { - var actualArgs = new List>(); + var actualArgs = new List>(); var focus = args.First()(context, InvokeeFactory.EmptyArgs); - if (!focus.Any()) return ElementNode.EmptyList; + if (!focus.Any()) return []; actualArgs.Add(focus); var newCtx = context.Nest(focus); actualArgs.AddRange(args.Skip(1).Select(a => a(newCtx, InvokeeFactory.EmptyArgs))); - if (actualArgs.Any(aa => !aa.Any())) return ElementNode.EmptyList; + if (actualArgs.Any(aa => !aa.Any())) return []; var entry = _scope.DynamicGet(_name, actualArgs); diff --git a/src/Hl7.Fhir.Base/FhirPath/Expressions/EvaluatorVisitor.cs b/src/Hl7.Fhir.Base/FhirPath/Expressions/EvaluatorVisitor.cs index 4c4fa60ab8..96a53c604f 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Expressions/EvaluatorVisitor.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Expressions/EvaluatorVisitor.cs @@ -6,10 +6,12 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Utility; using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using FP = Hl7.FhirPath.Expressions; namespace Hl7.FhirPath.Expressions @@ -24,9 +26,10 @@ public EvaluatorVisitor(SymbolTable symbols) } + [TemporarilyChanged] // we should refactor ConstantExpression to use IScopedNode too... public override Invokee VisitConstant(FP.ConstantExpression expression) { - return InvokeeFactory.Return(ElementNode.ForPrimitive(expression.Value)); + return InvokeeFactory.Return(ElementNode.ForPrimitive(expression.Value).ToScopedNode()); } public override Invokee VisitFunctionCall(FP.FunctionCallExpression expression) @@ -47,7 +50,7 @@ public override Invokee VisitFunctionCall(FP.FunctionCallExpression expression) public override Invokee VisitNewNodeListInit(FP.NewNodeListInitExpression expression) { - return InvokeeFactory.Return(ElementNode.EmptyList); + return InvokeeFactory.Return([]); } public override Invokee VisitVariableRef(FP.VariableRefExpression expression) @@ -82,7 +85,7 @@ public override Invokee VisitVariableRef(FP.VariableRefExpression expression) return chainResolves; - IEnumerable chainResolves(Closure context, IEnumerable invokees) + IEnumerable chainResolves(Closure context, IEnumerable invokees) { return context.ResolveValue(expression.Name) ?? resolve(Symbols, expression.Name, Enumerable.Empty())(context, []); } diff --git a/src/Hl7.Fhir.Base/FhirPath/Expressions/Invokee.cs b/src/Hl7.Fhir.Base/FhirPath/Expressions/Invokee.cs index a89c57d534..c414fbeed8 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Expressions/Invokee.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Expressions/Invokee.cs @@ -7,6 +7,7 @@ */ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.FhirPath.Functions; using System; using System.Collections.Generic; @@ -14,43 +15,42 @@ namespace Hl7.FhirPath.Expressions { - internal delegate IEnumerable Invokee(Closure context, IEnumerable arguments); + internal delegate IEnumerable Invokee(Closure context, IEnumerable arguments); internal static class InvokeeFactory { public static readonly IEnumerable EmptyArgs = Enumerable.Empty(); - - public static IEnumerable GetThis(Closure context, IEnumerable _) + public static IEnumerable GetThis(Closure context, IEnumerable _) { return context.GetThis(); } - public static IEnumerable GetTotal(Closure context, IEnumerable _) + public static IEnumerable GetTotal(Closure context, IEnumerable _) { return context.GetTotal(); } - public static IEnumerable GetContext(Closure context, IEnumerable _) + public static IEnumerable GetContext(Closure context, IEnumerable _) { return context.GetOriginalContext(); } - public static IEnumerable GetResource(Closure context, IEnumerable _) + public static IEnumerable GetResource(Closure context, IEnumerable _) { return context.GetResource(); } - public static IEnumerable GetRootResource(Closure context, IEnumerable arguments) + public static IEnumerable GetRootResource(Closure context, IEnumerable arguments) { return context.GetRootResource(); } - public static IEnumerable GetThat(Closure context, IEnumerable _) + public static IEnumerable GetThat(Closure context, IEnumerable _) { return context.GetThat(); } - public static IEnumerable GetIndex(Closure context, IEnumerable args) + public static IEnumerable GetIndex(Closure context, IEnumerable args) { return context.GetIndex(); @@ -59,10 +59,7 @@ public static IEnumerable GetIndex(Closure context, IEnumerable(Func func) { - return (ctx, args) => - { - return Typecasts.CastTo>(func()); - }; + return (_, _) => Typecasts.CastTo>(func()); } public static Invokee Wrap(Func func, bool propNull) @@ -72,14 +69,14 @@ public static Invokee Wrap(Func func, bool propNull) if (typeof(A) != typeof(EvaluationContext)) { var focus = args.First()(ctx, InvokeeFactory.EmptyArgs); - if (propNull && !focus.Any()) return ElementNode.EmptyList; + if (propNull && !focus.Any()) return []; - return Typecasts.CastTo>(func(Typecasts.CastTo(focus))); + return Typecasts.CastTo>(func(Typecasts.CastTo(focus))); } else { A lastPar = (A)(object)ctx.EvaluationContext; - return Typecasts.CastTo>(func(lastPar)); + return Typecasts.CastTo>(func(lastPar)); } }; } @@ -90,7 +87,7 @@ internal static Invokee WrapWithPropNullForFocus(Func fu { // propagate only null for focus var focus = args.First()(ctx, InvokeeFactory.EmptyArgs); - if (!focus.Any()) return ElementNode.EmptyList; + if (!focus.Any()) return[]; return Wrap(func, false)(ctx, args); }; @@ -101,19 +98,19 @@ public static Invokee Wrap(Func func, bool propNull) return (ctx, args) => { var focus = args.First()(ctx, InvokeeFactory.EmptyArgs); - if (propNull && !focus.Any()) return ElementNode.EmptyList; + if (propNull && !focus.Any()) return []; if (typeof(B) != typeof(EvaluationContext)) { var argA = args.Skip(1).First()(ctx, InvokeeFactory.EmptyArgs); - if (propNull && !argA.Any()) return ElementNode.EmptyList; + if (propNull && !argA.Any()) return []; - return Typecasts.CastTo>(func(Typecasts.CastTo(focus), Typecasts.CastTo(argA))); + return Typecasts.CastTo>(func(Typecasts.CastTo(focus), Typecasts.CastTo(argA))); } else { B lastPar = (B)(object)ctx.EvaluationContext; - return Typecasts.CastTo>(func(Typecasts.CastTo(focus), lastPar)); + return Typecasts.CastTo>(func(Typecasts.CastTo(focus), lastPar)); } }; } @@ -123,23 +120,23 @@ public static Invokee Wrap(Func func, bool propNull) return (ctx, args) => { var focus = args.First()((Closure)ctx, InvokeeFactory.EmptyArgs); - if (propNull && !focus.Any()) return ElementNode.EmptyList; + if (propNull && !focus.Any()) return []; var argA = args.Skip(1).First()(ctx, InvokeeFactory.EmptyArgs); - if (propNull && !argA.Any()) return ElementNode.EmptyList; + if (propNull && !argA.Any()) return []; if (typeof(C) != typeof(EvaluationContext)) { var argB = args.Skip(2).First()(ctx, InvokeeFactory.EmptyArgs); - if (propNull && !argB.Any()) return ElementNode.EmptyList; + if (propNull && !argB.Any()) return []; - return Typecasts.CastTo>(func(Typecasts.CastTo(focus), Typecasts.CastTo(argA), + return Typecasts.CastTo>(func(Typecasts.CastTo(focus), Typecasts.CastTo(argA), Typecasts.CastTo(argB))); } else { C lastPar = (C)(object)ctx.EvaluationContext; - return Typecasts.CastTo>(func(Typecasts.CastTo(focus), + return Typecasts.CastTo>(func(Typecasts.CastTo(focus), Typecasts.CastTo(argA), lastPar)); } }; @@ -150,26 +147,26 @@ public static Invokee Wrap(Func func, bool propNul return (ctx, args) => { var focus = args.First()((Closure)ctx, InvokeeFactory.EmptyArgs); - if (propNull && !focus.Any()) return ElementNode.EmptyList; + if (propNull && !focus.Any()) return []; var argA = args.Skip(1).First()(ctx, InvokeeFactory.EmptyArgs); - if (propNull && !argA.Any()) return ElementNode.EmptyList; + if (propNull && !argA.Any()) return []; var argB = args.Skip(2).First()(ctx, InvokeeFactory.EmptyArgs); - if (propNull && !argB.Any()) return ElementNode.EmptyList; + if (propNull && !argB.Any()) return []; if (typeof(D) != typeof(EvaluationContext)) { var argC = args.Skip(3).First()(ctx, InvokeeFactory.EmptyArgs); - if (propNull && !argC.Any()) return ElementNode.EmptyList; + if (propNull && !argC.Any()) return []; - return Typecasts.CastTo>(func(Typecasts.CastTo(focus), + return Typecasts.CastTo>(func(Typecasts.CastTo(focus), Typecasts.CastTo(argA), Typecasts.CastTo(argB), Typecasts.CastTo(argC))); } else { D lastPar = (D)(object)ctx.EvaluationContext; - return Typecasts.CastTo>(func(Typecasts.CastTo(focus), + return Typecasts.CastTo>(func(Typecasts.CastTo(focus), Typecasts.CastTo(argA), Typecasts.CastTo(argB), lastPar)); } @@ -186,18 +183,18 @@ public static Invokee WrapLogic(Func, Func, bool?> func) var right = args.Skip(2).First(); // Return function that actually executes the Invokee at the last moment - return Typecasts.CastTo>(func(() => left(ctx, InvokeeFactory.EmptyArgs).BooleanEval(), () => right(ctx, InvokeeFactory.EmptyArgs).BooleanEval())); + return Typecasts.CastTo>(func(() => left(ctx, InvokeeFactory.EmptyArgs).BooleanEval(), () => right(ctx, InvokeeFactory.EmptyArgs).BooleanEval())); }; } - public static Invokee Return(ITypedElement value) + public static Invokee Return(IScopedNode value) { - return (_, __) => (new[] { (ITypedElement)value }); + return (_, _) => [value]; } - public static Invokee Return(IEnumerable value) + public static Invokee Return(IEnumerable value) { - return (_, __) => value; + return (_, _) => value; } public static Invokee Invoke(string functionName, IEnumerable arguments, Invokee invokee) @@ -207,7 +204,7 @@ public static Invokee Invoke(string functionName, IEnumerable arguments try { var wrappedArguments = arguments.Skip(1).Select(wrapWithNextContext); - return invokee(ctx, [arguments.First(),.. wrappedArguments]); + return invokee(ctx, [arguments.First(), ..wrappedArguments]); } catch (Exception e) { diff --git a/src/Hl7.Fhir.Base/FhirPath/Expressions/SymbolTable.cs b/src/Hl7.Fhir.Base/FhirPath/Expressions/SymbolTable.cs index 7b58ae46d7..605ea30070 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Expressions/SymbolTable.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Expressions/SymbolTable.cs @@ -7,6 +7,7 @@ */ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -159,10 +160,10 @@ public static void AddLogic(this SymbolTable table, string name, Func f) => f.Any()); t.Add("count", (IEnumerable f) => f.Count()); - t.Add("trace", (IEnumerable f, string name, EvaluationContext ctx) + t.Add("trace", (IEnumerable f, string name, EvaluationContext ctx) => f.Trace(name, ctx)); - t.Add("allTrue", (IEnumerable f) => f.All(e => e.Value as bool? == true)); - t.Add("anyTrue", (IEnumerable f) => f.Any(e => e.Value as bool? == true)); - t.Add("allFalse", (IEnumerable f) => f.All(e => e.Value as bool? == false)); - t.Add("anyFalse", (IEnumerable f) => f.Any(e => e.Value as bool? == false)); - t.Add("combine", (IEnumerable l, IEnumerable r) => l.Concat(r)); - t.Add("binary.|", (object _, IEnumerable l, IEnumerable r) => l.DistinctUnion(r)); - t.Add("union", (IEnumerable l, IEnumerable r) => l.DistinctUnion(r)); - t.Add("binary.contains", (object _, IEnumerable a, ITypedElement b) => a.Contains(b)); - t.Add("binary.in", (object _, ITypedElement a, IEnumerable b) => b.Contains(a)); - t.Add("distinct", (IEnumerable f) => f.Distinct()); - t.Add("isDistinct", (IEnumerable f) => f.IsDistinct()); - t.Add("subsetOf", (IEnumerable f, IEnumerable a) => f.SubsetOf(a)); - t.Add("supersetOf", (IEnumerable f, IEnumerable a) => a.SubsetOf(f)); - t.Add("intersect", (IEnumerable f, IEnumerable a) => f.Intersect(a)); - t.Add("exclude", (IEnumerable f, IEnumerable a) => f.Exclude(a)); + t.Add("allTrue", (IEnumerable f) => f.All(e => e.Value as bool? == true)); + t.Add("anyTrue", (IEnumerable f) => f.Any(e => e.Value as bool? == true)); + t.Add("allFalse", (IEnumerable f) => f.All(e => e.Value as bool? == false)); + t.Add("anyFalse", (IEnumerable f) => f.Any(e => e.Value as bool? == false)); + t.Add("combine", (IEnumerable l, IEnumerable r) => l.Concat(r)); + t.Add("binary.|", (object _, IEnumerable l, IEnumerable r) => l.DistinctUnion(r)); + t.Add("union", (IEnumerable l, IEnumerable r) => l.DistinctUnion(r)); + t.Add("binary.contains", (object _, IEnumerable a, IScopedNode b) => a.Contains(b)); + t.Add("binary.in", (object _, IScopedNode a, IEnumerable b) => b.Contains(a)); + t.Add("distinct", (IEnumerable f) => f.Distinct()); + t.Add("isDistinct", (IEnumerable f) => f.IsDistinct()); + t.Add("subsetOf", (IEnumerable f, IEnumerable a) => f.SubsetOf(a)); + t.Add("supersetOf", (IEnumerable f, IEnumerable a) => a.SubsetOf(f)); + t.Add("intersect", (IEnumerable f, IEnumerable a) => f.Intersect(a)); + t.Add("exclude", (IEnumerable f, IEnumerable a) => f.Exclude(a)); t.Add("today", (object _) => P.Date.Today()); t.Add("now", (object _) => P.DateTime.Now()); @@ -55,21 +57,21 @@ public static SymbolTable AddStandardFP(this SymbolTable t) t.Add("binary.&", (object _, string a, string b) => (a ?? "") + (b ?? "")); - t.Add(new CallSignature("iif", typeof(IEnumerable), typeof(object), typeof(bool?), typeof(Invokee), typeof(Invokee)), runIif); - t.Add(new CallSignature("iif", typeof(IEnumerable), typeof(object), typeof(bool?), typeof(Invokee)), runIif); + t.Add(new CallSignature("iif", typeof(IEnumerable), typeof(object), typeof(bool?), typeof(Invokee), typeof(Invokee)), runIif); + t.Add(new CallSignature("iif", typeof(IEnumerable), typeof(object), typeof(bool?), typeof(Invokee)), runIif); // Functions that use normal null propagation and work with the focus (buy may ignore it) - t.Add("not", (IEnumerable f) => f.Not(), doNullProp: true); - // t.Add("builtin.children", (IEnumerable f, string a) => f.Navigate(a), doNullProp: true); + t.Add("not", (IEnumerable f) => f.Not(), doNullProp: true); + // t.Add("builtin.children", (IEnumerable f, string a) => f.Navigate(a), doNullProp: true); t.AddBuiltinChildren(); - t.Add("children", (IEnumerable f) => f.Children(), doNullProp: true); - t.Add("descendants", (IEnumerable f) => f.Descendants(), doNullProp: true); + t.Add("children", (IEnumerable f) => f.Children(), doNullProp: true); + t.Add("descendants", (IEnumerable f) => f.Descendants().ToScopedNodes(), doNullProp: true); - t.Add("binary.=", (object f, IEnumerable a, IEnumerable b) => a.IsEqualTo(b), doNullProp: true); - t.Add("binary.!=", (object f, IEnumerable a, IEnumerable b) => !a.IsEqualTo(b), doNullProp: true); - t.Add("binary.~", (object f, IEnumerable a, IEnumerable b) => a.IsEquivalentTo(b), doNullProp: false); - t.Add("binary.!~", (object f, IEnumerable a, IEnumerable b) => !a.IsEquivalentTo(b), doNullProp: false); + t.Add("binary.=", (object f, IEnumerable a, IEnumerable b) => a.IsEqualTo(b), doNullProp: true); + t.Add("binary.!=", (object f, IEnumerable a, IEnumerable b) => !a.IsEqualTo(b), doNullProp: true); + t.Add("binary.~", (object f, IEnumerable a, IEnumerable b) => a.IsEquivalentTo(b), doNullProp: false); + t.Add("binary.!~", (object f, IEnumerable a, IEnumerable b) => !a.IsEquivalentTo(b), doNullProp: false); t.Add("unary.-", (object f, int a) => -a, doNullProp: true); t.Add("unary.-", (object f, long a) => -a, doNullProp: true); @@ -116,13 +118,13 @@ public static SymbolTable AddStandardFP(this SymbolTable t) t.Add("binary.<=", (object f, P.Any a, P.Any b) => EqualityOperators.Compare(a, b, "<="), doNullProp: true); t.Add("binary.>=", (object f, P.Any a, P.Any b) => EqualityOperators.Compare(a, b, ">="), doNullProp: true); - t.Add("single", (IEnumerable f) => f.Single(), doNullProp: true); - t.Add("skip", (IEnumerable f, long a) => f.Skip((int)a), doNullProp: true); - t.Add("first", (IEnumerable f) => f.First(), doNullProp: true); - t.Add("last", (IEnumerable f) => f.Last(), doNullProp: true); - t.Add("tail", (IEnumerable f) => f.Tail(), doNullProp: true); - t.Add("take", (IEnumerable f, long a) => f.Take((int)a), doNullProp: true); - t.Add("builtin.item", (IEnumerable f, long a) => f.Item((int)a), doNullProp: true); + t.Add("single", (IEnumerable f) => f.Single(), doNullProp: true); + t.Add("skip", (IEnumerable f, long a) => f.Skip((int)a), doNullProp: true); + t.Add("first", (IEnumerable f) => f.First(), doNullProp: true); + t.Add("last", (IEnumerable f) => f.Last(), doNullProp: true); + t.Add("tail", (IEnumerable f) => f.Tail(), doNullProp: true); + t.Add("take", (IEnumerable f, long a) => f.Take((int)a), doNullProp: true); + t.Add("builtin.item", (IEnumerable f, long a) => f.Item((int)a), doNullProp: true); t.Add("toBoolean", (P.Any f) => f.ToBoolean(), doNullProp: true); t.Add("convertsToBoolean", (P.Any f) => f.ConvertsToBoolean(), doNullProp: true); @@ -165,12 +167,12 @@ public static SymbolTable AddStandardFP(this SymbolTable t) t.Add("replace", (string f, string regex, string subst) => f.FpReplace(regex, subst), doNullProp: true); t.Add("length", (string f) => f.Length, doNullProp: true); t.Add("split", (string f, string seperator) => f.FpSplit(seperator), doNullProp: true); - t.Add("join", (IEnumerable f, string separator) => f.FpJoin(separator), doNullProp: true); - t.Add("join", (IEnumerable f) => f.FpJoin(), doNullProp: true); - t.Add("indexOf", (IEnumerable f, ITypedElement elem, int start) => f.IndexOf(elem, start), doNullProp: true); - t.Add("indexOf", (IEnumerable f, ITypedElement elem) => f.IndexOf(elem), doNullProp: true); - t.Add("lastIndexOf", (IEnumerable f, ITypedElement elem, int start) => f.LastIndexOf(elem, start), doNullProp: true); - t.Add("lastIndexOf", (IEnumerable f, ITypedElement elem) => f.LastIndexOf(elem), doNullProp: true); + t.Add("join", (IEnumerable f, string separator) => f.FpJoin(separator), doNullProp: true); + t.Add("join", (IEnumerable f) => f.FpJoin(), doNullProp: true); + t.Add("indexOf", (IEnumerable f, IScopedNode elem, int start) => f.IndexOf(elem, start), doNullProp: true); + t.Add("indexOf", (IEnumerable f, IScopedNode elem) => f.IndexOf(elem), doNullProp: true); + t.Add("lastIndexOf", (IEnumerable f, IScopedNode elem, int start) => f.LastIndexOf(elem, start), doNullProp: true); + t.Add("lastIndexOf", (IEnumerable f, IScopedNode elem) => f.LastIndexOf(elem), doNullProp: true); // Math functions t.Add("abs", (decimal f) => Math.Abs(f), doNullProp: true); @@ -187,17 +189,17 @@ public static SymbolTable AddStandardFP(this SymbolTable t) t.Add("truncate", (decimal f) => Math.Truncate((double)f), doNullProp: true); // The next two functions existed pre-normative, so we have kept them. - t.Add("is", (ITypedElement f, string name) => f.Is(name), doNullProp: true); - t.Add("as", (IEnumerable f, string name) => f.FilterType(name), doNullProp: true); + t.Add("is", (IScopedNode f, string name) => f.Is(name), doNullProp: true); + t.Add("as", (IEnumerable f, string name) => f.FilterType(name), doNullProp: true); - t.Add("ofType", (IEnumerable f, string name) => f.FilterType(name), doNullProp: true); - t.Add("binary.is", (object f, ITypedElement left, string name) => left.Is(name), doNullProp: true); - t.Add("binary.as", (object f, IEnumerable left, string name) => left.FilterType(name), doNullProp: true); + t.Add("ofType", (IEnumerable f, string name) => f.FilterType(name), doNullProp: true); + t.Add("binary.is", (object f, IScopedNode left, string name) => left.Is(name), doNullProp: true); + t.Add("binary.as", (object f, IEnumerable left, string name) => left.FilterType(name), doNullProp: true); // Kept for backwards compatibility, but no longer part of the spec - t.Add("binary.as", (object f, IEnumerable left, string name) => left.FilterType(name), doNullProp: true); + t.Add("binary.as", (object f, IEnumerable left, string name) => left.FilterType(name), doNullProp: true); - t.Add("extension", (IEnumerable f, string url) => f.Extension(url), doNullProp: true); + t.Add("extension", (IEnumerable f, string url) => f.Extension(url), doNullProp: true); // Logic operators do not use null propagation and may do short-cut eval t.AddLogic("binary.and", (a, b) => a.And(b)); @@ -206,18 +208,18 @@ public static SymbolTable AddStandardFP(this SymbolTable t) t.AddLogic("binary.implies", (a, b) => a.Implies(b)); // Special late-bound functions - t.Add(new CallSignature("where", typeof(IEnumerable), typeof(object), typeof(Invokee)), runWhere); - t.Add(new CallSignature("select", typeof(IEnumerable), typeof(object), typeof(Invokee)), runSelect); + t.Add(new CallSignature("where", typeof(IEnumerable), typeof(object), typeof(Invokee)), runWhere); + t.Add(new CallSignature("select", typeof(IEnumerable), typeof(object), typeof(Invokee)), runSelect); t.Add(new CallSignature("all", typeof(bool), typeof(object), typeof(Invokee)), runAll); t.Add(new CallSignature("any", typeof(bool), typeof(object), typeof(Invokee)), runAny); t.Add(new CallSignature("exists", typeof(bool), typeof(object), typeof(Invokee)), runAny); - t.Add(new CallSignature("repeat", typeof(IEnumerable), typeof(object), typeof(Invokee)), runRepeat); - t.Add(new CallSignature("trace", typeof(IEnumerable), typeof(string), typeof(object), typeof(Invokee)), Trace); - t.Add(new CallSignature("defineVariable", typeof(IEnumerable), typeof(object), typeof(string)), DefineVariable); - t.Add(new CallSignature("defineVariable", typeof(IEnumerable), typeof(object), typeof(string), typeof(Invokee)), DefineVariable); + t.Add(new CallSignature("repeat", typeof(IEnumerable), typeof(object), typeof(Invokee)), runRepeat); + t.Add(new CallSignature("trace", typeof(IEnumerable), typeof(string), typeof(object), typeof(Invokee)), Trace); + t.Add(new CallSignature("defineVariable", typeof(IEnumerable), typeof(object), typeof(string)), DefineVariable); + t.Add(new CallSignature("defineVariable", typeof(IEnumerable), typeof(object), typeof(string), typeof(Invokee)), DefineVariable); - t.Add(new CallSignature("aggregate", typeof(IEnumerable), typeof(Invokee), typeof(Invokee)), runAggregate); - t.Add(new CallSignature("aggregate", typeof(IEnumerable), typeof(Invokee), typeof(Invokee), typeof(Invokee)), runAggregate); + t.Add(new CallSignature("aggregate", typeof(IEnumerable), typeof(Invokee), typeof(Invokee)), runAggregate); + t.Add(new CallSignature("aggregate", typeof(IEnumerable), typeof(Invokee), typeof(Invokee), typeof(Invokee)), runAggregate); t.AddVar("sct", "http://snomed.info/sct"); t.AddVar("loinc", "http://loinc.org"); @@ -230,7 +232,7 @@ public static SymbolTable AddStandardFP(this SymbolTable t) } /// - /// With the regular Add extension methods, a Wrap is added to each argument to turn it into IEnumerable<ITypedElement>. + /// With the regular Add extension methods, a Wrap is added to each argument to turn it into IEnumerable<IScopedNode>. /// For 'builtin.children' we know that the focus and the result are already of the correct type, /// so we created an optimized implementation avoiding the Wrap. /// @@ -238,8 +240,8 @@ public static SymbolTable AddStandardFP(this SymbolTable t) internal static void AddBuiltinChildren(this SymbolTable table) { table.Add(new CallSignature("builtin.children", - typeof(IEnumerable), - typeof(IEnumerable), + typeof(IEnumerable), + typeof(IEnumerable), typeof(string)), ( ctx, invokees) => { @@ -262,11 +264,11 @@ private static string getCoreValueSetUrl(string id) return "http://hl7.org/fhir/ValueSet/" + id; } - private static IEnumerable runAggregate(Closure ctx, IEnumerable arguments) + private static IEnumerable runAggregate(Closure ctx, IEnumerable arguments) { var focus = arguments.First()(ctx, InvokeeFactory.EmptyArgs); var incrExpre = arguments.Skip(1).First(); - IEnumerable initialValue = ElementNode.EmptyList; + IEnumerable initialValue = []; if (arguments.Count() > 2) { var initialValueExpr = arguments.Skip(2).First(); @@ -276,9 +278,9 @@ private static IEnumerable runAggregate(Closure ctx, IEnumerable< var totalContext = ctx.Nest(); totalContext.SetTotal(initialValue); - foreach (ITypedElement element in focus) + foreach (IScopedNode element in focus) { - var newFocus = ElementNode.CreateList(element); + IEnumerable newFocus = [element]; var newContext = totalContext.Nest(newFocus); newContext.SetThis(newFocus); newContext.SetTotal(totalContext.GetTotal()); @@ -289,7 +291,7 @@ private static IEnumerable runAggregate(Closure ctx, IEnumerable< return totalContext.GetTotal(); } - private static IEnumerable Trace(Closure ctx, IEnumerable arguments) + private static IEnumerable Trace(Closure ctx, IEnumerable arguments) { var focus = arguments.First()(ctx, InvokeeFactory.EmptyArgs); string name = arguments.Skip(1).First()(ctx, InvokeeFactory.EmptyArgs).FirstOrDefault()?.Value as string; @@ -301,7 +303,7 @@ private static IEnumerable Trace(Closure ctx, IEnumerable DefineVariable(Closure ctx, IEnumerable arguments) + private static IEnumerable DefineVariable(Closure ctx, IEnumerable arguments) { Invokee[] enumerable = arguments as Invokee[] ?? arguments.ToArray(); var focus = enumerable[0](ctx, InvokeeFactory.EmptyArgs); @@ -324,7 +326,7 @@ private static IEnumerable DefineVariable(Closure ctx, IEnumerabl return focus; } - private static IEnumerable runIif(Closure ctx, IEnumerable arguments) + private static IEnumerable runIif(Closure ctx, IEnumerable arguments) { // iif(criterion: expression, true-result: collection [, otherwise-result: collection]) : collection // note: short-circuit behavior is expected in this function @@ -342,26 +344,26 @@ private static IEnumerable runIif(Closure ctx, IEnumerable runWhere(Closure ctx, IEnumerable arguments) + private static IEnumerable runWhere(Closure ctx, IEnumerable arguments) { var focus = arguments.First()(ctx, InvokeeFactory.EmptyArgs); var lambda = arguments.Skip(1).First(); return CachedEnumerable.Create(runForeach()); - IEnumerable runForeach() + IEnumerable runForeach() { var index = 0; - foreach (ITypedElement element in focus) + foreach (IScopedNode element in focus) { - var newFocus = ElementNode.CreateList(element); + IEnumerable newFocus = [element]; var newContext = ctx.Nest(newFocus); newContext.SetThis(newFocus); - newContext.SetIndex(ElementNode.CreateList(index)); + newContext.SetIndex([new Integer(index)]); index++; if (lambda(newContext, InvokeeFactory.EmptyArgs).BooleanEval() == true) @@ -370,23 +372,23 @@ IEnumerable runForeach() } } - private static IEnumerable runSelect(Closure ctx, IEnumerable arguments) + private static IEnumerable runSelect(Closure ctx, IEnumerable arguments) { var focus = arguments.First()(ctx, InvokeeFactory.EmptyArgs); var lambda = arguments.Skip(1).First(); return CachedEnumerable.Create(runForeach()); - IEnumerable runForeach() + IEnumerable runForeach() { var index = 0; - foreach (ITypedElement element in focus) + foreach (IScopedNode element in focus) { - var newFocus = ElementNode.CreateList(element); + IEnumerable newFocus = [element]; var newContext = ctx.Nest(newFocus); newContext.SetThis(newFocus); - newContext.SetIndex(ElementNode.CreateList(index)); + newContext.SetIndex([new Integer(index)]); index++; var result = lambda(newContext, InvokeeFactory.EmptyArgs); @@ -396,12 +398,13 @@ IEnumerable runForeach() } } - private static IEnumerable runRepeat(Closure ctx, IEnumerable arguments) + [TemporarilyChanged] // We cast all of them to scoped nodes for now. This will not be necessary once we define a clear Equality operator for IScopedNode] + private static IEnumerable runRepeat(Closure ctx, IEnumerable arguments) { var newNodes = arguments.First()(ctx, InvokeeFactory.EmptyArgs).ToList(); var lambda = arguments.Skip(1).First(); - var fullResult = new List(); + var fullResult = new List(); while (newNodes.Any()) { @@ -409,12 +412,12 @@ private static IEnumerable runRepeat(Closure ctx, IEnumerable newFocus = [element]; var newContext = ctx.Nest(newFocus); newContext.SetThis(newFocus); - newContext.SetIndex(ElementNode.CreateList(index)); + newContext.SetIndex([new Integer(index)]); index++; var candidates = lambda(newContext, InvokeeFactory.EmptyArgs); @@ -429,47 +432,47 @@ private static IEnumerable runRepeat(Closure ctx, IEnumerable runAll(Closure ctx, IEnumerable arguments) + private static IEnumerable runAll(Closure ctx, IEnumerable arguments) { var focus = arguments.First()(ctx, InvokeeFactory.EmptyArgs); var lambda = arguments.Skip(1).First(); var index = 0; - foreach (ITypedElement element in focus) + foreach (IScopedNode element in focus) { - var newFocus = ElementNode.CreateList(element); + IEnumerable newFocus = [element]; var newContext = ctx.Nest(newFocus); newContext.SetThis(newFocus); - newContext.SetIndex(ElementNode.CreateList(index)); + newContext.SetIndex([new Integer(index)]); index++; var result = lambda(newContext, InvokeeFactory.EmptyArgs).BooleanEval(); - if (result == null) return ElementNode.EmptyList; - if (result == false) return ElementNode.CreateList(false); + if (result == null) return []; + if (result == false) return [new FhirBoolean(false)]; } - return ElementNode.CreateList(true); + return [new FhirBoolean(true)]; } - private static IEnumerable runAny(Closure ctx, IEnumerable arguments) + private static IEnumerable runAny(Closure ctx, IEnumerable arguments) { var focus = arguments.First()(ctx, InvokeeFactory.EmptyArgs); var lambda = arguments.Skip(1).First(); var index = 0; - foreach (ITypedElement element in focus) + foreach (IScopedNode element in focus) { - var newFocus = ElementNode.CreateList(element); + IEnumerable newFocus = [element]; var newContext = ctx.Nest(newFocus); newContext.SetThis(newFocus); - newContext.SetIndex(ElementNode.CreateList(index)); + newContext.SetIndex([new Integer(index)]); index++; var result = lambda(newContext, InvokeeFactory.EmptyArgs).BooleanEval(); - if (result == true) return ElementNode.CreateList(true); + if (result == true) return [new FhirBoolean(true)]; } - return ElementNode.CreateList(false); + return [new FhirBoolean(false)]; } } } diff --git a/src/Hl7.Fhir.Base/FhirPath/Expressions/Typecasts.cs b/src/Hl7.Fhir.Base/FhirPath/Expressions/Typecasts.cs index d1c6080dec..e242592a1d 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Expressions/Typecasts.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Expressions/Typecasts.cs @@ -6,14 +6,18 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Utility; using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using P = Hl7.Fhir.ElementModel.Types; +#pragma warning disable CS0612 // Type or member is obsolete namespace Hl7.FhirPath.Expressions { + [TemporarilyChanged] // Disabled pragmas until we have figured out an alternative to InstanceType internal static class Typecasts { public delegate object Cast(object source); @@ -23,15 +27,15 @@ internal static class Typecasts private static Cast makeNativeCast(Type to) => source => Convert.ChangeType(source, to); - private static ITypedElement any2primitiveTypedElement(object source) => ElementNode.ForPrimitive(source); + private static IScopedNode any2primitiveTypedElement(object source) => ElementNode.ForPrimitive(source).ToScopedNode(); - private static IEnumerable any2List(object source) => ElementNode.CreateList(source); + private static IEnumerable any2List(object source) => ElementNode.CreateList(source).ToScopedNodes(); private static P.Quantity tryQuantity(object source) { - if (source is ITypedElement element) + if (source is IScopedNode element) { - if (element.InstanceType == "Quantity") + if (element.Type.HasFlag(NodeType.Quantity)) { // Need to downcast from a FHIR Quantity to a System.Quantity return ParseQuantity(element); @@ -44,7 +48,7 @@ private static P.Quantity tryQuantity(object source) } - internal static P.Quantity ParseQuantity(ITypedElement qe) + internal static P.Quantity ParseQuantity(IScopedNode qe) { var value = qe.Children("value").SingleOrDefault()?.Value as decimal?; if (value == null) return null; @@ -60,10 +64,10 @@ private static Cast getImplicitCast(object f, Type to) if (to == typeof(object)) return id; if (from.CanBeTreatedAsType(to)) return id; - bool fromElemList = from.CanBeTreatedAsType(typeof(IEnumerable)); - if (to == typeof(P.Quantity) && from.CanBeTreatedAsType(typeof(ITypedElement))) return tryQuantity; - if (to == typeof(ITypedElement) && (!fromElemList)) return any2primitiveTypedElement; - if (to == typeof(IEnumerable)) return any2List; + bool fromElemList = from.CanBeTreatedAsType(typeof(IEnumerable)); + if (to == typeof(P.Quantity) && from.CanBeTreatedAsType(typeof(IScopedNode))) return tryQuantity; + if (to == typeof(IScopedNode) && (!fromElemList)) return any2primitiveTypedElement; + if (to == typeof(IEnumerable)) return any2List; if (from == typeof(long) && (to == typeof(decimal) || to == typeof(decimal?))) return makeNativeCast(typeof(decimal)); if (from == typeof(long?) && to == typeof(decimal?)) return makeNativeCast(typeof(decimal?)); @@ -77,7 +81,7 @@ private static Cast getImplicitCast(object f, Type to) if (typeof(P.Any).IsAssignableFrom(to) && !fromElemList) { - if (f is ITypedElement te && te.InstanceType == "Quantity") return o => ParseQuantity((ITypedElement)o); + if (f is IScopedNode te && te.Type.HasFlag(NodeType.Quantity)) return o => ParseQuantity((IScopedNode)o); return o => P.Any.Convert(o); } @@ -101,26 +105,26 @@ private static Cast getImplicitCast(object f, Type to) /// The level to unbox to. /// /// The level of unboxing is specified using a type. The highest level - /// being an followed by - /// followed by a primitive runtime type. + /// being an followed by + /// followed by a primitive runtime type. /// internal static object UnboxTo(object instance, Type to) { if (instance == null) return null; - if (instance is IEnumerable list) + if (instance is IEnumerable list) { var cachedEnum = CachedEnumerable.Create(list); - if (to.CanBeTreatedAsType(typeof(IEnumerable))) return cachedEnum; + if (to.CanBeTreatedAsType(typeof(IEnumerable))) return cachedEnum; if (!cachedEnum.Any()) return null; if (cachedEnum.Count() == 1) instance = cachedEnum.Single(); } - if (instance is ITypedElement element) + if (instance is IScopedNode element) { - if (to.CanBeTreatedAsType(typeof(ITypedElement))) return instance; + if (to.CanBeTreatedAsType(typeof(IScopedNode))) return instance; if (to == typeof(object)) return instance; // HACK - We assume the primitives @@ -190,8 +194,8 @@ public static object CastTo(object source, Type to) } //if source == null, or unboxed source == null.... - if (to == typeof(IEnumerable)) - return ElementNode.EmptyList; + if (to == typeof(IEnumerable)) + return Array.Empty(); if (to.IsNullable()) return null; else @@ -209,14 +213,14 @@ public static bool IsNullable(this Type t) public static string ReadableFhirPathName(object value) { - if (value is IEnumerable ete) + if (value is IEnumerable ete) { var values = ete.ToList(); var types = ete.Select(te => ReadableFhirPathName(te)).Distinct(); return values.Count > 1 ? "collection of " + String.Join("/", types) : types.Single(); } - else if (value is ITypedElement te) + else if (value is IScopedNode te) return te.InstanceType; else return value.GetType().Name; @@ -224,9 +228,9 @@ public static string ReadableFhirPathName(object value) public static string ReadableTypeName(Type t) { - if (t.CanBeTreatedAsType(typeof(IEnumerable))) + if (t.CanBeTreatedAsType(typeof(IEnumerable))) return "collection"; - else if (t.CanBeTreatedAsType(typeof(ITypedElement))) + else if (t.CanBeTreatedAsType(typeof(IScopedNode))) return "any type"; else if (t.CanBeTreatedAsType(typeof(P.Any))) return "FhirPath type " + t.Name; diff --git a/src/Hl7.Fhir.Base/FhirPath/FhirEvaluationContext.cs b/src/Hl7.Fhir.Base/FhirPath/FhirEvaluationContext.cs index ed03e377e3..fe0333323f 100644 --- a/src/Hl7.Fhir.Base/FhirPath/FhirEvaluationContext.cs +++ b/src/Hl7.Fhir.Base/FhirPath/FhirEvaluationContext.cs @@ -19,55 +19,14 @@ namespace Hl7.Fhir.FhirPath { public class FhirEvaluationContext : EvaluationContext { - /// Creates a new instance with default property values. - [Obsolete("This method does not initialize any members and will be removed in a future version. Use the empty constructor instead.")] - public static new FhirEvaluationContext CreateDefault() => new(); - /// Default constructor. Creates a new instance with default property values. public FhirEvaluationContext() { } - /// - [Obsolete("%resource and %rootResource are inferred from scoped nodes by the evaluator. This behaviour is triggered when using the parameterless FhirEvaluationContext() constructor. " + - "If you do not have access to a scoped node, or if you wish to explicitly override this behaviour, use the FhirEvaluationContext.WithResourceOverrides() method.")] - public FhirEvaluationContext(ITypedElement resource) : base(resource) - { - } - - /// - [Obsolete("%resource and %rootResource are inferred from scoped nodes by the evaluator. This behaviour is triggered when using the parameterless FhirEvaluationContext() constructor. " + - "If you do not have access to a scoped node, or if you wish to explicitly override this behaviour, use the FhirEvaluationContext.WithResourceOverrides() method.")] - public FhirEvaluationContext(ITypedElement? resource, ITypedElement? rootResource) : base(resource, rootResource) - { - } - - /// - /// Create a FhirEvaluationContext with a resource and an environment. - /// - /// - /// - [Obsolete("%resource and %rootResource are inferred from scoped nodes by the evaluator. This behaviour is triggered when using the parameterless FhirEvaluationContext() constructor. " + - "If you do not have access to a scoped node, or if you wish to explicitly override this behaviour, use the FhirEvaluationContext.WithResourceOverrides() method.")] - public FhirEvaluationContext(ITypedElement resource, IDictionary> environment) : base(resource, null, environment) - { - } - - /// - /// Create a FhirEvaluationContext and also set the variables %resource and %rootResource to their correct values. - /// - /// input for determining the variables %resource and %rootResource - [Obsolete("%resource and %rootResource are inferred from scoped nodes by the evaluator. This behaviour is triggered when using the parameterless FhirEvaluationContext() constructor. " + - "If you do not have access to a scoped node, or if you wish to explicitly override this behaviour, use the FhirEvaluationContext.WithResourceOverrides() method.")] - public FhirEvaluationContext(ScopedNode node) - : this(toNearestResource(node)) - { - RootResource = Resource is ScopedNode sn ? sn.ResourceContext : node; - } - public ICodeValidationTerminologyService? TerminologyService { get; set; } - private static ITypedElement toNearestResource(ScopedNode node) + private static IScopedNode toNearestResource(ScopedNode node) { var scan = node; diff --git a/src/Hl7.Fhir.Base/FhirPath/FhirPathCompiler.cs b/src/Hl7.Fhir.Base/FhirPath/FhirPathCompiler.cs index 89310fab8e..5133fc9181 100644 --- a/src/Hl7.Fhir.Base/FhirPath/FhirPathCompiler.cs +++ b/src/Hl7.Fhir.Base/FhirPath/FhirPathCompiler.cs @@ -7,6 +7,7 @@ */ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.FhirPath.Expressions; using Hl7.FhirPath.Parser; using Hl7.FhirPath.Sprache; @@ -52,7 +53,7 @@ public CompiledExpression Compile(Expression expression) { Invokee inv = expression.ToEvaluator(Symbols); - return (ITypedElement focus, EvaluationContext ctx) => + return (focus, ctx) => { var closure = Closure.Root(focus, ctx); return inv(closure, InvokeeFactory.EmptyArgs); diff --git a/src/Hl7.Fhir.Base/FhirPath/FhirPathCompilerCache.cs b/src/Hl7.Fhir.Base/FhirPath/FhirPathCompilerCache.cs index b926cfda2e..585ea53cf1 100644 --- a/src/Hl7.Fhir.Base/FhirPath/FhirPathCompilerCache.cs +++ b/src/Hl7.Fhir.Base/FhirPath/FhirPathCompilerCache.cs @@ -9,6 +9,7 @@ #nullable enable using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Utility; using System.Collections.Generic; @@ -58,9 +59,8 @@ public void Clear() /// Expression which is to be evaluated /// Context of the evaluation /// The result(s) of the expression - public IEnumerable Select(ITypedElement input, string expression, EvaluationContext? ctx = null) + public IEnumerable Select(IScopedNode input, string expression, EvaluationContext? ctx = null) { - input = input.ToScopedNode(); var evaluator = GetCompiledExpression(expression); return evaluator(input, ctx ?? new EvaluationContext()); } @@ -72,9 +72,8 @@ public IEnumerable Select(ITypedElement input, string expression, /// Expression which is to be evaluated /// Context of the evaluation /// The single result of the expression, and null if the expression returns multiple results - public object? Scalar(ITypedElement input, string expression, EvaluationContext? ctx = null) + public object? Scalar(IScopedNode input, string expression, EvaluationContext? ctx = null) { - input = input.ToScopedNode(); var evaluator = GetCompiledExpression(expression); return evaluator.Scalar(input, ctx ?? new EvaluationContext()); } @@ -86,9 +85,8 @@ public IEnumerable Select(ITypedElement input, string expression, /// Expression which is to be evaluated /// Context of the evaluation /// True if expression returns true of empty, otheriwse false - public bool Predicate(ITypedElement input, string expression, EvaluationContext? ctx = null) + public bool Predicate(IScopedNode input, string expression, EvaluationContext? ctx = null) { - input = input.ToScopedNode(); var evaluator = GetCompiledExpression(expression); return evaluator.Predicate(input, ctx ?? new EvaluationContext()); } @@ -100,9 +98,8 @@ public bool Predicate(ITypedElement input, string expression, EvaluationContext? /// Expression which is to be evaluated /// Context of the evaluation /// True if expression returns true , and false if expression returns empty of false. - public bool IsTrue(ITypedElement input, string expression, EvaluationContext? ctx = null) + public bool IsTrue(IScopedNode input, string expression, EvaluationContext? ctx = null) { - input = input.ToScopedNode(); var evaluator = GetCompiledExpression(expression); return evaluator.IsTrue(input, ctx ?? new EvaluationContext()); } @@ -116,10 +113,8 @@ public bool IsTrue(ITypedElement input, string expression, EvaluationContext? ct /// Expression which is to be evaluated /// Context of the evaluation /// True if the result of an expression is equal to a given boolean, otherwise false - public bool IsBoolean(ITypedElement input, string expression, bool value, EvaluationContext? ctx = null) + public bool IsBoolean(IScopedNode input, string expression, bool value, EvaluationContext? ctx = null) { - input = input.ToScopedNode(); - var evaluator = GetCompiledExpression(expression); return evaluator.IsBoolean(value, input, ctx ?? new EvaluationContext()); } diff --git a/src/Hl7.Fhir.Base/FhirPath/Functions/CollectionOperators.cs b/src/Hl7.Fhir.Base/FhirPath/Functions/CollectionOperators.cs index 66ccee1e0b..eed5804928 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Functions/CollectionOperators.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Functions/CollectionOperators.cs @@ -7,15 +7,17 @@ */ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Utility; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; namespace Hl7.FhirPath.Functions { internal static class CollectionOperators { - public static bool? BooleanEval(this IEnumerable focus) + public static bool? BooleanEval(this IEnumerable focus) { if (!focus.Any()) return null; @@ -30,44 +32,47 @@ internal static class CollectionOperators } - public static bool Not(this IEnumerable focus) + public static bool Not(this IEnumerable focus) => focus.Count() > 1 ? throw Error.InvalidOperation($"Operator {nameof(Not)} is not applicable for collections with more than one item.") : !focus.BooleanEval().Value; - public static IEnumerable DistinctUnion(this IEnumerable a, IEnumerable b) + [TemporarilyChanged] // We cast all of them to scoped nodes for now. This will not be necessary once we define a clear Equality operator for IScopedNode + public static IEnumerable DistinctUnion(this IEnumerable a, IEnumerable b) => a.Union(b, EqualityOperators.TypedElementEqualityComparer); - public static IEnumerable Item(this IEnumerable focus, int index) + public static IEnumerable Item(this IEnumerable focus, int index) => focus.Skip(index).Take(1); - public static ITypedElement Last(this IEnumerable focus) + public static IScopedNode Last(this IEnumerable focus) => focus.Reverse().First(); - public static IEnumerable Tail(this IEnumerable focus) + public static IEnumerable Tail(this IEnumerable focus) => focus.Skip(1); - public static bool Contains(this IEnumerable focus, ITypedElement value) + public static bool Contains(this IEnumerable focus, IScopedNode value) => focus.Contains(value, EqualityOperators.TypedElementEqualityComparer); - public static IEnumerable Distinct(this IEnumerable focus) + [TemporarilyChanged] // We cast all of them to scoped nodes for now. This will not be necessary once we define a clear Equality operator for IScopedNode + public static IEnumerable Distinct(this IEnumerable focus) => focus.Distinct(EqualityOperators.TypedElementEqualityComparer); - public static bool IsDistinct(this IEnumerable focus) + public static bool IsDistinct(this IEnumerable focus) => focus.Distinct(EqualityOperators.TypedElementEqualityComparer).Count() == focus.Count(); - public static bool SubsetOf(this IEnumerable focus, IEnumerable other) + public static bool SubsetOf(this IEnumerable focus, IEnumerable other) => focus.All(fitem => other.Contains(fitem)); - public static IEnumerable Intersect(this IEnumerable focus, IEnumerable other) + [TemporarilyChanged] // We cast all of them to scoped nodes for now. This will not be necessary once we define a clear Equality operator for IScopedNode + public static IEnumerable Intersect(this IEnumerable focus, IEnumerable other) => focus.Intersect(other, EqualityOperators.TypedElementEqualityComparer); - public static IEnumerable Exclude(this IEnumerable focus, IEnumerable other) + public static IEnumerable Exclude(this IEnumerable focus, IEnumerable other) => focus.Where(f => !other.Contains(f)); - public static int IndexOf(this IEnumerable focus, ITypedElement item, int start = 0) + public static int IndexOf(this IEnumerable focus, IScopedNode item, int start = 0) { - var typedElements = focus as ITypedElement[] ?? focus.ToArray(); + var typedElements = focus as IScopedNode[] ?? focus.ToArray(); for (int i = start; i < typedElements.Length; i++) { if (EqualityOperators.TypedElementEqualityComparer.Equals(typedElements[i], item)) @@ -78,9 +83,9 @@ public static int IndexOf(this IEnumerable focus, ITypedElement i return -1; } - public static int LastIndexOf(this IEnumerable focus, ITypedElement item, int to = -1) + public static int LastIndexOf(this IEnumerable focus, IScopedNode item, int to = -1) { - var typedElements = focus as ITypedElement[] ?? focus.ToArray(); + var typedElements = focus as IScopedNode[] ?? focus.ToArray(); to = to < 0 ? typedElements.Count() - 1 : to; for (int i = to; i >= 0; i--) { @@ -93,27 +98,26 @@ public static int LastIndexOf(this IEnumerable focus, ITypedEleme } - public static IEnumerable Navigate(this IEnumerable elements, string name) + public static IEnumerable Navigate(this IEnumerable elements, string name) => elements.SelectMany(e => e.Navigate(name)); - public static IEnumerable Navigate(this ITypedElement element, string name) + public static IEnumerable Navigate(this IScopedNode element, string name) { if (char.IsUpper(name[0])) { // If we are at a resource, we should match a path that is possibly not rooted in the resource // (e.g. doing "name.family" on a Patient is equivalent to "Patient.name.family") - // Also we do some poor polymorphism here: Resource.meta.lastUpdated is also allowed. var baseClasses = new[] { "Resource", "DomainResource" }; if (element.InstanceType == name || baseClasses.Contains(name)) { - return new List() { element }; + return new List() { element }; } } return element.Children(name); } - public static string FpJoin(this IEnumerable collection, string separator = null) + public static string FpJoin(this IEnumerable collection, string separator = null) { //if the collection is empty return the empty result if (!collection.Any()) diff --git a/src/Hl7.Fhir.Base/FhirPath/Functions/EqualityOperators.cs b/src/Hl7.Fhir.Base/FhirPath/Functions/EqualityOperators.cs index 6ea56ca9c8..94aea76358 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Functions/EqualityOperators.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Functions/EqualityOperators.cs @@ -9,6 +9,7 @@ #nullable enable using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.FhirPath; using Hl7.FhirPath.Expressions; using System; @@ -20,7 +21,7 @@ namespace Hl7.FhirPath.Functions { public static class EqualityOperators { - public static bool? IsEqualTo(this IEnumerable left, IEnumerable right, bool compareNames = false) + public static bool? IsEqualTo(this IEnumerable left, IEnumerable right, bool compareNames = false) { // If one or both of the arguments is an empty collection, a comparison operator will return an empty collection. // (though we might handle this more generally with the null-propagating functionality of the compiler @@ -50,7 +51,7 @@ public static class EqualityOperators // comparison of the elements, while this one does a comparison on equivalence rules from the FhirPath spec, // which differ considerably (e.g. names are not compared, FHIR Quantity is compared to System.Quantity, // there are specified coercions, etc.) - public static bool? IsEqualTo(this ITypedElement left, ITypedElement right, bool compareNames = false) + public static bool? IsEqualTo(this IScopedNode left, IScopedNode right, bool compareNames = false) { // If one or both of the arguments is an empty collection, a comparison operator will return an empty collection. // (though we might handle this more generally with the null-propagating functionality of the compiler @@ -66,9 +67,9 @@ public static class EqualityOperators // TODO: this is actually a cast with knowledge of FHIR->System mappings, we don't want that here anymore // Convert quantities - if (left.InstanceType == "Quantity" && l == null) + if (left.Type.HasFlag(NodeType.Quantity) && l == null) l = Typecasts.ParseQuantity(left); - if (right.InstanceType == "Quantity" && r == null) + if (right.Type.HasFlag(NodeType.Quantity) && r == null) r = Typecasts.ParseQuantity(right); // Compare primitives (or extended primitives) @@ -129,7 +130,7 @@ static P.Any upcastOne(P.Any value, P.Any other) => }; } - public static bool IsEquivalentTo(this IEnumerable left, IEnumerable right, bool compareNames = false) + public static bool IsEquivalentTo(this IEnumerable left, IEnumerable right, bool compareNames = false) { var r = right.ToList(); int count = 0; @@ -144,7 +145,7 @@ public static bool IsEquivalentTo(this IEnumerable left, IEnumera } - public static bool IsEquivalentTo(this ITypedElement left, ITypedElement right, bool compareNames = false) + public static bool IsEquivalentTo(this IScopedNode left, IScopedNode right, bool compareNames = false) { // Note that because of this behaviour, we should switch off null-propagating behaviour of IsEquivalent to if (left is null && right is null) return true; @@ -157,9 +158,9 @@ public static bool IsEquivalentTo(this ITypedElement left, ITypedElement right, // TODO: this is actually a cast with knowledge of FHIR->System mappings, we don't want that here anymore // Convert quantities - if (left.InstanceType == "Quantity" && l == null) + if (left.Type.HasFlag(NodeType.Quantity) && l == null) l = Typecasts.ParseQuantity(left); - if (right.InstanceType == "Quantity" && r == null) + if (right.Type.HasFlag(NodeType.Quantity) && r == null) r = Typecasts.ParseQuantity(right); // Compare primitives (or extended primitives) @@ -181,7 +182,7 @@ public static bool IsEquivalentTo(this ITypedElement left, ITypedElement right, return false; } - static bool namesAreEquivalent(ITypedElement le, ITypedElement ri) + static bool namesAreEquivalent(IScopedNode le, IScopedNode ri) { if (le.Name == "id" && ri.Name == "id") return true; // IN FHIR: don't compare 'id' elements for equivalence if (le.Name != ri.Name) return false; @@ -243,11 +244,11 @@ public static bool IsEquivalentTo(P.Any? left, P.Any? right) } } - public static readonly IEqualityComparer TypedElementEqualityComparer = new ValueProviderEqualityComparer(); + public static readonly IEqualityComparer TypedElementEqualityComparer = new ValueProviderEqualityComparer(); - private class ValueProviderEqualityComparer : IEqualityComparer + private class ValueProviderEqualityComparer : IEqualityComparer { - public bool Equals(ITypedElement? x, ITypedElement? y) + public bool Equals(IScopedNode? x, IScopedNode? y) { if (x is null && y is null) return true; if (x is null || y is null) return false; @@ -259,11 +260,11 @@ public bool Equals(ITypedElement? x, ITypedElement? y) return x.IsEqualTo(y) == true; } - public int GetHashCode(ITypedElement element) + public int GetHashCode(IScopedNode element) { var result = element.Value != null ? element.Value.GetHashCode() : 0; - if (element is ITypedElement element1) + if (element is IScopedNode element1) { var childnames = string.Concat(element1.Children().Select(c => c.Name)); if (!string.IsNullOrEmpty(childnames)) diff --git a/src/Hl7.Fhir.Base/FhirPath/Functions/StringOperators.cs b/src/Hl7.Fhir.Base/FhirPath/Functions/StringOperators.cs index 6f21c0898b..4149b9313f 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Functions/StringOperators.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Functions/StringOperators.cs @@ -7,6 +7,7 @@ */ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using System; using System.Collections.Generic; using System.Linq; @@ -33,29 +34,30 @@ public static string FpSubstring(this string me, long start, long? length) return me.Substring((int)start, (int)l); } - public static ITypedElement FpIndexOf(this string me, string fragment) + public static IScopedNode FpIndexOf(this string me, string fragment) { - return ElementNode.ForPrimitive(me.IndexOf(fragment, StringComparison.Ordinal)); + return new Integer(me.IndexOf(fragment, StringComparison.Ordinal)); } - public static IEnumerable ToChars(this string me) => - me.ToCharArray().Select(c => ElementNode.ForPrimitive(c)); + public static IEnumerable ToChars(this string me) => + me.Select(c => new FhirString(c.ToString())); public static string FpReplace(this string me, string find, string replace) { if (find == String.Empty) { // weird, but as specified: "abc".replace("","x") = "xaxbxcx" + // I wonder why adding toCharArray was necessary here... return replace + String.Join(replace, me.ToCharArray()) + replace; } else return me.Replace(find, replace); } - public static IEnumerable FpSplit(this string me, string seperator) + public static IEnumerable FpSplit(this string me, string seperator) { var results = me.Split(new[] { seperator }, StringSplitOptions.None); - return results.Select(s => ElementNode.ForPrimitive(s)); + return results.Select(s => new FhirString(s)); } public static string FpEncode(this string me, string encoding) diff --git a/src/Hl7.Fhir.Base/FhirPath/Functions/TypeOperators.cs b/src/Hl7.Fhir.Base/FhirPath/Functions/TypeOperators.cs index 34cbdb30e2..074a65ff70 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Functions/TypeOperators.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Functions/TypeOperators.cs @@ -10,13 +10,18 @@ using System.Collections.Generic; using System.Linq; using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Utility; +using System.Runtime.CompilerServices; + +#pragma warning disable CS0612 // Type or member is obsolete namespace Hl7.FhirPath.Functions { + [TemporarilyChanged] // disabled obsolete warning internal static class TypeOperators { - public static bool Is(this ITypedElement focus, string type) + public static bool Is(this IScopedNode focus, string type) { if (focus.InstanceType != null) { @@ -39,10 +44,10 @@ public static bool Is(string instanceType, string declaredType) } } - public static IEnumerable FilterType(this IEnumerable focus, string typeName) + public static IEnumerable FilterType(this IEnumerable focus, string typeName) => focus.Where(item => item.Is(typeName)); - public static ITypedElement CastAs(this ITypedElement focus, string typeName) + public static IScopedNode CastAs(this IScopedNode focus, string typeName) => focus.Is(typeName) ? focus : null; } } diff --git a/src/Hl7.Fhir.Base/FhirPath/Functions/UtilityOperators.cs b/src/Hl7.Fhir.Base/FhirPath/Functions/UtilityOperators.cs index 5b1cac9433..94ef726a38 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Functions/UtilityOperators.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Functions/UtilityOperators.cs @@ -1,4 +1,5 @@ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using System; using System.Collections.Generic; using System.Diagnostics; @@ -8,13 +9,13 @@ namespace Hl7.FhirPath.Functions { internal static class UtilityOperators { - public static IEnumerable Extension(this IEnumerable focus, string url) + public static IEnumerable Extension(this IEnumerable focus, string url) { return focus.Navigate("extension") .Where(es => es.Children("url").SingleOrDefault().Value as string == url); } - public static IEnumerable Trace(this IEnumerable focus, string name, EvaluationContext ctx) + public static IEnumerable Trace(this IEnumerable focus, string name, EvaluationContext ctx) { ctx.Tracer?.Invoke(name, focus); return focus; diff --git a/src/Hl7.Fhir.Base/FhirPath/TypedElementFPExtensions.cs b/src/Hl7.Fhir.Base/FhirPath/TypedElementFPExtensions.cs index be8610bcfb..a394069398 100644 --- a/src/Hl7.Fhir.Base/FhirPath/TypedElementFPExtensions.cs +++ b/src/Hl7.Fhir.Base/FhirPath/TypedElementFPExtensions.cs @@ -9,6 +9,7 @@ #nullable enable using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using System; using System.Collections.Generic; @@ -21,25 +22,25 @@ public static class IValueProviderFPExtensions private static Lazy CACHE = new(() => new(compiler: null, cacheSize: MAX_FP_EXPRESSION_CACHE_SIZE)); - /// + /// public static IEnumerable Select(this ITypedElement input, string expression, EvaluationContext? ctx = null) - => CACHE.Value.Select(input, expression, ctx); + => CACHE.Value.Select(input.ToScopedNode(), expression, ctx); - /// + /// public static object? Scalar(this ITypedElement input, string expression, EvaluationContext? ctx = null) - => CACHE.Value.Scalar(input, expression, ctx); + => CACHE.Value.Scalar(input.ToScopedNode(), expression, ctx); - /// + /// public static bool Predicate(this ITypedElement input, string expression, EvaluationContext? ctx = null) - => CACHE.Value.Predicate(input, expression, ctx); + => CACHE.Value.Predicate(input.ToScopedNode(), expression, ctx); - /// + /// public static bool IsTrue(this ITypedElement input, string expression, EvaluationContext? ctx = null) - => CACHE.Value.IsTrue(input, expression, ctx); + => CACHE.Value.IsTrue(input.ToScopedNode(), expression, ctx); - /// + /// public static bool IsBoolean(this ITypedElement input, string expression, bool value, EvaluationContext? ctx = null) - => CACHE.Value.IsBoolean(input, expression, value, ctx); + => CACHE.Value.IsBoolean(input.ToScopedNode(), expression, value, ctx); /// /// Reinitialize the cache. This method is only meant for the unit tests, but can be made public later. We need some refactoring here, I (MV) think. diff --git a/src/Hl7.Fhir.Base/Model/Base.TypedElement.cs b/src/Hl7.Fhir.Base/Model/Base.TypedElement.cs index 793e925503..e8723d6e5c 100644 --- a/src/Hl7.Fhir.Base/Model/Base.TypedElement.cs +++ b/src/Hl7.Fhir.Base/Model/Base.TypedElement.cs @@ -140,15 +140,20 @@ IEnumerable ITypedElement.Children(string? name) => _ => $"{ScopeInfo.Name}" }; + [TemporarilyChanged] // We need to use Children for now to preserve scope access, but we would really prefer poco-accesses here. When we refactor, we should change this back. bool IScopedNode.TryResolveBundleEntry(string fullUrl, [NotNullWhen(true)] out IScopedNode? result) { - result = this is Bundle b ? b.Entry.FirstOrDefault(entry => entry.FullUrl == fullUrl) : null; + result = this is Bundle b ? (b as IScopedNode) + .Children("entry").FirstOrDefault(entry => entry.Children("fullUrl") + .SingleOrDefault()?.Value is string url && url == fullUrl)? + .Children("resource").SingleOrDefault() : null; return result is not null; } + [TemporarilyChanged] // We need to use Children for now to preserve scope access, but we would really prefer poco-accesses here. When we refactor, we should change this back. bool IScopedNode.TryResolveContainedEntry(string id, [NotNullWhen(true)] out IScopedNode? result) { - result = this is DomainResource dr ? dr.Contained.FirstOrDefault(contained => contained.Id == id) : null; + result = this is DomainResource dr ? (dr as IScopedNode).Children("contained").FirstOrDefault(contained => contained.Children("id").SingleOrDefault()?.Value is string containedId && $"#{containedId}" == id) : null; return result is not null; } @@ -168,6 +173,7 @@ bool IScopedNode.TryResolveContainedEntry(string id, [NotNullWhen(true)] out ISc DomainResource => NodeType.DomainResource | NodeType.Resource, Resource => NodeType.Resource, ResourceReference or Canonical or CodeableReference => NodeType.Reference, + Quantity => NodeType.Quantity, _ => 0 }; diff --git a/src/Hl7.Fhir.Base/Model/IScopedNode.TemporaryMembers.cs b/src/Hl7.Fhir.Base/Model/IScopedNode.TemporaryMembers.cs new file mode 100644 index 0000000000..6c581c1991 --- /dev/null +++ b/src/Hl7.Fhir.Base/Model/IScopedNode.TemporaryMembers.cs @@ -0,0 +1,14 @@ +using Hl7.Fhir.Specification; +using System; + +namespace Hl7.Fhir.Model; + +#nullable enable + +public partial interface IScopedNode +{ + [Obsolete] + new IElementDefinitionSummary? Definition => throw new NotImplementedException(); + + // [Obsolete] new string? InstanceType => throw new NotImplementedException(); Commented this out to avoid compilation error +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Model/IScopedNode.cs b/src/Hl7.Fhir.Base/Model/IScopedNode.cs index bd77c5ceb4..82d4174433 100644 --- a/src/Hl7.Fhir.Base/Model/IScopedNode.cs +++ b/src/Hl7.Fhir.Base/Model/IScopedNode.cs @@ -20,6 +20,7 @@ public enum NodeType DomainResource = 1 << 2, Primitive = 1 << 3, Reference = 1 << 4, + Quantity = 1 << 5, } /// @@ -30,7 +31,7 @@ public enum NodeType /// the instance or derived from fully aware of the FHIR definitions and types /// #pragma warning disable CS0618 // Type or member is obsolete -public interface IScopedNode : ITypedElement, IShortPathGenerator +public partial interface IScopedNode : ITypedElement, IShortPathGenerator #pragma warning restore CS0618 // Type or member is obsolete { /// diff --git a/src/Hl7.Fhir.Base/Model/ScopedNodeExtensions.cs b/src/Hl7.Fhir.Base/Model/ScopedNodeExtensions.cs index ac2366d144..c08f0e4168 100644 --- a/src/Hl7.Fhir.Base/Model/ScopedNodeExtensions.cs +++ b/src/Hl7.Fhir.Base/Model/ScopedNodeExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Xml.Schema; namespace Hl7.Fhir.Model; @@ -10,6 +11,14 @@ namespace Hl7.Fhir.Model; public static class ScopedNodeExtensions { + internal static IEnumerable Parents(this IScopedNode node) + { + for(var scan = node.Parent; scan is not null; scan = scan.Parent) + { + yield return scan; + } + } + // wrote this, but it never gets picked over the ElementNodeExtensions version which is a shame. Let's keep it here for now. public static IEnumerable Children(this IEnumerable node, string? name = null) => node.SelectMany(n => n.Children(name)); @@ -60,21 +69,21 @@ public static IEnumerable Children(this IEnumerable no /// /// Extract the %resource variable from this IScopedNode /// - internal static IScopedNode GetResourceContext(this IScopedNode node) => node switch + internal static IScopedNode? GetResourceContext(this IScopedNode node) => node switch { { Parent: null } => node, // if parent is null, do not go further - { Parent: { } p } when p.Type.HasFlag(NodeType.Bundle) => node, // if parent is bundle, do not go further { Type: var type } when type.HasFlag(NodeType.Resource) => node, // if resource, return itself - _ => node.Parent!.GetResourceContext() // otherwise, go to parent + _ => node?.Parent?.GetResourceContext() // otherwise, go to parent }; /// /// Extract the %rootResource variable from this IScopedNode /// - internal static IScopedNode GetRootResourceContext(this IScopedNode node) => node.GetResourceContext() switch + internal static IScopedNode? GetRootResourceContext(this IScopedNode node) => node.GetResourceContext() switch { { Name : "contained" } containedResource => containedResource.Parent!, // if contained, return container - { } resource => resource // otherwise return %resource + { } resource => resource, // otherwise return %resource + _ => null }; internal static string? FindFullUrl(this IScopedNode node) @@ -108,6 +117,17 @@ internal static ResourceIdentity MakeAbsolute(this IScopedNode node, ResourceIde return identity; } - public static string MakeAbsolute(this ScopedNode node, string reference) => + public static string MakeAbsolute(this IScopedNode node, string reference) => node.MakeAbsolute(new ResourceIdentity(reference)).ToString(); + + internal static IScopedNode? GetParentResource(this IScopedNode node) => node.Parents().FirstOrDefault(parentNode => parentNode.Type.HasFlag(NodeType.Resource)); + + internal static string GetLocalLocation(this IScopedNode node) => + node.Parent is null + ? node.Location + : $"{node.GetParentResource()!.InstanceType}.{node.Location[(node.GetParentResource()!.Location.Length + 1)..]}"; + + public static IEnumerable ContainedResources(this IScopedNode node) => node.Children("contained"); + + public static IEnumerable BundledResources(this IScopedNode node) => node.Children("entry"); } \ No newline at end of file diff --git a/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs b/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs index 016051f269..676ac9784a 100644 --- a/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs +++ b/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs @@ -22,6 +22,7 @@ using System; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using Tasks = System.Threading.Tasks; diff --git a/src/Hl7.Fhir.ElementModel.Shared.Tests/Hl7.Fhir.ElementModel.Shared.Tests.projitems b/src/Hl7.Fhir.ElementModel.Shared.Tests/Hl7.Fhir.ElementModel.Shared.Tests.projitems index bcaceda5d0..a733d39366 100644 --- a/src/Hl7.Fhir.ElementModel.Shared.Tests/Hl7.Fhir.ElementModel.Shared.Tests.projitems +++ b/src/Hl7.Fhir.ElementModel.Shared.Tests/Hl7.Fhir.ElementModel.Shared.Tests.projitems @@ -31,6 +31,7 @@ + diff --git a/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeOnBaseTests.cs b/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeOnBaseTests.cs new file mode 100644 index 0000000000..c0cb5a8166 --- /dev/null +++ b/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeOnBaseTests.cs @@ -0,0 +1,167 @@ +using FluentAssertions; +using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; +using Hl7.Fhir.Serialization; +using Hl7.Fhir.Specification; +using Hl7.Fhir.Specification.Snapshot; +using Hl7.Fhir.Specification.Source; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Hl7.Fhir.ElementModel.Tests +{ + [TestClass] + public class ScopedNodeOnBaseTests + { + private IScopedNode _bundleNode; + + [TestInitialize] + public void SetupSource() + { + var bundleXml = File.ReadAllText(Path.Combine("TestData", "bundle-contained-references.xml")); + + var bundle = (new FhirXmlParser()).Parse(bundleXml); + Assert.IsNotNull(bundle); + _bundleNode = bundle.ToScopedNode(); + } + + [TestMethod] + public void GetContainedAndBundledResources() + { + Assert.AreEqual(0, _bundleNode!.ContainedResources().Count()); + + var entries = _bundleNode.BundledResources().OfType().ToList(); + Assert.AreEqual(7, entries.Count); + + Assert.AreEqual("urn:uuid:04121321-4af5-424c-a0e1-ed3aab1c349d", entries[1].FullUrl); + Assert.AreEqual("http://example.org/fhir/Patient/b", entries[3].FullUrl); + + Assert.IsFalse(entries[1].Resource!.ToScopedNode().ContainedResources().Any()); + Assert.IsNotNull(entries[1].Resource!.ToScopedNode().Children().First()); + + Assert.AreEqual("a", entries[2].Resource!.Id); + + var entry6 = entries[6].Resource; + Assert.AreEqual(2, entry6!.ContainedResources().Count()); + Assert.IsFalse(entry6.BundledResources().Any()); + Assert.AreEqual("orgY", (entry6.ContainedResources().Skip(1).First() as Resource)!.Id); + } + + [TestMethod] + public void GetFullUrl() + { + var entries = _bundleNode!.BundledResources().ToList(); + + Assert.AreEqual("http://example.org/fhir/Patient/b", entries[3].FindFullUrl()); + + IScopedNode entry3 = entries[3].Children("resource").FirstOrDefault(); + entry3 = entry3?.Children("managingOrganization").FirstOrDefault(); + Assert.IsNotNull(entry3); + entry3 = entry3.Children("reference").FirstOrDefault(); + Assert.IsNotNull(entry3); + Assert.AreEqual(entries[3].FindFullUrl(), entry3.FindFullUrl()); + Assert.AreEqual(entry3.GetParentResource()!.FindFullUrl(), entry3.FindFullUrl()); + + var entry6 = entries[6].Children("resource").FirstOrDefault(); + entry6 = entry6?.ContainedResources().Skip(1).FirstOrDefault(); + Assert.IsNotNull(entry6); + Assert.AreEqual("orgY", entry6.Children("id").FirstOrDefault()?.Value); + Assert.AreEqual(entries[6].FindFullUrl(), entry6.FindFullUrl()); + Assert.AreEqual(entry6.GetParentResource()!.FindFullUrl(), entry6.FindFullUrl()); + } + + [TestMethod] + public void TestMakeAbsolute() + { + var inner0 = _bundleNode!.BundledResources().First().Children("resource").Children("active").SingleOrDefault() as IScopedNode; + Assert.IsNotNull(inner0); + + Assert.AreEqual("http://example.org/fhir/Patient/3", inner0.MakeAbsolute("Patient/3")); + Assert.AreEqual("http://nu.nl/myPat/3x", inner0.MakeAbsolute("http://nu.nl/myPat/3x")); + Assert.AreEqual("http://example.org/fhir/Organization/5", inner0.MakeAbsolute("http://example.org/fhir/Organization/5")); + + var inner1 = _bundleNode.BundledResources().Skip(1).First().Children("resource").Children("active").SingleOrDefault() as IScopedNode; + + Assert.AreEqual("urn:uuid:04121321-4af5-424c-a0e1-ed3aab1c349d/3", inner1!.MakeAbsolute("Patient/3")); + Assert.AreEqual("http://nu.nl/myPat/3x", inner1!.MakeAbsolute("http://nu.nl/myPat/3x")); + Assert.AreEqual("http://example.org/fhir/Organization/5", inner1!.MakeAbsolute("http://example.org/fhir/Organization/5")); + } + + [TestMethod] + public void TestContainedCanResolveToContainer() + { + Assert.IsNull(_bundleNode!.Resolve("#")); + + var patient = _bundleNode!.BundledResources().Skip(6).First().Children("resource").First(); + Assert.IsNull(patient.Resolve("#")); + + var containedOrg = patient.ContainedResources().First(); + Assert.AreEqual("Patient", containedOrg.Resolve("#")!.InstanceType); + + var containedId = containedOrg.Children("id").First(); + Assert.AreEqual("Patient", containedId.Resolve("#")!.InstanceType); + } + + [TestMethod] + public void TestResolve() + { + IScopedNode inner7 = (_bundleNode!.BundledResources().Skip(6).First().Children("resource").Children("managingOrganization").SingleOrDefault() as IScopedNode)!; + + Assert.AreEqual("Bundle.entry[6].resource[0]", inner7.Resolve("http://example.org/fhir/Patient/e")!.Location); + Assert.AreEqual("Bundle.entry[6].resource[0].contained[1]", inner7.Resolve("#orgY")!.Location); + Assert.AreEqual("Bundle.entry[6].resource[0]", inner7.Resolve("#e")!.Location); + Assert.AreEqual("Bundle.entry[5].resource[0]", inner7.Resolve("http://example.org/fhir/Patient/d")!.Location); + Assert.AreEqual("Bundle.entry[5].resource[0]", inner7.Resolve("Patient/d")!.Location); + Assert.AreEqual("Bundle.entry[1].resource[0]", inner7.Resolve("urn:uuid:04121321-4af5-424c-a0e1-ed3aab1c349d")!.Location); + Assert.IsNull(inner7.Resolve("#d")); + Assert.IsNull(inner7.Resolve("http://nu.nl/3")); + + Assert.AreEqual("Bundle.entry[6].resource[0].contained[1]", inner7.Resolve()!.Location); + Assert.IsTrue(inner7.Children("reference").Any()); + Assert.AreEqual("Bundle.entry[6].resource[0].contained[1]", inner7.Children("reference").First().Resolve()!.Location); + + string lastUrlResolved = ""; + + Assert.IsNull(inner7.Resolve("#d", externalResolve)); + Assert.AreEqual("#d", lastUrlResolved); + Assert.IsNull(inner7.Resolve("http://nu.nl/3", externalResolve)); + Assert.AreEqual("http://nu.nl/3", lastUrlResolved); + + IScopedNode? externalResolve(string url) + { + lastUrlResolved = url; + return null; + } + } + + [TestMethod] + [TemporarilyChanged] // this test is strange. We cannot support it on pocos (yet) + [Ignore] + public void AtResourceWithoutDefinition() + { + // var provider = new NoTypeProvider(); + // var elementNode = ElementNode.Root(provider, "Patient"); + // elementNode.Add(provider, "active", true, "boolean"); + // + // var node = elementNode.ToPoco().ToScopedNode(); + // + // Assert.IsTrue(node.Type.HasFlag(NodeType.Resource)); + // var inner = node.Children().First(); + // Assert.IsFalse(inner.Type.HasFlag(NodeType.Resource)); + } + + [TestMethod] + public void Bundle_WithEntryWithoutFullUrl_ShouldNotThrow() + { + var bundle = new Bundle() { Type = Bundle.BundleType.Batch, Entry = [new Bundle.EntryComponent() { Resource = new Patient() }]}.ToTypedElement().ToScopedNode(); + + var enumerate = () => bundle.BundledResources(); + enumerate.Should().NotThrow().Subject.Should().ContainSingle(c => !c.Children("fullUrl").Any()); + } + } +} \ No newline at end of file diff --git a/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs b/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs index 665587f230..5c7eca5cd1 100644 --- a/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs +++ b/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs @@ -271,17 +271,15 @@ static bool CCDATypeNameMapper(string typeName, out string canonical) Assert.AreEqual("xhtml", typedElements.First().InstanceType); Assert.AreEqual("Section.text[0]", typedElements.First().Location); Assert.IsNotNull(typedElements.First().Value); - - } [TestMethod] public void Bundle_WithEntryWithoutFullUrl_ShouldNotThrow() { - var bundle = new Bundle() { Type = Bundle.BundleType.Batch, Entry = [new Bundle.EntryComponent() { Resource = new Patient() }]}.ToTypedElement().ToScopedNode(); + var bundle = new Bundle() { Type = Bundle.BundleType.Batch, Entry = [new Bundle.EntryComponent() { Resource = new Patient() }]}; var enumerate = () => bundle.BundledResources(); - enumerate.Should().NotThrow().Subject.Should().ContainSingle(c => c.FullUrl == null); + enumerate.Should().NotThrow().Subject.Should().ContainSingle(c => !c.Children("fullUrl").Any()); } private class CCDAResourceResolver : IAsyncResourceResolver diff --git a/src/Hl7.Fhir.R4/CompatibilitySuppressions.xml b/src/Hl7.Fhir.R4/CompatibilitySuppressions.xml index e593fbb5aa..aa6291ada4 100644 --- a/src/Hl7.Fhir.R4/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.R4/CompatibilitySuppressions.xml @@ -8,6 +8,13 @@ lib/net8.0/Hl7.Fhir.R4.dll true + + CP0002 + M:Hl7.Fhir.FhirPath.FhirPathExtensions.ToFhirPathResolver(System.Func{System.String,Hl7.Fhir.Model.Resource}) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + CP0011 F:Hl7.Fhir.Model.Questionnaire.QuestionnaireItemType.Attachment diff --git a/src/Hl7.Fhir.R4B/CompatibilitySuppressions.xml b/src/Hl7.Fhir.R4B/CompatibilitySuppressions.xml index b9c4ef03fe..ef7fd73f07 100644 --- a/src/Hl7.Fhir.R4B/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.R4B/CompatibilitySuppressions.xml @@ -1,6 +1,13 @@  + + CP0002 + M:Hl7.Fhir.FhirPath.FhirPathExtensions.ToFhirPathResolver(System.Func{System.String,Hl7.Fhir.Model.Resource}) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + PKV006 .NETStandard,Version=v2.0 diff --git a/src/Hl7.Fhir.R5/CompatibilitySuppressions.xml b/src/Hl7.Fhir.R5/CompatibilitySuppressions.xml index b9c4ef03fe..5c2d177157 100644 --- a/src/Hl7.Fhir.R5/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.R5/CompatibilitySuppressions.xml @@ -1,6 +1,13 @@  + + CP0002 + M:Hl7.Fhir.FhirPath.FhirPathExtensions.ToFhirPathResolver(System.Func{System.String,Hl7.Fhir.Model.Resource}) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + PKV006 .NETStandard,Version=v2.0 diff --git a/src/Hl7.Fhir.STU3/CompatibilitySuppressions.xml b/src/Hl7.Fhir.STU3/CompatibilitySuppressions.xml index 1ee0d490d0..e056630df1 100644 --- a/src/Hl7.Fhir.STU3/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.STU3/CompatibilitySuppressions.xml @@ -29,6 +29,13 @@ lib/net8.0/Hl7.Fhir.STU3.dll true + + CP0002 + M:Hl7.Fhir.FhirPath.FhirPathExtensions.ToFhirPathResolver(System.Func{System.String,Hl7.Fhir.Model.Resource}) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + CP0011 F:Hl7.Fhir.Model.ExplanationOfBenefit.ActInvoiceGroupCode.CPINV diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/FhirPath/FhirPathExtensions.cs b/src/Hl7.Fhir.Shims.STU3AndUp/FhirPath/FhirPathExtensions.cs index 8f72726daa..17efec6bf4 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/FhirPath/FhirPathExtensions.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/FhirPath/FhirPathExtensions.cs @@ -23,18 +23,18 @@ public static class FhirPathExtensions private static readonly FhirPathCompilerCache CACHE = new(COMPILER); /// - /// Converts results of a resolver from Resource to ITypedElement + /// Converts results of a resolver from Resource to IScopedNode /// /// results of a resolver as Resource - /// Result of the convertion to ITypedElement - public static Func ToFhirPathResolver(this Func resolver) + /// Result of the convertion to IScopedNode + public static Func ToFhirPathResolver(this Func resolver) { return navResolver; - ITypedElement? navResolver(string url) + IScopedNode? navResolver(string url) { var resource = resolver(url); - return resource?.ToTypedElement(); + return resource; } } @@ -46,23 +46,23 @@ public static class FhirPathExtensions /// now used by 1 unit test FhirPathScaleTest internal static SymbolTable GetSymbols() => COMPILER.Symbols; - /// + /// public static IEnumerable Select(this Base input, string expression, FhirEvaluationContext? ctx = null) => CACHE.Select(input.ToTypedElement().ToScopedNode(), expression, ctx ?? new FhirEvaluationContext()).ToFhirValues(); - /// + /// public static object? Scalar(this Base input, string expression, FhirEvaluationContext? ctx = null) => CACHE.Scalar(input.ToTypedElement().ToScopedNode(), expression, ctx ?? new FhirEvaluationContext()); - /// + /// public static bool Predicate(this Base input, string expression, FhirEvaluationContext? ctx = null) => CACHE.Predicate(input.ToTypedElement().ToScopedNode(), expression, ctx ?? new FhirEvaluationContext()); - /// + /// public static bool IsTrue(this Base input, string expression, FhirEvaluationContext? ctx = null) => CACHE.IsTrue(input.ToTypedElement().ToScopedNode(), expression, ctx ?? new FhirEvaluationContext()); - /// + /// public static bool IsBoolean(this Base input, string expression, bool value, FhirEvaluationContext? ctx = null) => CACHE.IsBoolean(input.ToTypedElement().ToScopedNode(), expression, value, ctx ?? new FhirEvaluationContext()); } diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs index e9764a5f14..f4c9961bbe 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs @@ -628,7 +628,7 @@ void VerifyRule(StructureDefinition output, EvaluationContext ctx, SnapshotGener var rule = test.Rule[i]; Console.WriteLine($"Verify rule {i}: '{rule.Text}'"); - var nav = output.ToTypedElement(); + var nav = output; var expr = _fhirPathCompiler.Compile(rule.FhirPath); Assert.IsTrue(expr.Predicate(nav, ctx), $"FAILED Rule {i}: '{rule.Text}'"); } @@ -783,7 +783,7 @@ void ReplaceTestRule(string id, string originalExpression, string fixedExpressio // Custom context for accessing input & expected result class SnapshotEvaluationContext : FhirEvaluationContext { - Dictionary _aliases; + Dictionary _aliases; string _testPath; public SnapshotEvaluationContext( @@ -794,8 +794,8 @@ public SnapshotEvaluationContext( TestResolver = resolver ?? throw new ArgumentNullException(nameof(resolver)); if (input is null) { throw new ArgumentNullException(nameof(input)); } if (generated is null) { throw new ArgumentNullException(nameof(generated)); } - Input = input.ToTypedElement(); - Generated = generated.ToTypedElement(); + Input = input; + Generated = generated; Id = id ?? throw new ArgumentNullException(nameof(id)); Assert.AreEqual(id, generated.Id); this.Tracer = this.Trace; @@ -803,7 +803,7 @@ public SnapshotEvaluationContext( this.WithResourceOverrides(Generated); } - void Trace(string msg, IEnumerable elems) + void Trace(string msg, IEnumerable elems) { Console.WriteLine($"[TRACE] {msg}:"); foreach (var elem in elems) @@ -816,19 +816,19 @@ void Trace(string msg, IEnumerable elems) public IResourceResolver TestResolver { get; } - public ITypedElement Input { get; } + public IScopedNode Input { get; } - public ITypedElement Generated { get; } + public IScopedNode Generated { get; } // Custom FhirPath method implementations - Dictionary Aliases => _aliases ?? (_aliases = new Dictionary()); + Dictionary Aliases => _aliases ?? (_aliases = new Dictionary()); - void AddAlias(string alias, ITypedElement elem) => Aliases[alias] = elem; + void AddAlias(string alias, IScopedNode elem) => Aliases[alias] = elem; - ITypedElement Alias(string alias) => Aliases[alias]; + IScopedNode Alias(string alias) => Aliases[alias]; - ITypedElement Fixture(string name) + IScopedNode Fixture(string name) { if (name == $"{Id}-input") { return Input; } if (name == $"{Id}-output") { return Generated; } @@ -842,7 +842,7 @@ ITypedElement Fixture(string name) { filePath = Path.ChangeExtension(filePath, "json"); } - return Load(filePath).ToTypedElement(); + return Load(filePath); } // Otherwise assume name refers to a core resource, e.g. 'patient' @@ -851,7 +851,7 @@ ITypedElement Fixture(string name) if (!(typeName is null)) { #pragma warning disable CS0618 // Type or member is obsolete - return TestResolver.FindStructureDefinitionForCoreType(typeName).ToTypedElement(); + return TestResolver.FindStructureDefinitionForCoreType(typeName); #pragma warning restore CS0618 // Type or member is obsolete } @@ -862,18 +862,18 @@ ITypedElement Fixture(string name) // Add custom FHIRPath methods for unit testing public static void AddSymbols(SymbolTable symbols) { - symbols.Add("fixture", Fixture); - symbols.Add("aliasAs", AliasAs); - symbols.Add("alias", Alias); - symbols.Add("check", Check); + symbols.Add("fixture", Fixture); + symbols.Add("aliasAs", AliasAs); + symbols.Add("alias", Alias); + symbols.Add("check", Check); } // Custom FHIRPath methods for unit testing - public static ITypedElement Fixture(ITypedElement elem, string name, EvaluationContext ctx) + public static IScopedNode Fixture(IScopedNode elem, string name, EvaluationContext ctx) => ctx is SnapshotEvaluationContext sctx ? sctx.Fixture(name) : null; - public static ITypedElement AliasAs(ITypedElement elem, string id, EvaluationContext ctx) + public static IScopedNode AliasAs(IScopedNode elem, string id, EvaluationContext ctx) { if (ctx is SnapshotEvaluationContext sctx) { @@ -882,10 +882,10 @@ public static ITypedElement AliasAs(ITypedElement elem, string id, EvaluationCon return elem; } - public static ITypedElement Alias(ITypedElement elem, string id, EvaluationContext ctx) + public static IScopedNode Alias(IScopedNode elem, string id, EvaluationContext ctx) => ctx is SnapshotEvaluationContext sctx ? sctx.Alias(id) : null; - public static ITypedElement Check(ITypedElement elem, bool condition, string message, EvaluationContext ctx) + public static IScopedNode Check(IScopedNode elem, bool condition, string message, EvaluationContext ctx) { Assert.IsTrue(condition, $"[CHECK] '{elem.Name}' {message}"); //if (!condition) diff --git a/src/Hl7.Fhir.Support.Tests/FhirPath/FhirPathTests.cs b/src/Hl7.Fhir.Support.Tests/FhirPath/FhirPathTests.cs index e96465eed5..1f787b8b41 100644 --- a/src/Hl7.Fhir.Support.Tests/FhirPath/FhirPathTests.cs +++ b/src/Hl7.Fhir.Support.Tests/FhirPath/FhirPathTests.cs @@ -56,7 +56,7 @@ public void ResolveOnEmptyTest() public void HtmlChecks(string xml, bool expected, string because) { var evaluator = _compiler.Compile("htmlChecks()"); - evaluator.Predicate(ElementNode.ForPrimitive(xml), new FhirEvaluationContext()).Should().Be(expected, because); + evaluator.Predicate(new XHtml(xml), new FhirEvaluationContext()).Should().Be(expected, because); } [DataTestMethod] diff --git a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathContextTests.cs b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathContextTests.cs index f8b1b46d84..bfd43cf23b 100644 --- a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathContextTests.cs +++ b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathContextTests.cs @@ -12,7 +12,7 @@ namespace Hl7.FhirPath.R4.Tests.PocoTests; [TestClass] public class FhirPathContextTests { - ScopedNode _bundle; + IScopedNode _bundle; [TestInitialize] public void SetupSource() diff --git a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathEvaluatorTest.cs b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathEvaluatorTest.cs index e0b7ed9b0e..8bbec18cdc 100644 --- a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathEvaluatorTest.cs +++ b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathEvaluatorTest.cs @@ -255,7 +255,7 @@ public void TestAll() public void TestAny() { fixture.IsTrue(@"Patient.identifier.any(use = 'official')"); - fixture.IsTrue(@"Patient.identifier.skip(999).any(use = 'official') = false"); // {}.Any() aways returns true + fixture.IsTrue(@"Patient.identifier.skip(999).any(use = 'official') = false"); // {}.Any() always returns false fixture.IsTrue(@"Patient.contained.skip(1).item.any(code.code = 'COMORBIDITY')"); // really need to filter on Questionnare (as('Questionnaire')) } @@ -605,10 +605,10 @@ public void defineVariable_with_compile_success() var expr = "defineVariable('root', 'r1-').select(defineVariable('v1', 'v1').defineVariable('v2', 'v2').select(%v1 | %v2)).select(%root & $this)"; var compiler = new FhirPathCompiler(); var exprCompiled = compiler.Compile(expr); - var r = exprCompiled(fixture.PatientExample.ToTypedElement(), new FhirEvaluationContext()); + var r = exprCompiled(fixture.PatientExample, new FhirEvaluationContext()); Assert.AreEqual(2, r.Count()); - Assert.AreEqual("r1-v1", r.First().ToString()); - Assert.AreEqual("r1-v2", r.Skip(1).First().ToString()); + Assert.AreEqual("r1-v1", r.First().Value); + Assert.AreEqual("r1-v2", r.Skip(1).First().Value); // .toStrictEqual(["r1-v1", "r1-v2"]); } /* diff --git a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathExtensionsTest.cs b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathExtensionsTest.cs index 66c774a61c..e206923e27 100644 --- a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathExtensionsTest.cs +++ b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathExtensionsTest.cs @@ -48,8 +48,6 @@ public void TestResolve() } [TestMethod] - [Ignore("This test calls resolve on a primitive ElementNode. We will solve this when we rewrite the FhirPath engine against IScopedNode")] - [TemporarilyChanged] public void TestResolve2() { var statement = "'http://example.org/doesntexist'.resolve().id"; @@ -65,8 +63,6 @@ IScopedNode resolver(string url) } [TestMethod] - [Ignore("This test calls resolve on a list of ElementNodes. We will solve this when we rewrite the FhirPath engine against IScopedNode")] - [TemporarilyChanged] public void TestResolveList() { var statement = "Bundle.entry.where(fullUrl = 'http://example.org/fhir/Patient/e')" + diff --git a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathNavTest.cs b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathNavTest.cs index f69ab4a8d7..888309647e 100644 --- a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathNavTest.cs +++ b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathNavTest.cs @@ -10,21 +10,25 @@ // extern alias dstu2; using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; using Hl7.Fhir.Specification; using Hl7.FhirPath.Functions; using Hl7.FhirPath.Tests; using System.Linq; +using System.Text.Json; +using System.Xml.Serialization; using Xunit; namespace Hl7.FhirPath.R4.Tests { public class FhirPathNavTest { - public ITypedElement getTestData() + public IScopedNode getTestData() { var tpXml = TestData.ReadTextFile("fp-test-patient.xml"); - return FhirXmlNode.Parse(tpXml).ToTypedElement(new PocoStructureDefinitionSummaryProvider()); + var engine = FhirSerializationEngineFactory.Ostrich(ModelInfo.ModelInspector); + return engine.DeserializeFromXml(tpXml); } [Fact] diff --git a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathParallelTest.cs b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathParallelTest.cs index c08b10229c..0bb6f4fbd0 100644 --- a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathParallelTest.cs +++ b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathParallelTest.cs @@ -34,8 +34,8 @@ public static void Initialize(TestContext ctx) [TestCategory("LongRunner")] public async Tasks.Task TestSelectMethods() { - await MassiveParallelSelectsShouldBeCorrect("Api", new Func>((nav, expr, context) => IValueProviderFPExtensions.Select(nav, expr, context))); - await MassiveParallelSelectsShouldBeCorrect("Concurrent", new Func>((nav, expr, context) => FhirPathExtensions.Select(nav, expr, context))); + await MassiveParallelSelectsShouldBeCorrect("Api", new Func>((nav, expr, context) => IValueProviderFPExtensions.Select(nav, expr, context))); + await MassiveParallelSelectsShouldBeCorrect("Concurrent", new Func>((nav, expr, context) => FhirPathExtensions.Select(nav, expr, context))); } /// @@ -46,15 +46,14 @@ public async Tasks.Task TestSelectMethods() /// This may indicate a multithreading problem in the FhirPath evaluation. /// You may need to run the test in Release mode to reveal the error. /// - public static async Tasks.Task MassiveParallelSelectsShouldBeCorrect(string testName, Func> selector) + public static async Tasks.Task MassiveParallelSelectsShouldBeCorrect(string testName, Func> selector) { var actual = new ConcurrentBag<(string canonical, ValueSet resource)>(); var buffer = new BufferBlock(); var processor = new ActionBlock(r => { - var typedElement = r.ToTypedElement(); var evalContext = new EvaluationContext(); - var canonical = selector(typedElement, "url", evalContext).Single().Value.ToString(); + var canonical = selector(r, "url", evalContext).Single().Value.ToString(); actual.Add((canonical, r)); } , @@ -127,7 +126,7 @@ private static CompiledExpression GetCompiledExpression(string expression) } - public static IEnumerable Select(this ITypedElement input, string expression, EvaluationContext ctx = null) + public static IEnumerable Select(this IScopedNode input, string expression, EvaluationContext ctx = null) { var evaluator = GetCompiledExpression(expression); return evaluator(input, ctx ?? new EvaluationContext()); diff --git a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathTest.cs b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathTest.cs index 2b5ff8fed3..3994e7fa6d 100644 --- a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathTest.cs +++ b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathTest.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading.Tasks.Dataflow; using P = Hl7.Fhir.ElementModel.Types; @@ -59,7 +60,7 @@ public void TestFhirPathTrace() Assert.AreEqual(P.DateTime.Parse("2018-05-24T14:48:00+00:00"), result.First().Value); bool traced = false; - ctx.Tracer = (string name, System.Collections.Generic.IEnumerable results) => + ctx.Tracer = (string name, System.Collections.Generic.IEnumerable results) => { System.Diagnostics.Trace.WriteLine($"{name}"); Assert.AreEqual("log", name); @@ -77,7 +78,7 @@ public void TestFhirPathTrace() Assert.IsTrue(traced); traced = false; - ctx.Tracer = (string name, System.Collections.Generic.IEnumerable results) => + ctx.Tracer = (string name, System.Collections.Generic.IEnumerable results) => { System.Diagnostics.Trace.WriteLine($"{name}"); Assert.IsTrue(name == "id" || name == "log"); diff --git a/src/Hl7.FhirPath.Tests/Functions/CollectionOperatorsTests.cs b/src/Hl7.FhirPath.Tests/Functions/CollectionOperatorsTests.cs index 1ee9268009..a0828b0f0c 100644 --- a/src/Hl7.FhirPath.Tests/Functions/CollectionOperatorsTests.cs +++ b/src/Hl7.FhirPath.Tests/Functions/CollectionOperatorsTests.cs @@ -1,6 +1,8 @@ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.FhirPath.Functions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; using System.Linq; namespace HL7.FhirPath.Tests.Functions @@ -46,8 +48,19 @@ public void TestIntersect() [TestMethod] public void TestExclude() { - var left = ElementNode.CreateList(1, 3, 3, 5, 6); - var right = ElementNode.CreateList(5, 6); + IEnumerable left = + [ + new Integer(1), + new Integer(3), + new Integer(3), + new Integer(5), + new Integer(6) + ]; + IEnumerable right = + [ + new Integer(5), + new Integer(6) + ]; CollectionAssert.AreEqual(ElementNode.CreateList(1, 3, 3).ToList(), left.Exclude(right).ToList()); } diff --git a/src/Hl7.FhirPath.Tests/Functions/FunctionsTests.cs b/src/Hl7.FhirPath.Tests/Functions/FunctionsTests.cs index 1744aec2e6..a1049e150f 100644 --- a/src/Hl7.FhirPath.Tests/Functions/FunctionsTests.cs +++ b/src/Hl7.FhirPath.Tests/Functions/FunctionsTests.cs @@ -500,7 +500,7 @@ public static IEnumerable AllFunctionTestcases() [DynamicData(nameof(AllFunctionTestcases), DynamicDataSourceType.Method)] public void AssertTestcases(string expression, bool expected, bool invalid = false) { - ITypedElement dummy = ElementNode.ForPrimitive(true); + IScopedNode dummy = new FhirBoolean(true); if (invalid) { @@ -541,11 +541,11 @@ public void SingleScalarTest() { iterations++; - return ElementNode.CreateList(iterations); + return new Integer(iterations); }); var expression = new FhirPathCompiler(symbols).Compile("once()"); - var result = expression.Scalar(null, new EvaluationContext()); + var result = expression.Scalar(null!, new EvaluationContext()); Assert.AreEqual(result, 1); } diff --git a/src/Hl7.FhirPath.Tests/Tests/BasicFunctionTests.cs b/src/Hl7.FhirPath.Tests/Tests/BasicFunctionTests.cs index 8016f1e7b4..0878580cfc 100644 --- a/src/Hl7.FhirPath.Tests/Tests/BasicFunctionTests.cs +++ b/src/Hl7.FhirPath.Tests/Tests/BasicFunctionTests.cs @@ -283,21 +283,21 @@ public void TestDivZero() [TestMethod] public void TestStringJoin() { - var dummy = ElementNode.CreateList("This ", "is ", "one ", "sentence", "."); + var dummy = ElementNode.CreateList("This ", "is ", "one ", "sentence", ".").ToScopedNodes(); var result = dummy.FpJoin(string.Empty); Assert.IsNotNull(result); Assert.AreEqual("This is one sentence.", result); - dummy = ElementNode.CreateList("a", "b", "c"); + dummy = ElementNode.CreateList("a", "b", "c").ToScopedNodes(); result = dummy.FpJoin(); Assert.IsNotNull(result); Assert.AreEqual("abc", result); - dummy = ElementNode.CreateList(); + dummy = ElementNode.CreateList().ToScopedNodes(); result = dummy.FpJoin(string.Empty); Assert.AreEqual(string.Empty, result); - dummy = ElementNode.CreateList("This", "is", "a", "separated", "sentence."); + dummy = ElementNode.CreateList("This", "is", "a", "separated", "sentence.").ToScopedNodes(); result = dummy.FpJoin(";"); Assert.IsNotNull(result); Assert.AreEqual("This;is;a;separated;sentence.", result); @@ -307,7 +307,7 @@ public void TestStringJoin() [ExpectedException(typeof(InvalidOperationException))] public void TestStringJoinError() { - var dummy = ElementNode.CreateList("This", "is", "sentence", "with", 1, "number."); + var dummy = ElementNode.CreateList("This", "is", "sentence", "with", 1, "number.").ToScopedNodes(); dummy.FpJoin(string.Empty); } } diff --git a/src/Hl7.FhirPath.Tests/Tests/CastTests.cs b/src/Hl7.FhirPath.Tests/Tests/CastTests.cs index 2fee7e189d..e0451ec953 100644 --- a/src/Hl7.FhirPath.Tests/Tests/CastTests.cs +++ b/src/Hl7.FhirPath.Tests/Tests/CastTests.cs @@ -10,9 +10,11 @@ //extern alias dstu2; using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Specification; using Hl7.FhirPath.Expressions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; using System.Collections.Generic; using System.Linq; @@ -21,24 +23,24 @@ namespace Hl7.FhirPath.Tests [TestClass] public class CastTests { - private static readonly ITypedElement complex = new ComplexValue(); - private static readonly IEnumerable collection = ElementNode.CreateList(4, 5, complex); - private static readonly IEnumerable singleV = ElementNode.CreateList(4L); - private static readonly IEnumerable singleC = ElementNode.CreateList(complex); - private static readonly IEnumerable emptyColl = ElementNode.EmptyList; + private static readonly IScopedNode complex = new ComplexValue().ToScopedNode(); + private static readonly IEnumerable collection = ElementNode.CreateList(4, 5, complex).ToScopedNodes(); + private static readonly IEnumerable singleV = ElementNode.CreateList(4L).ToScopedNodes(); + private static readonly IEnumerable singleC = ElementNode.CreateList(complex).ToScopedNodes(); + private static readonly IEnumerable emptyColl = []; [TestMethod] public void TestUnbox() { Assert.IsNull(Typecasts.UnboxTo(emptyColl, typeof(string))); - collection.SequenceEqual(Typecasts.UnboxTo(collection, typeof(IEnumerable)) as IEnumerable); - Assert.AreEqual(complex, Typecasts.UnboxTo(singleC, typeof(ITypedElement))); + collection.SequenceEqual(Typecasts.UnboxTo(collection, typeof(IEnumerable)) as IEnumerable); + Assert.AreEqual(complex, Typecasts.UnboxTo(singleC, typeof(IScopedNode))); Assert.AreEqual(4L, Typecasts.UnboxTo(singleV, typeof(long))); - Assert.AreEqual(4L, Typecasts.UnboxTo(ElementNode.ForPrimitive(4L), typeof(long))); + Assert.AreEqual(4L, Typecasts.UnboxTo(new Integer64(4L), typeof(long))); - Assert.AreEqual(complex, Typecasts.UnboxTo(complex, typeof(ITypedElement))); + Assert.AreEqual(complex, Typecasts.UnboxTo(complex, typeof(IScopedNode))); Assert.IsNull(Typecasts.UnboxTo(null, typeof(string))); Assert.AreEqual(4L, Typecasts.UnboxTo(4L, typeof(long))); Assert.AreEqual("hi!", Typecasts.UnboxTo("hi!", typeof(string))); @@ -48,8 +50,8 @@ public void TestUnbox() public void CastFromNull() { checkCast(null, null); - checkCast>(null, ElementNode.EmptyList); - checkCast(null, null); + checkCast>(null, []); + checkCast(null, null); Assert.IsFalse(Typecasts.CanCastTo(null, typeof(bool))); checkCast(null, null); checkCast(null, null); @@ -59,8 +61,8 @@ public void CastFromNull() public void CastCollection() { checkCast(collection, collection); - checkCast>(collection, collection); - Assert.IsFalse(Typecasts.CanCastTo(collection, typeof(ITypedElement))); + checkCast>(collection, collection); + Assert.IsFalse(Typecasts.CanCastTo(collection, typeof(IScopedNode))); Assert.IsFalse(Typecasts.CanCastTo(collection, typeof(bool))); Assert.IsFalse(Typecasts.CanCastTo(collection, typeof(bool?))); Assert.IsFalse(Typecasts.CanCastTo(collection, typeof(string))); @@ -71,10 +73,10 @@ public void CastComplex() { checkCast(complex, complex); - Assert.IsTrue(Typecasts.CanCastTo(complex, typeof(IEnumerable))); - var result = (IEnumerable)Typecasts.CastTo(complex, typeof(IEnumerable)); + Assert.IsTrue(Typecasts.CanCastTo(complex, typeof(IEnumerable))); + var result = (IEnumerable)Typecasts.CastTo(complex, typeof(IEnumerable)); Assert.AreEqual(complex, result.Single()); - checkCast(complex, complex); + checkCast(complex, complex); Assert.IsFalse(Typecasts.CanCastTo(collection, typeof(bool))); Assert.IsFalse(Typecasts.CanCastTo(collection, typeof(bool?))); Assert.IsFalse(Typecasts.CanCastTo(collection, typeof(string))); @@ -85,12 +87,12 @@ public void CastValue() { checkCast(4L, 4L); - Assert.IsTrue(Typecasts.CanCastTo(4, typeof(IEnumerable))); - var result = (IEnumerable)Typecasts.CastTo(4L, typeof(IEnumerable)); + Assert.IsTrue(Typecasts.CanCastTo(4, typeof(IEnumerable))); + var result = (IEnumerable)Typecasts.CastTo(4L, typeof(IEnumerable)); Assert.AreEqual(4L, result.Single().Value); - Assert.IsTrue(Typecasts.CanCastTo(4L, typeof(ITypedElement))); - var result2 = (ITypedElement)Typecasts.CastTo(4L, typeof(ITypedElement)); + Assert.IsTrue(Typecasts.CanCastTo(4L, typeof(IScopedNode))); + var result2 = (IScopedNode)Typecasts.CastTo(4L, typeof(IScopedNode)); Assert.AreEqual(4L, result2.Value); checkCast(true, true); @@ -110,12 +112,12 @@ public void CastNullable() { checkCast("hi", "hi"); - Assert.IsTrue(Typecasts.CanCastTo("hi", typeof(IEnumerable))); - var result = (IEnumerable)Typecasts.CastTo("hi", typeof(IEnumerable)); + Assert.IsTrue(Typecasts.CanCastTo("hi", typeof(IEnumerable))); + var result = (IEnumerable)Typecasts.CastTo("hi", typeof(IEnumerable)); Assert.AreEqual("hi", result.Single().Value); - Assert.IsTrue(Typecasts.CanCastTo("hi", typeof(ITypedElement))); - var result2 = (ITypedElement)Typecasts.CastTo("hi", typeof(ITypedElement)); + Assert.IsTrue(Typecasts.CanCastTo("hi", typeof(IScopedNode))); + var result2 = (IScopedNode)Typecasts.CastTo("hi", typeof(IScopedNode)); Assert.AreEqual("hi", result2.Value); checkCast(true, true); @@ -173,6 +175,6 @@ public object Value public IElementDefinitionSummary Definition => null; - public IEnumerable Children(string name = null) => new ITypedElement[0]; + public IEnumerable Children(string name = null) => []; } } \ No newline at end of file diff --git a/src/Hl7.FhirPath.Tests/Tests/ConversionsTests.cs b/src/Hl7.FhirPath.Tests/Tests/ConversionsTests.cs index 431ff34532..1779864f9f 100644 --- a/src/Hl7.FhirPath.Tests/Tests/ConversionsTests.cs +++ b/src/Hl7.FhirPath.Tests/Tests/ConversionsTests.cs @@ -215,7 +215,7 @@ public void ConvertToTime() [TestMethod] public void CheckTypeDetermination() { - var values = ElementNode.CreateList(1, 1L, true, "hi", 4.0m, 4.0f, P.DateTime.Now()); + var values = ElementNode.CreateList(1, 1L, true, "hi", 4.0m, 4.0f, P.DateTime.Now()).ToScopedNodes(); Test.IsInstanceOfType(values.Item(0).Single().Value, typeof(int)); Test.IsInstanceOfType(values.Item(0).Single().Value, typeof(long)); @@ -230,7 +230,7 @@ public void CheckTypeDetermination() [TestMethod] public void TestItemSelection() { - var values = ElementNode.CreateList(1L, 2, 3L, 4, 5, 6, 7); + var values = ElementNode.CreateList(1L, 2, 3L, 4, 5, 6, 7).ToScopedNodes(); Assert.AreEqual(1L, values.Item(0).Single().Value); Assert.AreEqual(2, values.Item(1).Single().Value); diff --git a/src/Hl7.FhirPath.Tests/Tests/EnviromentTests.cs b/src/Hl7.FhirPath.Tests/Tests/EnviromentTests.cs index c06df154a5..7591e8d213 100644 --- a/src/Hl7.FhirPath.Tests/Tests/EnviromentTests.cs +++ b/src/Hl7.FhirPath.Tests/Tests/EnviromentTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.FhirPath; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; @@ -16,7 +17,7 @@ public void TestEnvironment() var compiler = new FhirPathCompiler(); var expr = compiler.Compile("%var = 1"); - expr.IsTrue(null!, new EvaluationContext { Environment = new Dictionary> {{ "var", new [] { ElementNode.ForPrimitive(1) }}}} ).Should().BeTrue(); - expr.IsTrue(null!, new EvaluationContext { Environment = new Dictionary> {{ "var", new [] { ElementNode.ForPrimitive(2) }}}} ).Should().BeFalse(); + expr.IsTrue(null!, new EvaluationContext { Environment = new Dictionary> {{ "var", new [] { new Integer(1) }}}} ).Should().BeTrue(); + expr.IsTrue(null!, new EvaluationContext { Environment = new Dictionary> {{ "var", new [] { new Integer(2) }}}} ).Should().BeFalse(); } } \ No newline at end of file