Skip to content

Commit

Permalink
Merge pull request #3004 from FirelyTeam/spike/remove-iscopednode
Browse files Browse the repository at this point in the history
Refactor FP engine against PocoNode
  • Loading branch information
ewoutkramer authored Feb 25, 2025
2 parents 930fb98 + 2745a35 commit b02b2ec
Show file tree
Hide file tree
Showing 80 changed files with 3,050 additions and 2,317 deletions.
832 changes: 703 additions & 129 deletions src/Hl7.Fhir.Base/CompatibilitySuppressions.xml

Large diffs are not rendered by default.

123 changes: 0 additions & 123 deletions src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs

This file was deleted.

81 changes: 40 additions & 41 deletions src/Hl7.Fhir.Base/ElementModel/ISourceNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,51 @@

using System.Collections.Generic;

namespace Hl7.Fhir.ElementModel
namespace Hl7.Fhir.ElementModel;

/// <summary>
/// A node within a tree of FHIR data.
/// </summary>
/// <remarks>
/// <para>This interface is typically implemented by a parser for one of the low-level serialization formats for FHIR, i.e.
/// FHIR xml/json/rdf or v3 XML. The interface does not depend on the availability of FHIR metadata and definitions
/// (in contrast to <see cref="ITypedElement" />), so the names of the nodes will have their type suffixes (for choice types)
/// and all primitives values are represented as strings, instead of native objects.</para>
/// <para>Implementations of this interface that want to report errors while parsing should only do so on the
/// <see cref="Children(string)"/> function and <see cref="Text"/> getter.</para>
/// </remarks>
public interface ISourceNode
{
/// <summary>
/// A node within a tree of FHIR data.
/// Gets the name of the node, e.g. "active", "valueQuantity".
/// </summary>
/// <remarks>
/// <para>This interface is typically implemented by a parser for one of the low-level serialization formats for FHIR, i.e.
/// FHIR xml/json/rdf or v3 XML. The interface does not depend on the availability of FHIR metadata and definitions
/// (in contrast to <see cref="ITypedElement" />), so the names of the nodes will have their type suffixes (for choice types)
/// and all primitives values are represented as strings, instead of native objects.</para>
/// <para>Implementations of this interface that want to report errors while parsing should only do so on the
/// <see cref="Children(string)"/> function and <see cref="Text"/> getter.</para>
/// <remarks>Since the node has no type information, choice elements are represented as their
/// name on the wire, possibly including the type suffix for choice elements.
/// </remarks>
public interface ISourceNode
{
/// <summary>
/// Gets the name of the node, e.g. "active", "valueQuantity".
/// </summary>
/// <remarks>Since the node has no type information, choice elements are represented as their
/// name on the wire, possibly including the type suffix for choice elements.
/// </remarks>
string Name { get; }
string Name { get; }

/// <summary>
/// Gets the text of the primitive value of the node
/// </summary>
/// <value>Returns the raw textual value as represented in the serialization, or null if there is no value in this node.</value>
string Text { get; }
/// <summary>
/// Gets the text of the primitive value of the node
/// </summary>
/// <value>Returns the raw textual value as represented in the serialization, or null if there is no value in this node.</value>
string Text { get; }

/// <summary>
/// Gets the location of this node within the tree of data.
/// </summary>
/// <value>A string of dot-separated names representing the path to the node within the tree, including indices
/// to distinguish repeated occurences of an element.</value>
string Location { get; }
/// <summary>
/// Gets the location of this node within the tree of data.
/// </summary>
/// <value>A string of dot-separated names representing the path to the node within the tree, including indices
/// to distinguish repeated occurences of an element.</value>
string Location { get; }

/// <summary>
/// Enumerates the direct child nodes of the current node (if any).
/// </summary>
/// <param name="name">Optional. The name filter for the children. Can be omitted to not filter by name.</param>
/// <returns>The children of the node matching the given filter, or all children if no filter was specified.
/// If no children match the given filter, the function returns an empty enumerable.</returns>
/// <remarks>
/// <para>If the <paramref name="name"/>parameter ends in an asterix ('*'),
/// the function will return the children of which the name starts with the given name.</para>
/// <para>Repeating elements will always be returned consecutively.</para></remarks>
IEnumerable<ISourceNode> Children(string name = null);
}
/// <summary>
/// Enumerates the direct child nodes of the current node (if any).
/// </summary>
/// <param name="name">Optional. The name filter for the children. Can be omitted to not filter by name.</param>
/// <returns>The children of the node matching the given filter, or all children if no filter was specified.
/// If no children match the given filter, the function returns an empty enumerable.</returns>
/// <remarks>
/// <para>If the <paramref name="name"/>parameter ends in an asterix ('*'),
/// the function will return the children of which the name starts with the given name.</para>
/// <para>Repeating elements will always be returned consecutively.</para></remarks>
IEnumerable<ISourceNode> Children(string name = null);
}
4 changes: 2 additions & 2 deletions src/Hl7.Fhir.Base/ElementModel/NewPocoBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ private ClassMapping classMappingForElement(ITypedElement node, PropertyMapping?

// Otherwise, let's use the ITypedElement's instance type.
if (node.InstanceType is { } instanceType &&
inspector.FindClassMapping(instanceType) is { NativeType.IsAbstract: false } mapping)
inspector.FindClassMapping(instanceType) is { NativeType.IsAbstract: false } mapping && typeof(Base).IsAssignableFrom(mapping.NativeType))
return mapping;

