Skip to content

Commit 6731010

Browse files
authored
Merge pull request #2747 from FirelyTeam/feature/location-itypedelement
Fixed location property on TypedElementOnSourceNode.cs
2 parents e0c5f31 + 8717149 commit 6731010

File tree

6 files changed

+231
-195
lines changed

6 files changed

+231
-195
lines changed

src/Hl7.Fhir.Base/ElementModel/SourceNodeExtensions.cs

+8
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,14 @@ public static string GetResourceTypeIndicator(this ISourceNode node) =>
170170
/// <seealso cref="IAnnotated"/>
171171
public static IEnumerable<object> Annotations(this ISourceNode node, Type type) =>
172172
node is IAnnotated ann ? ann.Annotations(type) : Enumerable.Empty<object>();
173+
174+
/// <summary>
175+
/// Gets specific annotations from the list of annotations on the node.
176+
/// </summary>
177+
/// <returns>All of the annotations of the given type, or an empty list if none were found.</returns>
178+
/// <seealso cref="IAnnotated"/>
179+
public static IEnumerable<T> Annotations<T>(this ISourceNode node) =>
180+
(node is IAnnotated ann ? ann.Annotations<T>() : []);
173181

174182
/// <summary>
175183
/// Gets a specific annotation from the list of annotations on the node.

src/Hl7.Fhir.Base/ElementModel/TypedElementOnSourceNode.cs

+36-31
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@
1414
using System.Threading;
1515
using P = Hl7.Fhir.ElementModel.Types;
1616

