Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Hl7.Fhir.Specification;
using Hl7.Fhir.Utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

Expand Down
211 changes: 211 additions & 0 deletions src/Hl7.Fhir.Base/ElementModel/PocoElementNode2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
using Hl7.Fhir.Model;
using Hl7.Fhir.Specification;
using Hl7.Fhir.Utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;

namespace Hl7.Fhir.ElementModel;

#nullable enable

public abstract record PocoElementNode2(PocoElementNode2? Parent, string Name) : IEnumerable<SinglePocoElementNode>
{
public abstract IEnumerator<SinglePocoElementNode> GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public static SinglePocoElementNode Root(Base @base, string? name = null) => @base switch
{
PrimitiveType primitive => new SinglePrimitiveElementNode(primitive, name),
{ } b => new SinglePocoElementNode(b, null, null, name)
};

public static SinglePocoElementNode ForPrimitive(PrimitiveType primitive) =>
new SinglePrimitiveElementNode(primitive);

public static SinglePocoElementNode ForPrimitive<T>(object value) where T : PrimitiveType, new() =>
new SinglePrimitiveElementNode(new T { ObjectValue = value });

public static IEnumerable<SinglePocoElementNode> FromList(IEnumerable<PrimitiveType> primitives, string? name = null) =>
new RepeatingPrimitiveElementNode(primitives.ToList());
Comment thread
ewoutkramer marked this conversation as resolved.
Outdated

public static IEnumerable<SinglePocoElementNode> FromList<T>(IEnumerable<object> values) where T : PrimitiveType, new() =>
new RepeatingPrimitiveElementNode(values.Select(v => new T { ObjectValue = v }).ToList());
}

public record SinglePocoElementNode(Base Poco, PocoElementNode2? Parent, int? Index, string? Name)
: PocoElementNode2(Parent, Name ?? Poco.TypeName), IScopedNode, IFhirValueProvider, IResourceTypeSupplier, IAnnotated
{
public IEnumerable<PocoElementNode2> Children() =>
Poco.GetElementPairs()
.Select(ep =>
nodeFor(ep.Key, ep.Value)
);

public PocoElementNode2? Child(string name) => Poco.TryGetValue(name, out var result)
? nodeFor(name, result)
: null;

private PocoElementNode2 nodeFor(string name, object value) =>
value switch
{
PrimitiveType primitive => new SinglePrimitiveElementNode(primitive, name) { Parent = this },
Base b => new SinglePocoElementNode(b, this, null, name),
IEnumerable<PrimitiveType> primitiveList => new RepeatingPrimitiveElementNode(primitiveList.ToList(), name) { Parent = this },
IEnumerable<Base> list => new RepeatingPocoElementNode(list.ToList(), this, name),
_ => throw new InvalidOperationException("Unexpected element in child list")
};

private IEnumerable<SinglePocoElementNode> asList() => [this];

public override IEnumerator<SinglePocoElementNode> GetEnumerator() => asList().GetEnumerator();

string IShortPathGenerator.ShortPath => (Index, Parent) switch
{
// if we have an index, we have a parent.
({ } idx, { } parent) => $"{((IShortPathGenerator)parent).ShortPath}.{Name}[{idx}]",
// Note that we omit indices here.
(_, { } parent) => $"{((IShortPathGenerator)parent).ShortPath}.{Name}",
// if we have neither, we are the root. Note that we omit indices here.
_ => Name
};

Base IFhirValueProvider.FhirValue => Poco;

string? IResourceTypeSupplier.ResourceType => Poco is Resource
? ((ITypedElement)this).InstanceType
: null;

public IEnumerable<object> Annotations(Type type)
{
if (type == typeof(ITypedElement) || type == typeof(IShortPathGenerator) || type == typeof(IScopedNode))
return [this];
if (type == typeof(IFhirValueProvider))
return [this];
if (type == typeof(IResourceTypeSupplier))
return [this];
return Poco.Annotations(type);
}

#region ITypedElement

string ITypedElement.InstanceType =>
Poco switch
{
BackboneElement => "BackboneElement",
Element when Poco.TypeName.Contains('.') => "Element",
_ => Poco.TypeName
};

object? ITypedElement.Value => ValueInternal;

// needed for ITE
protected virtual object? ValueInternal => null;

string ITypedElement.Location => (Index, Parent) switch
{
// if we have an index, write it
({ } idx, { } parent) => $"{((ITypedElement)parent).Location}.{Name}[{idx}]",
// if we do not, write 0 as idx
(_, { } parent) => $"{((ITypedElement)parent).Location}.{Name}[0]",
// if we have neither, we are the root.
_ => Name
};

// needed for ITE
IElementDefinitionSummary? ITypedElement.Definition => null;

IEnumerable<ITypedElement> ITypedElement.Children(string? name) => (this as IScopedNode).Children(name);

#endregion

#region IScopedNode

IScopedNode? IScopedNode.Parent => Parent switch
{
RepeatingPocoElementNode rpen => rpen[Index!.Value],
SinglePocoElementNode spen => spen,
_ => null
};

IEnumerable<IScopedNode> IScopedNode.Children(string? name) => name is null
? Children().SelectMany(node => node)
: Child(name) ?? Enumerable.Empty<SinglePocoElementNode>();

[TemporarilyChanged] // we should investigate whether we want to even use this anymore. If we do, we should make this implementation explicit.
NodeType IScopedNode.Type => Poco switch
{
Bundle => NodeType.Bundle | NodeType.Resource,
PrimitiveType => NodeType.Primitive,
DomainResource => NodeType.DomainResource | NodeType.Resource,
Resource => NodeType.Resource,
ResourceReference or Canonical or CodeableReference => NodeType.Reference,
Quantity => NodeType.Quantity,
_ => 0
};

bool IScopedNode.TryResolveBundleEntry(string fullUrl, [NotNullWhen(true)] out IScopedNode? result)
{
result = Poco is Bundle
? this
.Child<RepeatingPocoElementNode>("entry")
?.FirstOrDefault<Bundle.EntryComponent>(entry =>
entry.FullUrl == fullUrl)
?.Child<SinglePocoElementNode>("resource")
: null;
return result is not null;
}

bool IScopedNode.TryResolveContainedEntry(string id, [NotNullWhen(true)] out IScopedNode? result)
{
result = Poco is DomainResource
? this
.Child<RepeatingPocoElementNode>("contained")
?.FirstOrDefault<Resource>(contained => $"#{contained.Id}" == id)
: null;
return result is not null;
}

#endregion
}