// No useable concrete type in the property, nor in the instance type, so we need to create
Expand Down Expand Up @@ -183,7 +183,7 @@ private ClassMapping determineBestDynamicMappingForElement(ITypedElement node)
if (node.Value is not null || (node.InstanceType is { } it && char.IsLower(it[0])))
return determineBestPrimitiveMapping();

if (node.Annotation<IResourceTypeSupplier>() is not null)
if (node.Annotation<IResourceTypeSupplier>() is not null || node.Definition?.IsResource is true)
return getClassMapping(DYNAMIC_RESOURCE_TYPE_NAME);

return getClassMapping(DYNAMIC_DATATYPE_TYPE_NAME);
Expand Down
36 changes: 0 additions & 36 deletions src/Hl7.Fhir.Base/ElementModel/PocoBuilderExtensions.cs

This file was deleted.

96 changes: 96 additions & 0 deletions src/Hl7.Fhir.Base/ElementModel/PocoNode.Primitives.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using Hl7.Fhir.Model;
using Hl7.FhirPath;
using System;
using System.Collections.Generic;
using System.Linq;

#nullable enable

namespace Hl7.Fhir.ElementModel;

public partial record PocoNode
{
/// <summary>
/// Constructs a PocoNode from a PrimitiveType
/// </summary>
/// <returns></returns>
public static PocoNode ForPrimitive(PrimitiveType primitive) =>
new PrimitiveNode(primitive, null, null);


/// <summary>
/// Constructs a PocoNode from an object. Allowed objects are those that can be converted to a PrimitiveType, and are not yet PrimitiveTypes.
/// </summary>
/// <returns></returns>
public static PocoNode ForAnyPrimitive(object value)
{
return ForPrimitive(PrimitiveNode.InferFromValue(value));
}

/// <summary>
/// Constructs a PocoNode from a value and a type. The type must be a PrimitiveType.
/// </summary>
/// <param name="value"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static PocoNode ForPrimitive<T>(object value) where T : PrimitiveType, new() =>
new PrimitiveNode(new T { ObjectValue = value }, null, null);

/// <summary>
/// Constructs a PocoNode from a list of PrimitiveTypes
/// </summary>
/// <param name="primitives"></param>
/// <param name="name"></param>
/// <returns></returns>
public static IEnumerable<PocoNode> FromList(IEnumerable<PrimitiveType> primitives, string? name = null) =>
primitives.Select(ForPrimitive);

/// <summary>
/// Constructs multiple PocoNodes from a list of values and a type. The type must be a PrimitiveType.
/// </summary>
/// <param name="values"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static IEnumerable<PocoNode> FromList<T>(IEnumerable<object> values) where T : PrimitiveType, new() =>
values.Select(ForPrimitive<T>);

/// <summary>
/// Constructs multiple PocoNodes from a list of objects. Allowed objects are those that can be converted to a PrimitiveType, and are not yet PrimitiveTypes.
/// </summary>
/// <param name="values"></param>
/// <returns></returns>
public static IEnumerable<PocoNode> FromAnyList(IEnumerable<object> values) =>
values.Select(v => v as PocoNode ?? ForAnyPrimitive(v));
}

public record PrimitiveNode(PrimitiveType Primitive, PocoNodeOrList? ParentNode, int? Index, string? Name = null) : PocoNode(Primitive, ParentNode, Index, Name)
{
protected override object? ValueInternal => Primitive.ToITypedElementValue();
internal object? Value => ValueInternal;

internal static PrimitiveType InferFromValue(object value) => value switch
{
Types.Quantity qt => new FPQuantity(qt),
Types.DateTime dt => new FPDateTime(dt),
Types.Date d => new FPDate(d),
Types.Time t => new FPTime(t),
decimal dec => new FPDecimal(dec),
float f => new FPDecimal((decimal)f),
double d => new FPDecimal((decimal)d),
bool b => new FPBoolean(b),
int i => new FPInteger(i),
long l => new FPLong(l),
string s => new FPString(s),
_ => throw new ArgumentException("Cannot infer primitive type from value", nameof(value))
};

protected override string? TextInternal => Primitive.ToString();
}

internal record PrimitiveListNode(IReadOnlyList<PrimitiveType> Primitives, PocoNodeOrList? ParentNode, string? Name = null) : PocoListNode(Primitives, ParentNode, Name ?? "value")
{
public override IEnumerator<PocoNode> GetEnumerator() =>
Primitives.Select((primitive, index) => new PrimitiveNode(primitive, ParentNode, index, Name)).GetEnumerator();

internal IEnumerable<object?> Values => Primitives.Select(p => p.ObjectValue);
}
Loading

0 comments on commit b02b2ec

Please sign in to comment.