17+
#nullable enable
18+
1719
namespace Hl7.Fhir.ElementModel
1820
{
1921
internal class TypedElementOnSourceNode : ITypedElement, IAnnotated, IExceptionSource, IShortPathGenerator
2022
{
2123
private const string XHTML_INSTANCETYPE = "xhtml";
2224
private const string XHTML_DIV_TAG_NAME = "div";
2325

24-
public TypedElementOnSourceNode(ISourceNode source, string type, IStructureDefinitionSummaryProvider provider, TypedElementSettings settings = null)
26+
public TypedElementOnSourceNode(ISourceNode source, string? type, IStructureDefinitionSummaryProvider provider, TypedElementSettings? settings = null)
2527
{
2628
if (source == null) throw Error.ArgumentNull(nameof(source));
2729

@@ -31,12 +33,13 @@ public TypedElementOnSourceNode(ISourceNode source, string type, IStructureDefin
3133
if (source is IExceptionSource ies && ies.ExceptionHandler == null)
3234
ies.ExceptionHandler = (o, a) => ExceptionHandler.NotifyOrThrow(o, a);
3335

36+
Location = source.Name;
3437
ShortPath = source.Name;
3538
_source = source;
3639
(InstanceType, Definition) = buildRootPosition(type);
3740
}
3841

39-
private (string instanceType, IElementDefinitionSummary definition) buildRootPosition(string type)
42+
private (string instanceType, IElementDefinitionSummary? definition) buildRootPosition(string? type)
4043
{
4144
var rootType = type ?? _source.GetResourceTypeIndicator();
4245
if (rootType == null)
@@ -45,7 +48,7 @@ public TypedElementOnSourceNode(ISourceNode source, string type, IStructureDefin
4548
throw Error.Format(nameof(type), $"Cannot determine the type of the root element at '{_source.Location}', " +
4649
$"please supply a type argument.");
4750
else
48-
return (rootType, null);
51+
return ("Base", null);
4952
}
5053

5154
var elementType = Provider.Provide(rootType);
@@ -66,7 +69,7 @@ public TypedElementOnSourceNode(ISourceNode source, string type, IStructureDefin
6669
}
6770

6871

69-
private TypedElementOnSourceNode(TypedElementOnSourceNode parent, ISourceNode source, IElementDefinitionSummary definition, string instanceType, string prettyPath)
72+
private TypedElementOnSourceNode(TypedElementOnSourceNode parent, ISourceNode source, IElementDefinitionSummary? definition, string instanceType, string prettyPath, string location)
7073
{
7174
_source = source;
7275
ShortPath = prettyPath;
@@ -75,11 +78,12 @@ private TypedElementOnSourceNode(TypedElementOnSourceNode parent, ISourceNode so
7578
Definition = definition;
7679
InstanceType = instanceType;
7780
_settings = parent._settings;
81+
Location = location;
7882
}
7983

80-
public ExceptionNotificationHandler ExceptionHandler { get; set; }
84+
public ExceptionNotificationHandler? ExceptionHandler { get; set; }
8185

82-
private void raiseTypeError(string message, object source, bool warning = false, string location = null)
86+
private void raiseTypeError(string message, object source, bool warning = false, string? location = null)
8387
{
8488
var exMessage = $"Type checking the data: {message}";
8589
if (!string.IsNullOrEmpty(location))
@@ -101,7 +105,7 @@ private void raiseTypeError(string message, object source, bool warning = false,
101105

102106
private readonly TypedElementSettings _settings;
103107

104-
public IElementDefinitionSummary Definition { get; private set; }
108+
public IElementDefinitionSummary? Definition { get; private set; }
105109

106110
public string Name => Definition?.ElementName ?? _source.Name;
107111

@@ -120,7 +124,7 @@ private void raiseTypeError(string message, object source, bool warning = false,
120124
// R3 and R4, these value (and url and id elements by the way) will indicate which type
121125
// of system types there are, implicitly specifying the mapping between primitive
122126
// FHIR types and system types.
123-
private static Type tryMapFhirPrimitiveTypeToSystemType(string fhirType)
127+
private static Type? tryMapFhirPrimitiveTypeToSystemType(string fhirType)
124128
{
125129
switch (fhirType)
126130
{
@@ -158,7 +162,7 @@ private static Type tryMapFhirPrimitiveTypeToSystemType(string fhirType)
158162
}
159163
}
160164

161-
private object valueFactory()
165+
private object? valueFactory()
162166
{
163167
string sourceText = _source.Text;
164168

@@ -209,7 +213,7 @@ private object valueFactory()
209213
if (P.Any.TryParse(sourceText, typeof(P.DateTime), out var dateTimeVal))
210214
{
211215
// TruncateToDate converts 1991-02-03T11:22:33Z to 1991-02-03+00:00 which is not a valid date!
212-
var date = (dateTimeVal as P.DateTime).TruncateToDate();
216+
var date = (dateTimeVal as P.DateTime)!.TruncateToDate();
213217
// so we cut off timezone by converting it to timeoffset and cast back to date.
214218
return P.Date.FromDateTimeOffset(date.ToDateTimeOffset(0, 0, 0, TimeSpan.Zero));
215219
}
@@ -220,13 +224,13 @@ private object valueFactory()
220224
}
221225
}
222226

223-
private object _value;
227+
private object? _value;
224228
private bool _valueInitialized = false;
225229
private static object _initializationLock = new();
226230

227-
public object Value => LazyInitializer.EnsureInitialized(ref _value, ref _valueInitialized, ref _initializationLock, valueFactory);
231+
public object Value => LazyInitializer.EnsureInitialized(ref _value, ref _valueInitialized, ref _initializationLock, valueFactory)!;
228232

229-
private string deriveInstanceType(ISourceNode current, IElementDefinitionSummary info)
233+
private string? deriveInstanceType(ISourceNode current, IElementDefinitionSummary info)
230234
{
231235
var resourceTypeIndicator = current.GetResourceTypeIndicator();
232236

@@ -338,7 +342,7 @@ private string typeFromLogicalModelCanonical(ITypeSerializationInfo info)
338342
return pos > -1 ? type.Substring(pos + 1) : type;
339343
}
340344

341-
private bool tryGetBySuffixedName(Dictionary<string, IElementDefinitionSummary> dis, string name, out IElementDefinitionSummary info)
345+
private bool tryGetBySuffixedName(Dictionary<string, IElementDefinitionSummary> dis, string name, out IElementDefinitionSummary? info)
342346
{
343347
// Simplest case, one on one match between name and element name
344348
if (dis.TryGetValue(name, out info))
@@ -361,7 +365,7 @@ private bool tryGetBySuffixedName(Dictionary<string, IElementDefinitionSummary>
361365
}
362366
}
363367

364-
private IEnumerable<TypedElementOnSourceNode> enumerateElements(Dictionary<string, IElementDefinitionSummary> dis, ISourceNode parent, string name)
368+
private IEnumerable<TypedElementOnSourceNode> enumerateElements(Dictionary<string, IElementDefinitionSummary> dis, ISourceNode parent, string? name)
365369
{
366370
IEnumerable<ISourceNode> childSet;
367371

@@ -372,17 +376,17 @@ private IEnumerable<TypedElementOnSourceNode> enumerateElements(Dictionary<strin
372376
{
373377
var hit = dis.TryGetValue(name, out var info);
374378
childSet = hit
375-
? (info.IsChoiceElement ? parent.Children(name + "*") : parent.Children(name))
379+
? (info!.IsChoiceElement ? parent.Children(name + "*") : parent.Children(name))
376380
: Enumerable.Empty<ISourceNode>();
377381
}
378382

379-
string lastName = null;
383+
string? lastName = null;
380384
int _nameIndex = 0;
381385

382386
foreach (var scan in childSet)
383387
{
384388
var hit = tryGetBySuffixedName(dis, scan.Name, out var info);
385-
string instanceType = info == null ? null :
389+
string? instanceType = info == null ? null :
386390
deriveInstanceType(scan, info);
387391

388392
// If we have definitions for the children, but we didn't find definitions for this
@@ -406,28 +410,31 @@ private IEnumerable<TypedElementOnSourceNode> enumerateElements(Dictionary<strin
406410
}
407411

408412
var prettyPath =
409-
hit && !info.IsCollection ? $"{ShortPath}.{info.ElementName}" : $"{ShortPath}.{scan.Name}[{_nameIndex}]";
413+
hit && !info!.IsCollection ? $"{ShortPath}.{info.ElementName}" : $"{ShortPath}.{scan.Name}[{_nameIndex}]";
414+
415+
var location =
416+
hit ? $"{Location}.{info!.ElementName}[{_nameIndex}]" : $"{Location}.{scan.Name}[{_nameIndex}]";
410417

411418
// Special condition for ccda.
412419
// If we encounter a xhtml node in a ccda document we will flatten all childnodes
413420
// and use their content to build up the xml.
414421
// The xml will be put in this node and children will be ignored.
415-
if (instanceType == XHTML_INSTANCETYPE && info.Representation == XmlRepresentation.CdaText)
422+
if (instanceType == XHTML_INSTANCETYPE && info!.Representation == XmlRepresentation.CdaText)
416423
{
417424
#pragma warning disable CS0618 // Type or member is obsolete
418425
var xmls = scan.Children().Select(c => c.Annotation<ICdaInfoSupplier>()?.XHtmlText);
419426
#pragma warning restore CS0618 // Type or member is obsolete
420427

421428
var source = SourceNode.Valued(scan.Name, string.Join(string.Empty, xmls));
422-
yield return new TypedElementOnSourceNode(this, source, info, instanceType, prettyPath);
429+
yield return new TypedElementOnSourceNode(this, source, info, instanceType, prettyPath, location);
423430
continue;
424431
}
425432

426-
yield return new TypedElementOnSourceNode(this, scan, info, instanceType, prettyPath);
433+
yield return new TypedElementOnSourceNode(this, scan, info, instanceType!, prettyPath, location);
427434
}
428435
}
429436

430-
public IEnumerable<ITypedElement> Children(string name = null)
437+
public IEnumerable<ITypedElement> Children(string? name = null)
431438
{
432439
// If we have an xhtml typed node and there is not a div tag around the content
433440
// then we will not enumerate through the children of this node, since there will be no types
@@ -461,13 +468,13 @@ public IEnumerable<ITypedElement> Children(string name = null)
461468
private IEnumerable<ITypedElement> runAdditionalRules(IEnumerable<ITypedElement> children)
462469
{
463470
#pragma warning disable 612, 618
464-
var additionalRules = _source.Annotations(typeof(AdditionalStructuralRule));
471+
var additionalRules = _source.Annotations<AdditionalStructuralRule>().ToArray();
465472
var stateBag = new Dictionary<AdditionalStructuralRule, object>();
466473
foreach (var child in children)
467474
{
468-
foreach (var rule in additionalRules.Cast<AdditionalStructuralRule>())
475+
foreach (var rule in additionalRules)
469476
{
470-
stateBag.TryGetValue(rule, out object state);
477+
stateBag.TryGetValue(rule, out object? state);
471478
state = rule(child, this, state);
472479
if (state != null) stateBag[rule] = state;
473480
}
@@ -477,25 +484,23 @@ private IEnumerable<ITypedElement> runAdditionalRules(IEnumerable<ITypedElement>
477484
#pragma warning restore 612, 618
478485
}
479486

480-
public string Location => _source.Location;
487+
public string Location { get; init; }
481488

482489
public string ShortPath { get; private set; }
483490

484491
public override string ToString() =>
485-
$"{(InstanceType != null ? ($"[{InstanceType}] ") : "")}{_source}";
492+
$"{(($"[{InstanceType}] "))}{_source}";
486493

487494
public IEnumerable<object> Annotations(Type type)
488495
{
489-
#pragma warning disable IDE0046 // Convert to conditional expression
490496
if (type == typeof(TypedElementOnSourceNode) || type == typeof(ITypedElement) || type == typeof(IShortPathGenerator))
491-
#pragma warning restore IDE0046 // Convert to conditional expression
492497
return new[] { this };
493498
else
494499
return _source.Annotations(type);
495500
}
496501
}
497502

498503
[Obsolete("This class is used for internal purposes and is subject to change without notice. Don't use.")]
499-
public delegate object AdditionalStructuralRule(ITypedElement node, IExceptionSource ies, object state);
504+
public delegate object? AdditionalStructuralRule(ITypedElement node, IExceptionSource ies, object? state);
500505
}
501506

src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ static bool CCDATypeNameMapper(string typeName, out string canonical)
267267
Assert.AreEqual(1, assertXHtml.Count());
268268
Assert.AreEqual("text", assertXHtml.First().Name);
269269
Assert.AreEqual("xhtml", assertXHtml.First().InstanceType);
270-
Assert.AreEqual("text", assertXHtml.First().Location);
270+
Assert.AreEqual("Section.text[0]", assertXHtml.First().Location);
271271
Assert.IsNotNull(assertXHtml.First().Value);
272272

273273

src/Hl7.Fhir.ElementModel.Shared.Tests/TypedElementOnSourceNodeTests.cs

+26-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
using System;
1+
using Hl7.Fhir.Model;
2+
using System;
23
using System.Linq;
3-
using System.Threading.Tasks;
44
using Hl7.Fhir.Serialization;
55
using Hl7.Fhir.Specification;
66
using Microsoft.VisualStudio.TestTools.UnitTesting;
7+
using System.Collections.Generic;
8+
using Task = System.Threading.Tasks.Task;
79

810
namespace Hl7.Fhir.ElementModel.Tests
911
{
@@ -22,5 +24,27 @@ public async Task TestExceptionComplexTypeValue()
2224

2325
var _ = typedBundle.Children("entry").First().Value;
2426
}
27+
28+
private SourceNode testPatient => SourceNode.Node("Patient",
29+
SourceNode.Resource("contained", "Observation", SourceNode.Valued("valueBoolean", "true")),
30+
SourceNode.Valued("active", "true",
31+
SourceNode.Valued("id", "myId2"),
32+
SourceNode.Node("extension",
33+
SourceNode.Valued("url", "http://example.org/ext"),
34+
SourceNode.Valued("valueString", "world!"))));
35+
36+
private TypedElementOnSourceNode getTestPatient => (TypedElementOnSourceNode)testPatient.ToTypedElement(ModelInfo.ModelInspector, "Patient");
37+
38+
[TestMethod]
39+
public void KnowsPath()
40+
{
41+
var tp = getTestPatient;
42+
Assert.AreEqual("Patient", getTestPatient.Location);
43+
Assert.AreEqual("Patient.contained[0].value[0]", getTestPatient.Children("contained").First().Children("value").First().Location);
44+
Assert.AreEqual("Patient.active[0]", getTestPatient.Children("active").First().Location);
45+
Assert.AreEqual("Patient.active[0].id[0]", getTestPatient.Children("active").First().Children("id").First().Location);
46+
Assert.AreEqual("Patient.active[0].extension[0].url[0]", getTestPatient.Children("active").First().Children("extension").First().Children("url").First().Location);
47+
Assert.AreEqual("Patient.active[0].extension[0].value[0]", getTestPatient.Children("active").First().Children("extension").First().Children("value").First().Location);
48+
}
2549
}
2650
}

src/Hl7.Fhir.Serialization.Shared.Tests/RoundtripAllSerializers.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ public string RoundTripXml(string original) =>
2828
engine.SerializeToXml(
2929
engine.DeserializeFromJson(
3030
engine.SerializeToJson(
31-
engine.DeserializeFromXml(original))));
31+
engine.DeserializeFromXml(original)!))!);
3232
public string RoundTripJson(string original) =>
3333
engine.SerializeToJson(
3434
engine.DeserializeFromXml(
3535
engine.SerializeToXml(
36-
engine.DeserializeFromJson(original))));
36+
engine.DeserializeFromJson(original)!))!);
3737
}
3838

3939
internal class TypedElementBasedRoundtripper(IStructureDefinitionSummaryProvider provider) : IRoundTripper
@@ -249,7 +249,7 @@ public void TestMatchAndExactly(ZipArchiveEntry entry)
249249
? NEW_POCO_ENGINE.DeserializeFromXml(input)
250250
: NEW_POCO_ENGINE.DeserializeFromJson(input);
251251

252-
var r2 = (Resource)resource.DeepCopy();
252+
var r2 = (Resource)resource!.DeepCopy();
253253
Assert.IsTrue(resource.Matches(r2),
254254
"Serialization of " + name + " did not match output - Matches test");
255255
Assert.IsTrue(resource.IsExactly(r2),

0 commit comments

Comments
 (0)