Skip to content

Commit

Permalink
Merge branch 'develop-6.0' into 6.0/multiple-small-issues
Browse files Browse the repository at this point in the history
  • Loading branch information
mmsmits authored Dec 5, 2024
2 parents 5218744 + e90f43c commit c2387ed
Show file tree
Hide file tree
Showing 53 changed files with 540 additions and 373 deletions.
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) =>
primitives.Select(ForPrimitive);

public static IEnumerable<SinglePocoElementNode> FromList<T>(IEnumerable<object> values) where T : PrimitiveType, new() =>
values.Select(ForPrimitive<T>);
}

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;

IEnumerable<object> IAnnotated.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
}

internal 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

0 comments on commit c2387ed

Please sign in to comment.