public record RepeatingPocoElementNode(IReadOnlyList<Base> Pocos, PocoElementNode2? Parent, string Name) : PocoElementNode2(Parent, Name)
{
public SinglePocoElementNode this[int index] => new(Pocos[index], Parent, index, Name);

public IEnumerable<SinglePocoElementNode> Where<T>(Func<T, bool> predicate) where T : Base =>
Pocos.OfType<T>().Where(predicate).Select((poco, index) => new SinglePocoElementNode(poco, Parent, index, Name));

public SinglePocoElementNode? FirstOrDefault<T>(Func<T, bool> predicate) where T : Base
{
for (int index = 0; index < Pocos.Count; index++)
{
if (Pocos[index] is T item && predicate(item))
return new SinglePocoElementNode(item, Parent, index, Name);
}

return null;
}

public override IEnumerator<SinglePocoElementNode> GetEnumerator() => Pocos.Select((poco, index) => new SinglePocoElementNode(poco, Parent, index, Name)).GetEnumerator();
}

public record SinglePrimitiveElementNode(PrimitiveType Primitive, string? Name = null) : SinglePocoElementNode(Primitive, null, null, Name)
{
protected override object? ValueInternal => Primitive.ToITypedElementValue();
}

internal record RepeatingPrimitiveElementNode(IReadOnlyList<PrimitiveType> Primitives, string? Name = null) : RepeatingPocoElementNode(Primitives, null, Name ?? "value")
{
public override IEnumerator<SinglePocoElementNode> GetEnumerator() =>
Primitives.Select((primitive, index) => new SinglePrimitiveElementNode(primitive, Name) { Index = index }).GetEnumerator();
}

public static class PocoElementNodeExtensions
{
public static T? Child<T>(this SinglePocoElementNode? node, string name) where T : PocoElementNode2 => node?.Child(name) as T;
}
8 changes: 7 additions & 1 deletion src/Hl7.Fhir.Base/ElementModel/TypedElementExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,16 @@ public static class TypedElementExtensions
/// <param name="modelInspector">The <see cref="ModelInspector"/> containing the POCO classes to be used for deserialization.</param>
/// <param name="rootName"></param>
/// <returns></returns>
[TemporarilyChanged]
public static ITypedElement ToTypedElement(this Base @base, ModelInspector modelInspector, string? rootName = null)
=> new PocoElementNode(modelInspector, @base, rootName: rootName);

/// <summary>
/// Converts a Poco to a new PocoElementNode.
/// </summary>
/// <param name="base">The Poco that should be converted to an <see cref="ITypedElement"/>.</param>
/// <param name="rootName"></param>
public static SinglePocoElementNode ToElementNode(this Base @base, string? rootName = null) => PocoElementNode2.Root(@base, rootName);

/// <summary>
/// Determines whether the specified ITypedElement is equal to the current ITypedElement. You can discard the order of the elements
/// by setting the <paramref name="ignoreOrder"/> to <c>true</c>.
Expand Down
7 changes: 7 additions & 0 deletions src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ public static bool Predicate(this CompiledExpression evaluator, IScopedNode inpu
return result is null || result.Value;
}

