From 572adf1eca5e56578561431fc9f202b3025d0a5e Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Wed, 6 Sep 2023 12:58:13 +0200 Subject: [PATCH 1/6] First try --- .../ElementModel/ElementNodeExtensions.cs | 4 ++ .../ElementModel/IBaseElementNavigator.cs | 63 +++++++++++++++++++ src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs | 22 +++++++ .../ElementModel/IScopedNodeExtensions.cs | 19 ++++++ .../ElementModel/ITypedElement.cs | 45 +------------ src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs | 16 ++++- .../ElementModel/ScopedNodeExtensions.cs | 2 +- .../ElementModel/TypedElementExtensions.cs | 4 +- .../TypedElementParseExtensions.cs | 21 ++++--- 9 files changed, 137 insertions(+), 59 deletions(-) create mode 100644 src/Hl7.Fhir.Base/ElementModel/IBaseElementNavigator.cs create mode 100644 src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs create mode 100644 src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs diff --git a/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs index 75f3ce82d0..ac9f9c4f96 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs @@ -108,5 +108,9 @@ public static IReadOnlyCollection ChildDefinitions(th public static ScopedNode ToScopedNode(this ITypedElement node) => node as ScopedNode ?? new ScopedNode(node); + + public static IScopedNode AsScopedNode(this ITypedElement node) => ToScopedNode(node); } } + +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/IBaseElementNavigator.cs b/src/Hl7.Fhir.Base/ElementModel/IBaseElementNavigator.cs new file mode 100644 index 0000000000..ebb80d7070 --- /dev/null +++ b/src/Hl7.Fhir.Base/ElementModel/IBaseElementNavigator.cs @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + +using System.Collections.Generic; + +#nullable enable + +namespace Hl7.Fhir.ElementModel +{ + public interface IBaseElementNavigator where TDerived : IBaseElementNavigator + { + /// + /// Enumerate the child nodes present in the source representation (if any) + /// + /// Return only the children with the given name. + /// + IEnumerable Children(string? name = null); + + /// + /// Name of the node, e.g. "active", "value". + /// + string Name { get; } + + /// + /// Type of the node. If a FHIR type, this is just a simple string, otherwise a StructureDefinition url for a type defined as a logical model. + /// + string InstanceType { get; } + + /// + /// The value of the node (if it represents a primitive FHIR value) + /// + /// + /// FHIR primitives are mapped to underlying C# types as follows: + /// + /// instant Hl7.Fhir.ElementModel.Types.DateTime + /// time Hl7.Fhir.ElementModel.Types.Time + /// date Hl7.Fhir.ElementModel.Types.Date + /// dateTime Hl7.Fhir.ElementModel.Types.DateTime + /// decimal decimal + /// boolean bool + /// integer int + /// unsignedInt int + /// positiveInt int + /// long/integer64 long (name will be finalized in R5) + /// string string + /// code string + /// id string + /// uri, oid, uuid, + /// canonical, url string + /// markdown string + /// base64Binary string (uuencoded) + /// xhtml string + /// + object Value { get; } + } +} + +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs b/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs new file mode 100644 index 0000000000..a31c646268 --- /dev/null +++ b/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + +#nullable enable + +namespace Hl7.Fhir.ElementModel +{ + /// + /// + /// + public interface IScopedNode : IBaseElementNavigator + { + IScopedNode? Parent { get; } + } +} + +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs new file mode 100644 index 0000000000..695950cbed --- /dev/null +++ b/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + +#nullable enable + +namespace Hl7.Fhir.ElementModel +{ + public static class IScopedNodeExtensions + { + public static string GetLocation(this IScopedNode node) => ""; // TODO + } +} + +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/ITypedElement.cs b/src/Hl7.Fhir.Base/ElementModel/ITypedElement.cs index bb7e8403a8..6e0a5a88ee 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ITypedElement.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ITypedElement.cs @@ -7,7 +7,6 @@ */ using Hl7.Fhir.Specification; -using System.Collections.Generic; namespace Hl7.Fhir.ElementModel { @@ -19,51 +18,9 @@ namespace Hl7.Fhir.ElementModel /// the instance or derived from fully aware of the FHIR definitions and types /// - public interface ITypedElement + public interface ITypedElement : IBaseElementNavigator { - /// - /// Enumerate the child nodes present in the source representation (if any) - /// - /// Return only the children with the given name. - /// - IEnumerable Children(string name = null); - - /// - /// Name of the node, e.g. "active", "value". - /// - string Name { get; } - /// - /// Type of the node. If a FHIR type, this is just a simple string, otherwise a StructureDefinition url for a type defined as a logical model. - /// - string InstanceType { get; } - - /// - /// The value of the node (if it represents a primitive FHIR value) - /// - /// - /// FHIR primitives are mapped to underlying C# types as follows: - /// - /// instant Hl7.Fhir.ElementModel.Types.DateTime - /// time Hl7.Fhir.ElementModel.Types.Time - /// date Hl7.Fhir.ElementModel.Types.Date - /// dateTime Hl7.Fhir.ElementModel.Types.DateTime - /// decimal decimal - /// boolean bool - /// integer int - /// unsignedInt int - /// positiveInt int - /// long/integer64 long (name will be finalized in R5) - /// string string - /// code string - /// id string - /// uri, oid, uuid, - /// canonical, url string - /// markdown string - /// base64Binary string (uuencoded) - /// xhtml string - /// - object Value { get; } /// /// An indication of the location of this node within the data represented by the ITypedElement. diff --git a/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs b/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs index 72001de883..51eb9c5bd5 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs @@ -16,7 +16,7 @@ namespace Hl7.Fhir.ElementModel { - public class ScopedNode : ITypedElement, IAnnotated, IExceptionSource + public class ScopedNode : ITypedElement, IScopedNode, IAnnotated, IExceptionSource { private class Cache { @@ -45,6 +45,7 @@ public ScopedNode(ITypedElement wrapped, string? instanceUri = null) private ScopedNode(ScopedNode parentNode, ScopedNode? parentResource, ITypedElement wrapped, string? fullUrl) { Current = wrapped; + Parent = parentNode; ExceptionHandler = parentNode.ExceptionHandler; ParentResource = parentNode.AtResource ? parentNode : parentResource; @@ -229,11 +230,22 @@ private set } } + public IScopedNode? Parent { get; private set; } + /// public IEnumerable Annotations(Type type) => type == typeof(ScopedNode) ? (new[] { this }) : Current.Annotations(type); + + private IEnumerable childrenInternal(string? name = null) => + Current.Children(name).Select(c => new ScopedNode(this, ParentResource, c, _fullUrl)); + /// public IEnumerable Children(string? name = null) => - Current.Children(name).Select(c => new ScopedNode(this, ParentResource, c, _fullUrl)); + childrenInternal(name); + + IEnumerable IBaseElementNavigator.Children(string? name) => + childrenInternal(name); } } + +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/ScopedNodeExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/ScopedNodeExtensions.cs index de357cd544..d4f2359777 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ScopedNodeExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ScopedNodeExtensions.cs @@ -136,7 +136,7 @@ public static string MakeAbsolute(this ScopedNode node, string reference) => string? url = element switch { { Value: string s } => s, - { InstanceType: FhirTypeConstants.REFERENCE } => element.ParseResourceReference()?.Reference, + { InstanceType: FhirTypeConstants.REFERENCE } => element.ParseResourceReference()?.Reference, _ => null }; diff --git a/src/Hl7.Fhir.Base/ElementModel/TypedElementExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/TypedElementExtensions.cs index 986e54c602..e703bdc706 100644 --- a/src/Hl7.Fhir.Base/ElementModel/TypedElementExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/TypedElementExtensions.cs @@ -37,7 +37,7 @@ public static ITypedElement ToTypedElement(this Base @base, ModelInspector model /// When true the order of the children is discarded. When false the order of children is part /// of the equation. /// true when the ITypedElements are equal, false otherwise. - public static bool IsExactlyEqualTo(this ITypedElement left, ITypedElement right, bool ignoreOrder = false) + public static bool IsExactlyEqualTo(this T left, T right, bool ignoreOrder = false) where T : IBaseElementNavigator { if (left == null && right == null) return true; if (left == null || right == null) return false; @@ -95,7 +95,7 @@ public static bool ValueEquality(T1 val1, T2 val2) /// /// /// true when matches the , false otherwise. - public static bool Matches(this ITypedElement value, ITypedElement pattern) + public static bool Matches(this T value, T pattern) where T : IBaseElementNavigator { if (value == null && pattern == null) return true; if (value == null || pattern == null) return false; diff --git a/src/Hl7.Fhir.Base/ElementModel/TypedElementParseExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/TypedElementParseExtensions.cs index 08b0f6d57a..f18cc4eeba 100644 --- a/src/Hl7.Fhir.Base/ElementModel/TypedElementParseExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/TypedElementParseExtensions.cs @@ -31,14 +31,14 @@ public static class TypedElementParseExtensions /// 'string' => code /// 'uri' => code /// - public static Element ParseBindable(this ITypedElement instance) + public static Element ParseBindable(this T instance) where T : IBaseElementNavigator { return instance.InstanceType switch { - FhirTypeConstants.CODE => instance.ParsePrimitive(), - FhirTypeConstants.STRING => new Code(instance.ParsePrimitive()?.Value), - FhirTypeConstants.URI => new Code(instance.ParsePrimitive()?.Value), + FhirTypeConstants.CODE => instance.ParsePrimitive(), + FhirTypeConstants.STRING => new Code(instance.ParsePrimitive()?.Value), + FhirTypeConstants.URI => new Code(instance.ParsePrimitive()?.Value), FhirTypeConstants.CODING => instance.ParseCoding(), FhirTypeConstants.CODEABLE_CONCEPT => instance.ParseCodeableConcept(), FhirTypeConstants.QUANTITY => parseQuantity(), @@ -62,7 +62,7 @@ Element parseExtension() } } - public static Model.Quantity ParseQuantity(this ITypedElement instance) + public static Model.Quantity ParseQuantity(this T instance) where T : IBaseElementNavigator { var newQuantity = new Quantity { @@ -79,11 +79,11 @@ public static Model.Quantity ParseQuantity(this ITypedElement instance) return newQuantity; } - public static T ParsePrimitive(this ITypedElement instance) where T : PrimitiveType, new() + public static T ParsePrimitive(this U instance) where T : PrimitiveType, new() where U : IBaseElementNavigator => new T() { ObjectValue = instance.Value }; - public static Coding ParseCoding(this ITypedElement instance) + public static Coding ParseCoding(this T instance) where T : IBaseElementNavigator { return new Coding() { @@ -95,7 +95,7 @@ public static Coding ParseCoding(this ITypedElement instance) }; } - public static ResourceReference ParseResourceReference(this ITypedElement instance) + public static ResourceReference ParseResourceReference(this T instance) where T : IBaseElementNavigator { return new ResourceReference() { @@ -104,7 +104,7 @@ public static ResourceReference ParseResourceReference(this ITypedElement instan }; } - public static CodeableConcept ParseCodeableConcept(this ITypedElement instance) + public static CodeableConcept ParseCodeableConcept(this T instance) where T : IBaseElementNavigator { return new CodeableConcept() { @@ -114,6 +114,7 @@ public static CodeableConcept ParseCodeableConcept(this ITypedElement instance) }; } - public static string GetString(this IEnumerable instance) => instance.SingleOrDefault()?.Value as string; + public static string GetString(this IEnumerable instance) where T : IBaseElementNavigator + => instance.SingleOrDefault()?.Value as string; } } From be5c020ca75e3dbdca7aa36e03386fb3a75857a6 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Thu, 7 Sep 2023 09:30:32 +0200 Subject: [PATCH 2/6] - IScopedNode for FhirPath as well - extra extension methods for IScopedNode --- .../ScopedNodeToTypedElementAdapter.cs | 41 +++++++++++++++++++ .../ElementModel/IScopedNodeExtensions.cs | 14 ++++++- .../FhirPath/CompiledExpression.cs | 12 ++++++ .../FhirPath/EvaluationContext.cs | 4 ++ .../FhirPath/FhirPathCompilerCache.cs | 4 ++ .../Functions/FunctionsTests.cs | 2 +- 6 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs diff --git a/src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs b/src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs new file mode 100644 index 0000000000..592005deeb --- /dev/null +++ b/src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + +#nullable enable + +using Hl7.Fhir.Specification; +using System.Collections.Generic; +using System.Linq; + +namespace Hl7.Fhir.ElementModel +{ + internal class ScopedNodeToTypedElementAdapter : ITypedElement + { + private readonly IScopedNode _node; + + public ScopedNodeToTypedElementAdapter(IScopedNode node) + { + _node = node; + } + + public string Location => _node.GetLocation(); + + public IElementDefinitionSummary Definition => throw new System.NotImplementedException(); + + public string Name => _node.Name; + + public string InstanceType => _node.InstanceType; + + public object Value => _node.Value; + + public IEnumerable Children(string? name = null) => + _node.Children(name).Select(n => new ScopedNodeToTypedElementAdapter(n)); + } +} + +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs index 695950cbed..61eeed9e76 100644 --- a/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs @@ -8,11 +8,23 @@ #nullable enable +using System.Collections.Generic; +using System.Linq; + namespace Hl7.Fhir.ElementModel { public static class IScopedNodeExtensions { - public static string GetLocation(this IScopedNode node) => ""; // TODO + public static string GetLocation(this IScopedNode node) => + node.Parent is null ? node.Name : $"{node.Name}.{node.Parent.GetLocation()}"; // TODO: add unittest and verify this is the definition of Location + + public static IEnumerable Children(this IEnumerable nodes, string? name = null) => + nodes.SelectMany(n => n.Children(name)); + + public static ITypedElement AsTypedElement(this IScopedNode node) => + node is ITypedElement ite ? ite : new ScopedNodeToTypedElementAdapter(node); + + public static IScopedNode GetRootResource(this IScopedNode node) => null!;// TODO } } diff --git a/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs b/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs index 5a8ae6a4b4..31800daf8b 100644 --- a/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs +++ b/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs @@ -24,6 +24,9 @@ public static class CompiledExpressionExtensions return result.Any() ? result.Single().Value : null; } + public static object? Scalar(this CompiledExpression evaluator, IScopedNode input, EvaluationContext ctx) => + Scalar(evaluator, input.AsTypedElement(), ctx); + /// /// Evaluates an expression and returns true for expression being evaluated as true or empty, otherwise false. /// @@ -37,6 +40,10 @@ public static bool Predicate(this CompiledExpression evaluator, ITypedElement in return result is null || result.Value; } + public static bool Predicate(this CompiledExpression evaluator, IScopedNode input, EvaluationContext ctx) => + Predicate(evaluator, input.AsTypedElement(), ctx); + + /// /// Evaluates an expression and returns true for expression being evaluated as true, and false if the expression returns false or empty. /// @@ -49,6 +56,9 @@ public static bool IsTrue(this CompiledExpression evaluator, ITypedElement input var result = evaluator(input, ctx).BooleanEval(); return result is not null && result.Value; } + public static bool IsTrue(this CompiledExpression evaluator, IScopedNode input, EvaluationContext ctx) => + IsTrue(evaluator, input.AsTypedElement(), ctx); + /// /// Evaluates if the result of an expression is equal to a given boolean. @@ -63,6 +73,8 @@ public static bool IsBoolean(this CompiledExpression evaluator, bool value, ITyp var result = evaluator(input, ctx).BooleanEval(); return result is not null && result.Value == value; } + public static bool IsBoolean(this CompiledExpression evaluator, bool value, IScopedNode input, EvaluationContext ctx) => + IsBoolean(evaluator, value, input.AsTypedElement(), ctx); } } diff --git a/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs b/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs index 36a1af6241..ae44614b9e 100644 --- a/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs +++ b/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs @@ -30,6 +30,10 @@ public EvaluationContext(ITypedElement resource, ITypedElement rootResource) RootResource = rootResource ?? resource; } + public EvaluationContext(IScopedNode resource) : this(resource.AsTypedElement(), resource.GetRootResource().AsTypedElement()) + { + } + /// /// The data represented by %rootResource. /// diff --git a/src/Hl7.Fhir.Base/FhirPath/FhirPathCompilerCache.cs b/src/Hl7.Fhir.Base/FhirPath/FhirPathCompilerCache.cs index 18da767905..8a5399ccf3 100644 --- a/src/Hl7.Fhir.Base/FhirPath/FhirPathCompilerCache.cs +++ b/src/Hl7.Fhir.Base/FhirPath/FhirPathCompilerCache.cs @@ -11,6 +11,7 @@ using Hl7.Fhir.ElementModel; using Hl7.Fhir.Utility; using System.Collections.Generic; +using System.Linq; namespace Hl7.FhirPath { @@ -65,6 +66,9 @@ public IEnumerable Select(ITypedElement input, string expression, return evaluator(input, ctx ?? EvaluationContext.CreateDefault()); } + public IEnumerable Select(IScopedNode input, string expression, EvaluationContext? ctx = null) => + Select(input.AsTypedElement(), expression, ctx).Select(t => t.AsScopedNode()); + /// /// Evaluates an expression against a given context and returns a single result /// diff --git a/src/Hl7.FhirPath.Tests/Functions/FunctionsTests.cs b/src/Hl7.FhirPath.Tests/Functions/FunctionsTests.cs index cf5ea7e564..270ef37591 100644 --- a/src/Hl7.FhirPath.Tests/Functions/FunctionsTests.cs +++ b/src/Hl7.FhirPath.Tests/Functions/FunctionsTests.cs @@ -546,7 +546,7 @@ public void SingleScalarTest() }); var expression = new FhirPathCompiler(symbols).Compile("once()"); - var result = expression.Scalar(null, new EvaluationContext()); + var result = expression.Scalar((ITypedElement)null, new EvaluationContext()); Assert.AreEqual(result, 1); } From a4796f6ffda4d3c76e395dffff7b755362d49347 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Fri, 15 Sep 2023 09:42:51 +0200 Subject: [PATCH 3/6] Added Childindex --- src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs | 3 +++ .../ElementModel/IScopedNodeExtensions.cs | 9 +++++++-- src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs | 13 ++++++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs b/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs index a31c646268..3555dd2976 100644 --- a/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs +++ b/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs @@ -16,6 +16,9 @@ namespace Hl7.Fhir.ElementModel public interface IScopedNode : IBaseElementNavigator { IScopedNode? Parent { get; } + + int? ChildIndex { get; } + } } diff --git a/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs index 61eeed9e76..0174ea832f 100644 --- a/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs @@ -16,15 +16,20 @@ namespace Hl7.Fhir.ElementModel public static class IScopedNodeExtensions { public static string GetLocation(this IScopedNode node) => - node.Parent is null ? node.Name : $"{node.Name}.{node.Parent.GetLocation()}"; // TODO: add unittest and verify this is the definition of Location + node.Parent is null ? node.Name : $"{node.Parent.GetLocation()}.{node.Name}{node.index()}"; // TODO: add unittest and verify this is the definition of Location + private static string? index(this IScopedNode node) => + node.ChildIndex is null ? null : $"[{node.ChildIndex}]"; public static IEnumerable Children(this IEnumerable nodes, string? name = null) => nodes.SelectMany(n => n.Children(name)); public static ITypedElement AsTypedElement(this IScopedNode node) => node is ITypedElement ite ? ite : new ScopedNodeToTypedElementAdapter(node); - public static IScopedNode GetRootResource(this IScopedNode node) => null!;// TODO + public static IScopedNode GetRootResource(this IScopedNode node) // TODO + { + return null!; + } } } diff --git a/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs b/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs index 51eb9c5bd5..2dd1d1e9ce 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs @@ -232,10 +232,21 @@ private set public IScopedNode? Parent { get; private set; } + public int? ChildIndex + { + get + { + if (Definition?.IsCollection == false) return null; + + // TODO: maybe too costly? + var index = Parent?.Children(Name).ToList().IndexOf(this); + return index == -1 ? null : index; + } + } + /// public IEnumerable Annotations(Type type) => type == typeof(ScopedNode) ? (new[] { this }) : Current.Annotations(type); - private IEnumerable childrenInternal(string? name = null) => Current.Children(name).Select(c => new ScopedNode(this, ParentResource, c, _fullUrl)); From 45dbd22640053ebdf3ed74e1fb34dbe56cc649da Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 14 Nov 2023 13:52:06 +0100 Subject: [PATCH 4/6] revert some location elements --- .../ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs | 2 +- src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs | 3 --- src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs | 5 ----- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs b/src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs index 592005deeb..84f8152c79 100644 --- a/src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs +++ b/src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs @@ -23,7 +23,7 @@ public ScopedNodeToTypedElementAdapter(IScopedNode node) _node = node; } - public string Location => _node.GetLocation(); + public string Location => throw new System.NotImplementedException(); public IElementDefinitionSummary Definition => throw new System.NotImplementedException(); diff --git a/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs b/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs index 3555dd2976..a31c646268 100644 --- a/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs +++ b/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs @@ -16,9 +16,6 @@ namespace Hl7.Fhir.ElementModel public interface IScopedNode : IBaseElementNavigator { IScopedNode? Parent { get; } - - int? ChildIndex { get; } - } } diff --git a/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs index 0174ea832f..5df2b90c86 100644 --- a/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs @@ -15,11 +15,6 @@ namespace Hl7.Fhir.ElementModel { public static class IScopedNodeExtensions { - public static string GetLocation(this IScopedNode node) => - node.Parent is null ? node.Name : $"{node.Parent.GetLocation()}.{node.Name}{node.index()}"; // TODO: add unittest and verify this is the definition of Location - - private static string? index(this IScopedNode node) => - node.ChildIndex is null ? null : $"[{node.ChildIndex}]"; public static IEnumerable Children(this IEnumerable nodes, string? name = null) => nodes.SelectMany(n => n.Children(name)); From f59a10fd0b3f101ad724c66e75258c889dd39f03 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 14 Nov 2023 14:46:09 +0100 Subject: [PATCH 5/6] Documentation and clean up --- src/Benchmarks/EnumUtilityBenchmarks.cs | 15 ++++++----- .../ScopedNodeToTypedElementAdapter.cs | 20 +++++++++------ .../ElementModel/ElementNodeExtensions.cs | 5 ++++ .../ElementModel/IBaseElementNavigator.cs | 4 +++ src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs | 9 ++++++- .../ElementModel/IScopedNodeExtensions.cs | 25 +++++++++++++------ src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs | 16 ++---------- .../FhirPath/CompiledExpression.cs | 11 -------- .../FhirPath/EvaluationContext.cs | 4 --- .../FhirPath/FhirPathCompilerCache.cs | 4 --- .../Functions/FunctionsTests.cs | 2 +- 11 files changed, 60 insertions(+), 55 deletions(-) diff --git a/src/Benchmarks/EnumUtilityBenchmarks.cs b/src/Benchmarks/EnumUtilityBenchmarks.cs index 527acc7653..1ea5258531 100644 --- a/src/Benchmarks/EnumUtilityBenchmarks.cs +++ b/src/Benchmarks/EnumUtilityBenchmarks.cs @@ -3,6 +3,8 @@ using Hl7.Fhir.Utility; using System; +#nullable enable + namespace Firely.Sdk.Benchmarks { [MemoryDiagnoser] @@ -16,7 +18,7 @@ public string EnumToString() => SearchParamType.String.ToString(); [Benchmark] - public string EnumGetName() + public string? EnumGetName() => Enum.GetName(StringSearchParam); [Benchmark] @@ -37,18 +39,18 @@ public SearchParamType EnumParseIgnoreCase() [Benchmark] public SearchParamType EnumUtilityParseLiteral() - => EnumUtility.ParseLiteral("string").Value; + => EnumUtility.ParseLiteral("string")!.Value; [Benchmark] - public Enum EnumUtilityParseLiteralNonGeneric() + public Enum? EnumUtilityParseLiteralNonGeneric() => EnumUtility.ParseLiteral("string", typeof(SearchParamType)); [Benchmark] public SearchParamType EnumUtilityParseLiteralIgnoreCase() - => EnumUtility.ParseLiteral("string", true).Value; + => EnumUtility.ParseLiteral("string", true)!.Value; [Benchmark] - public Enum EnumUtilityParseLiteralIgnoreCaseNonGeneric() + public Enum? EnumUtilityParseLiteralIgnoreCaseNonGeneric() => EnumUtility.ParseLiteral("string", typeof(SearchParamType), true); [Benchmark] @@ -56,7 +58,8 @@ public Enum EnumUtilityParseLiteralIgnoreCaseNonGeneric() => EnumUtility.GetSystem(StringSearchParam); [Benchmark] - public string EnumUtilityGetSystemNonGeneric() + public string? EnumUtilityGetSystemNonGeneric() => EnumUtility.GetSystem(StringSearchParamEnum); } } +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs b/src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs index 84f8152c79..3d0814c177 100644 --- a/src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs +++ b/src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs @@ -14,27 +14,33 @@ namespace Hl7.Fhir.ElementModel { + /// + /// An adapter from to . + /// + /// Be careful, this adapter does not implement the and + /// property. + /// internal class ScopedNodeToTypedElementAdapter : ITypedElement { - private readonly IScopedNode _node; + private readonly IScopedNode _adaptee; - public ScopedNodeToTypedElementAdapter(IScopedNode node) + public ScopedNodeToTypedElementAdapter(IScopedNode adaptee) { - _node = node; + _adaptee = adaptee; } public string Location => throw new System.NotImplementedException(); public IElementDefinitionSummary Definition => throw new System.NotImplementedException(); - public string Name => _node.Name; + public string Name => _adaptee.Name; - public string InstanceType => _node.InstanceType; + public string InstanceType => _adaptee.InstanceType; - public object Value => _node.Value; + public object Value => _adaptee.Value; public IEnumerable Children(string? name = null) => - _node.Children(name).Select(n => new ScopedNodeToTypedElementAdapter(n)); + _adaptee.Children(name).Select(n => new ScopedNodeToTypedElementAdapter(n)); } } diff --git a/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs index ac9f9c4f96..6df5f749d2 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs @@ -109,6 +109,11 @@ public static IReadOnlyCollection ChildDefinitions(th public static ScopedNode ToScopedNode(this ITypedElement node) => node as ScopedNode ?? new ScopedNode(node); + /// + /// Convert a to a . + /// + /// An + /// public static IScopedNode AsScopedNode(this ITypedElement node) => ToScopedNode(node); } } diff --git a/src/Hl7.Fhir.Base/ElementModel/IBaseElementNavigator.cs b/src/Hl7.Fhir.Base/ElementModel/IBaseElementNavigator.cs index ebb80d7070..df03548715 100644 --- a/src/Hl7.Fhir.Base/ElementModel/IBaseElementNavigator.cs +++ b/src/Hl7.Fhir.Base/ElementModel/IBaseElementNavigator.cs @@ -12,6 +12,10 @@ namespace Hl7.Fhir.ElementModel { + /// + /// The base interface for and ."/> + /// + /// public interface IBaseElementNavigator where TDerived : IBaseElementNavigator { /// diff --git a/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs b/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs index a31c646268..968dc12268 100644 --- a/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs +++ b/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs @@ -11,10 +11,17 @@ namespace Hl7.Fhir.ElementModel { /// - /// + /// An element within a tree of typed FHIR data with also a parent element. /// + /// + /// This interface represents FHIR data as a tree of elements, including type information either present in + /// the instance or derived from fully aware of the FHIR definitions and types + /// public interface IScopedNode : IBaseElementNavigator { + /// + /// The parent node of this node, or null if this is the root node. + /// IScopedNode? Parent { get; } } } diff --git a/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs index 5df2b90c86..30bbca5b85 100644 --- a/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs @@ -8,6 +8,7 @@ #nullable enable + using System.Collections.Generic; using System.Linq; @@ -15,16 +16,26 @@ namespace Hl7.Fhir.ElementModel { public static class IScopedNodeExtensions { - public static IEnumerable Children(this IEnumerable nodes, string? name = null) => - nodes.SelectMany(n => n.Children(name)); - + /// + /// Converts a to a . + /// + /// An node + /// An implementation of + /// Be careful when using this method, the returned does not implement + /// the methods and . + /// public static ITypedElement AsTypedElement(this IScopedNode node) => node is ITypedElement ite ? ite : new ScopedNodeToTypedElementAdapter(node); - public static IScopedNode GetRootResource(this IScopedNode node) // TODO - { - return null!; - } + /// + /// Returns the parent resource of this node, or null if this node is not part of a resource. + /// + /// + /// + /// + public static IEnumerable Children(this IEnumerable nodes, string? name = null) => + nodes.SelectMany(n => n.Children(name)); + } } diff --git a/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs b/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs index 2dd1d1e9ce..dabf05cff3 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs @@ -218,8 +218,7 @@ public string? InstanceUri { // Scan up until the first parent that knowns the instance uri (at the last the // root, if it has been supplied). - if (_cache.InstanceUri is null) - _cache.InstanceUri = ParentResources().SkipWhile(p => p.InstanceUri is null).FirstOrDefault()?.InstanceUri; + _cache.InstanceUri ??= ParentResources().SkipWhile(p => p.InstanceUri is null).FirstOrDefault()?.InstanceUri; return _cache.InstanceUri; } @@ -230,20 +229,9 @@ private set } } + /// public IScopedNode? Parent { get; private set; } - public int? ChildIndex - { - get - { - if (Definition?.IsCollection == false) return null; - - // TODO: maybe too costly? - var index = Parent?.Children(Name).ToList().IndexOf(this); - return index == -1 ? null : index; - } - } - /// public IEnumerable Annotations(Type type) => type == typeof(ScopedNode) ? (new[] { this }) : Current.Annotations(type); diff --git a/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs b/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs index 31800daf8b..54072a9c50 100644 --- a/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs +++ b/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs @@ -24,9 +24,6 @@ public static class CompiledExpressionExtensions return result.Any() ? result.Single().Value : null; } - public static object? Scalar(this CompiledExpression evaluator, IScopedNode input, EvaluationContext ctx) => - Scalar(evaluator, input.AsTypedElement(), ctx); - /// /// Evaluates an expression and returns true for expression being evaluated as true or empty, otherwise false. /// @@ -40,10 +37,6 @@ public static bool Predicate(this CompiledExpression evaluator, ITypedElement in return result is null || result.Value; } - public static bool Predicate(this CompiledExpression evaluator, IScopedNode input, EvaluationContext ctx) => - Predicate(evaluator, input.AsTypedElement(), ctx); - - /// /// Evaluates an expression and returns true for expression being evaluated as true, and false if the expression returns false or empty. /// @@ -56,8 +49,6 @@ public static bool IsTrue(this CompiledExpression evaluator, ITypedElement input var result = evaluator(input, ctx).BooleanEval(); return result is not null && result.Value; } - public static bool IsTrue(this CompiledExpression evaluator, IScopedNode input, EvaluationContext ctx) => - IsTrue(evaluator, input.AsTypedElement(), ctx); /// @@ -73,8 +64,6 @@ public static bool IsBoolean(this CompiledExpression evaluator, bool value, ITyp var result = evaluator(input, ctx).BooleanEval(); return result is not null && result.Value == value; } - public static bool IsBoolean(this CompiledExpression evaluator, bool value, IScopedNode input, EvaluationContext ctx) => - IsBoolean(evaluator, value, input.AsTypedElement(), ctx); } } diff --git a/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs b/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs index ae44614b9e..36a1af6241 100644 --- a/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs +++ b/src/Hl7.Fhir.Base/FhirPath/EvaluationContext.cs @@ -30,10 +30,6 @@ public EvaluationContext(ITypedElement resource, ITypedElement rootResource) RootResource = rootResource ?? resource; } - public EvaluationContext(IScopedNode resource) : this(resource.AsTypedElement(), resource.GetRootResource().AsTypedElement()) - { - } - /// /// The data represented by %rootResource. /// diff --git a/src/Hl7.Fhir.Base/FhirPath/FhirPathCompilerCache.cs b/src/Hl7.Fhir.Base/FhirPath/FhirPathCompilerCache.cs index 8a5399ccf3..18da767905 100644 --- a/src/Hl7.Fhir.Base/FhirPath/FhirPathCompilerCache.cs +++ b/src/Hl7.Fhir.Base/FhirPath/FhirPathCompilerCache.cs @@ -11,7 +11,6 @@ using Hl7.Fhir.ElementModel; using Hl7.Fhir.Utility; using System.Collections.Generic; -using System.Linq; namespace Hl7.FhirPath { @@ -66,9 +65,6 @@ public IEnumerable Select(ITypedElement input, string expression, return evaluator(input, ctx ?? EvaluationContext.CreateDefault()); } - public IEnumerable Select(IScopedNode input, string expression, EvaluationContext? ctx = null) => - Select(input.AsTypedElement(), expression, ctx).Select(t => t.AsScopedNode()); - /// /// Evaluates an expression against a given context and returns a single result /// diff --git a/src/Hl7.FhirPath.Tests/Functions/FunctionsTests.cs b/src/Hl7.FhirPath.Tests/Functions/FunctionsTests.cs index 270ef37591..cf5ea7e564 100644 --- a/src/Hl7.FhirPath.Tests/Functions/FunctionsTests.cs +++ b/src/Hl7.FhirPath.Tests/Functions/FunctionsTests.cs @@ -546,7 +546,7 @@ public void SingleScalarTest() }); var expression = new FhirPathCompiler(symbols).Compile("once()"); - var result = expression.Scalar((ITypedElement)null, new EvaluationContext()); + var result = expression.Scalar(null, new EvaluationContext()); Assert.AreEqual(result, 1); } From 3836d3089b5e5fc6947da8479c4b2ea1e8829a21 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Fri, 1 Dec 2023 15:51:02 +0100 Subject: [PATCH 6/6] Added support for Async resource resolving. --- .../Specification/Source/SnapshotSource.cs | 16 +++++++++++----- src/firely-net-sdk.props | 4 ++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/SnapshotSource.cs b/src/Hl7.Fhir.Conformance/Specification/Source/SnapshotSource.cs index 8700f8db37..f9680ccc95 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/SnapshotSource.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/SnapshotSource.cs @@ -3,7 +3,7 @@ using Hl7.Fhir.Utility; using System; using System.Diagnostics; -using T=System.Threading.Tasks; +using T = System.Threading.Tasks; namespace Hl7.Fhir.Specification.Source { @@ -34,7 +34,9 @@ public SnapshotSource(SnapshotGenerator generator) /// Creates a new instance of the for the specified internal resolver. /// An internal instance. The implementation should be idempotent (i.e. cached), so the generated snapshots are persisted in memory. /// Configuration settings for the snapshot generator. - public SnapshotSource(IResourceResolver source, SnapshotGeneratorSettings settings) +#pragma warning disable CS0618 // Type or member is obsolete + public SnapshotSource(ISyncOrAsyncResourceResolver source, SnapshotGeneratorSettings settings) +#pragma warning restore CS0618 // Type or member is obsolete { // SnapshotGenerator ctor will throw if source or settings are null Generator = new SnapshotGenerator(source, settings); @@ -43,11 +45,13 @@ public SnapshotSource(IResourceResolver source, SnapshotGeneratorSettings settin /// Creates a new instance of the for the specified internal resolver. /// An internal instance. The implementation should be idempotent (i.e. cached), so the generated snapshots are persisted in memory. /// Determines if the source should always discard any existing snapshot components provided by the internal source and force re-generation. - public SnapshotSource(IResourceResolver source, bool regenerate) +#pragma warning disable CS0618 // Type or member is obsolete + public SnapshotSource(ISyncOrAsyncResourceResolver source, bool regenerate) +#pragma warning restore CS0618 // Type or member is obsolete : this(source, createSettings(regenerate)) { } // Create default SnapshotGeneratorSettings, apply the specified regenerate flag - static SnapshotGeneratorSettings createSettings(bool regenerate) + private static SnapshotGeneratorSettings createSettings(bool regenerate) { var settings = SnapshotGeneratorSettings.CreateDefault(); settings.ForceRegenerateSnapshots = regenerate; @@ -56,7 +60,9 @@ static SnapshotGeneratorSettings createSettings(bool regenerate) /// Creates a new instance of the for the specified internal resolver. /// An internal instance. The implementation should be idempotent (i.e. cached), so the generated snapshots are persisted in memory. - public SnapshotSource(IResourceResolver source) +#pragma warning disable CS0618 // Type or member is obsolete + public SnapshotSource(ISyncOrAsyncResourceResolver source) +#pragma warning restore CS0618 // Type or member is obsolete : this(source, SnapshotGeneratorSettings.CreateDefault()) { } /// Returns the internal instance used by the source. diff --git a/src/firely-net-sdk.props b/src/firely-net-sdk.props index e0da87fe14..7d59a3a0ca 100644 --- a/src/firely-net-sdk.props +++ b/src/firely-net-sdk.props @@ -6,8 +6,8 @@ - 5.4.1 - + 5.4.2 + alpha3 Firely (info@fire.ly) and contributors Firely (https://fire.ly) Copyright 2013-2023 Firely. Contains materials (C) HL7 International