/// <inheritdoc cref="Predicate(Hl7.FhirPath.CompiledExpression,Hl7.Fhir.Model.IScopedNode,Hl7.FhirPath.EvaluationContext)"/>
public static bool Predicate(this CompiledExpression evaluator, Base input, EvaluationContext ctx)
{
var result = evaluator(input.ToElementNode(), ctx).BooleanEval();
return result is null || result.Value;
}

/// <summary>
/// Evaluates an expression and returns true for expression being evaluated as true, and false if the expression returns false or empty.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Hl7.Fhir.Base/FhirPath/ElementNavFhirExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public static bool HtmlChecks(this IScopedNode focus)
if (r is null)
return null;

var fhirValue = r.Annotation<IFhirValueProvider>();
var fhirValue = r.Annotation<IFhirValueProvider>() ?? r as IFhirValueProvider;
if (fhirValue != null)
{
return fhirValue.FhirValue;
Expand Down
2 changes: 1 addition & 1 deletion src/Hl7.Fhir.Base/FhirPath/Expressions/Closure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static Closure Root(IScopedNode root, EvaluationContext ctx = null)

newClosure.SetThis(input);
newClosure.SetThat(input);
newClosure.SetIndex([new Integer(0)]);
newClosure.SetIndex(PocoElementNode2.ForPrimitive<Integer>(1));
newClosure.SetOriginalContext(input);

if (newContext.Resource != null) newClosure.SetResource(new[] { newContext.Resource });
Expand Down
21 changes: 11 additions & 10 deletions src/Hl7.Fhir.Base/FhirPath/Expressions/SymbolTableInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -360,10 +360,10 @@ IEnumerable<IScopedNode> runForeach()

foreach (IScopedNode element in focus)
{
IEnumerable<IScopedNode> newFocus = [element];
IScopedNode[] newFocus = [element];
var newContext = ctx.Nest(newFocus);
newContext.SetThis(newFocus);
newContext.SetIndex([new Integer(index)]);
newContext.SetIndex(PocoElementNode2.ForPrimitive<Integer>(index));
index++;

if (lambda(newContext, InvokeeFactory.EmptyArgs).BooleanEval() == true)
Expand All @@ -388,7 +388,7 @@ IEnumerable<IScopedNode> runForeach()
IEnumerable<IScopedNode> newFocus = [element];
var newContext = ctx.Nest(newFocus);
newContext.SetThis(newFocus);
newContext.SetIndex([new Integer(index)]);
newContext.SetIndex(PocoElementNode2.ForPrimitive<Integer>(index));
index++;

var result = lambda(newContext, InvokeeFactory.EmptyArgs);
Expand Down Expand Up @@ -417,7 +417,7 @@ private static IEnumerable<IScopedNode> runRepeat(Closure ctx, IEnumerable<Invok
IEnumerable<IScopedNode> newFocus = [element];
var newContext = ctx.Nest(newFocus);
newContext.SetThis(newFocus);
newContext.SetIndex([new Integer(index)]);
newContext.SetIndex(PocoElementNode2.ForPrimitive<Integer>(index));
index++;

var candidates = lambda(newContext, InvokeeFactory.EmptyArgs);
Expand All @@ -443,15 +443,15 @@ private static IEnumerable<IScopedNode> runAll(Closure ctx, IEnumerable<Invokee>
IEnumerable<IScopedNode> newFocus = [element];
var newContext = ctx.Nest(newFocus);
newContext.SetThis(newFocus);
newContext.SetIndex([new Integer(index)]);
newContext.SetIndex(PocoElementNode2.ForPrimitive<Integer>(index));
index++;

var result = lambda(newContext, InvokeeFactory.EmptyArgs).BooleanEval();
if (result == null) return [];
if (result == false) return [new FhirBoolean(false)];
if (result == false) return PocoElementNode2.ForPrimitive<FhirBoolean>(false);
}

return [new FhirBoolean(true)];
return PocoElementNode2.ForPrimitive<FhirBoolean>(true);
}

private static IEnumerable<IScopedNode> runAny(Closure ctx, IEnumerable<Invokee> arguments)
Expand All @@ -465,14 +465,15 @@ private static IEnumerable<IScopedNode> runAny(Closure ctx, IEnumerable<Invokee>
IEnumerable<IScopedNode> newFocus = [element];
var newContext = ctx.Nest(newFocus);
newContext.SetThis(newFocus);
newContext.SetIndex([new Integer(index)]);
newContext.SetIndex(PocoElementNode2.ForPrimitive<Integer>(index));
index++;

var result = lambda(newContext, InvokeeFactory.EmptyArgs).BooleanEval();
if (result == true) return [new FhirBoolean(true)];
if (result == true) return PocoElementNode2.ForPrimitive<FhirBoolean>(true);
}

return [new FhirBoolean(false)];
//return PocoElementNode2.ForPrimitive<FhirBoolean>(false);
return PocoElementNode2.Root(new FhirBoolean(false));
}
}
}
Loading