From 7cfde367907f555e09fd9d975a74b50ed153f15b Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Wed, 20 Apr 2022 14:36:42 +0200 Subject: [PATCH 01/12] added test to reproduce stack overflow exception --- .../Hl7.Fhir.Specification.Tests.csproj | 40 + .../absolute-contentReference-stu3.xml | 3723 +++++++++++++++++ .../Validation/BasicValidationTests.cs | 40 + 3 files changed, 3803 insertions(+) create mode 100644 src/Hl7.Fhir.Specification.Tests/TestData/validation/absolute-contentReference-stu3.xml diff --git a/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj b/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj index 8db23528ce..f2782d1c1e 100644 --- a/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj +++ b/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj @@ -31,6 +31,46 @@ PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Hl7.Fhir.Specification.Tests/TestData/validation/absolute-contentReference-stu3.xml b/src/Hl7.Fhir.Specification.Tests/TestData/validation/absolute-contentReference-stu3.xml new file mode 100644 index 0000000000..ddb72410b0 --- /dev/null +++ b/src/Hl7.Fhir.Specification.Tests/TestData/validation/absolute-contentReference-stu3.xml @@ -0,0 +1,3723 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs b/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs index d1172ee1d3..bad4212a42 100644 --- a/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs @@ -1270,6 +1270,46 @@ public void ValidateWithTargetProfileAndChildDefinitions() } + [Fact] + public void ValidateAbsoluteContentReferences() + { + //prepare + var resolver = new MultiResolver( + new DirectorySource(@"TestData\validation"), + ZipSource.CreateValidationSource()); + + var validator = new Validator(new ValidationSettings() { ResourceResolver = resolver, GenerateSnapshot = false }); + + var questionnaire = new Questionnaire() + { + Meta = new Meta() + { + Profile = new string[] { "https://firely-sdk.org/fhir/StructureDefinition/AbsoluteContentReference" } + }, + Status = PublicationStatus.Active, + Item = new List + { + new Questionnaire.ItemComponent() + { + LinkId = "1", + Type = Questionnaire.QuestionnaireItemType.Boolean, + Item = new List + { + new Questionnaire.ItemComponent() + { + LinkId = "1", + Type = Questionnaire.QuestionnaireItemType.String + } + } + } + } + }; + + var outcome = validator.Validate(questionnaire); + Assert.True(outcome.Success); + } + + private class ClearSnapshotResolver : IResourceResolver { private readonly IResourceResolver _resolver; From 57ec84be376fdf3976fe133e1d354cc6206bbeaa Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Wed, 20 Apr 2022 16:10:30 +0200 Subject: [PATCH 02/12] Initial fix - needs testing --- .../Snapshot/SnapshotGeneratorTest.cs | 40 ++++++----- .../Navigation/ElementDefinitionNavigator.cs | 41 +++++------ .../Navigation/ProfileReference.cs | 39 +++++++---- .../Navigation/StructureDefinitionWalker.cs | 10 ++- .../Specification/Snapshot/ElementMatcher.cs | 46 ++++++------ .../Snapshot/SnapshotGenerator.cs | 70 +++++++------------ .../Source/ResourceResolverExtensions.cs | 11 +-- .../StructureDefinitionSummaryProvider.cs | 33 ++++----- .../ElementDefinitionNavigatorExtensions.cs | 34 +++++++++ .../Validation/Validator.cs | 38 +++++----- 10 files changed, 185 insertions(+), 177 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index 9671f3c2d1..80992316cf 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -45,7 +45,7 @@ public class SnapshotGeneratorTest2 private ZipSource _zipSource; private CachedResolver _testResolver; private TimingSource _source; - private readonly SnapshotGeneratorSettings _settings = new SnapshotGeneratorSettings() + private readonly SnapshotGeneratorSettings _settings = new() { // Throw on unresolved profile references; must include in TestData folder GenerateSnapshotForExternalProfiles = true, @@ -69,7 +69,7 @@ public void Setup() _testResolver = new CachedResolver(new MultiResolver(_zipSource, _source)); } - private StructureDefinition CreateStructureDefinition(string url, params ElementDefinition[] elements) + private StructureDefinition createStructureDefinition(string url, params ElementDefinition[] elements) { return new StructureDefinition { @@ -156,7 +156,7 @@ public async T.Task OverriddenNestedStructureDefinitionLists() var code = "someCode"; var discriminatorPath = "system"; - var baseSD = CreateStructureDefinition(baseCanonical, + var baseSD = createStructureDefinition(baseCanonical, new ElementDefinition { Path = "Practitioner.identifier", @@ -184,7 +184,7 @@ public async T.Task OverriddenNestedStructureDefinitionLists() } }); - var derivedSD = CreateStructureDefinition("http://yourdomain.org/fhir/StructureDefinition/Derived", + var derivedSD = createStructureDefinition("http://yourdomain.org/fhir/StructureDefinition/Derived", new ElementDefinition { Path = "Practitioner.identifier", @@ -1611,7 +1611,7 @@ private void dumpIssues(List issues) [Conditional("DEBUG")] private void dumpIssue(OperationOutcome.IssueComponent issue, int index) { - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); sb.AppendFormat("* Issue #{0}: Severity = '{1}' Code = '{2}'", index, issue.Severity, issue.Code); if (issue.Details != null) { @@ -2131,7 +2131,7 @@ public async T.Task TestBaseAnnotations_ExtensionDefinition() // [WMR 20170714] NEW // Annotated Base Element for backbone elements is not included in base structuredefinition ? - private static StructureDefinition MyTestObservation => new StructureDefinition() + private static StructureDefinition MyTestObservation => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -2922,7 +2922,7 @@ public async T.Task FindComplexTestExtensions() } // Ewout: type slices cannot contain renamed elements! - private static StructureDefinition ObservationTypeSliceProfile => new StructureDefinition() + private static StructureDefinition ObservationTypeSliceProfile => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -3070,7 +3070,7 @@ public async T.Task TestUnresolvedBaseProfile() assertIssue(outcome.Issue[0], Issue.UNAVAILABLE_REFERENCED_PROFILE, profile.BaseDefinition); } - private static StructureDefinition ObservationTypeResliceProfile => new StructureDefinition() + private static StructureDefinition ObservationTypeResliceProfile => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ObservationTypeSliceProfile.Url, @@ -3151,7 +3151,7 @@ public async T.Task TestTypeReslicing() } // Choice type constraint, with element renaming - private static StructureDefinition ObservationTypeConstraintProfile => new StructureDefinition() + private static StructureDefinition ObservationTypeConstraintProfile => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -3246,7 +3246,7 @@ public async T.Task TestInvalidChoiceTypeConstraints() assertIssue(outcome.Issue[0], SnapshotGenerator.PROFILE_ELEMENTDEF_INVALID_CHOICE_CONSTRAINT); } - private static StructureDefinition ClosedExtensionSliceObservationProfile => new StructureDefinition() + private static StructureDefinition ClosedExtensionSliceObservationProfile => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -6665,7 +6665,7 @@ public async T.Task TestAuPatientDerived2() // [WMR 20180410] Add unit tests for content references - public StructureDefinition QuestionnaireWithNestedItems = new StructureDefinition() + public StructureDefinition QuestionnaireWithNestedItems = new() { Type = FHIRAllTypes.Questionnaire.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Questionnaire), @@ -7179,7 +7179,7 @@ public async T.Task TestExtensionUrlFixedValueSimple() [TestMethod] public async T.Task TestExtensionUrlFixedValueComplex() { - StructureDefinition ComplexTestExtension = new StructureDefinition() + StructureDefinition ComplexTestExtension = new() { Type = FHIRAllTypes.Extension.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Extension), @@ -7418,14 +7418,15 @@ await act [TestMethod] public async T.Task SnapshotSucceedsWithExtendedVariantElementDef() { - var structureDef = new StructureDefinition(); - structureDef.BaseDefinition = "http://hl7.org/fhir/StructureDefinition/Observation"; - structureDef.Type = "Observation"; - structureDef.Url = "http://some.canonical"; - - structureDef.Differential = new StructureDefinition.DifferentialComponent + var structureDef = new StructureDefinition { - Element = new System.Collections.Generic.List{ + BaseDefinition = "http://hl7.org/fhir/StructureDefinition/Observation", + Type = "Observation", + Url = "http://some.canonical", + + Differential = new StructureDefinition.DifferentialComponent + { + Element = new System.Collections.Generic.List{ new ElementDefinition { ElementId = "Observation.value[x].extension", @@ -7458,6 +7459,7 @@ public async T.Task SnapshotSucceedsWithExtendedVariantElementDef() } } } + } }; diff --git a/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs b/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs index eef92e5872..cb8e19576b 100644 --- a/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs @@ -6,13 +6,13 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ +using Hl7.Fhir.Model; +using Hl7.Fhir.Utility; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; -using Hl7.Fhir.Model; -using System.Diagnostics; -using Hl7.Fhir.Utility; namespace Hl7.Fhir.Specification.Navigation { @@ -282,17 +282,17 @@ public bool JumpToNameReference(string nameReference) { if (Count == 0) return false; + var profileRef = ProfileReference.Parse(nameReference); + + // Namereferences should normally look like '#Questionnaire.item', but in snapshots for profiles, these can also + // be absolute urls pointing to an external (core) StructureDefinition, but we cannot handle those here as + // that should be dealt with by the caller. + if (profileRef.IsAbsolute && profileRef.CanonicalUrl != StructureDefinition.Url) + throw new NotSupportedException("Can only jump to local nameReferences, not " + nameReference); + for (int pos = 0; pos < Count; pos++) { - if (nameReference.StartsWith("#")) - { - if (Elements[pos].ElementId == nameReference.TrimStart('#')) - { - OrdinalPosition = pos; - return true; - } - } - else if (Elements[pos].ContentReference == nameReference) + if (Elements[pos].ElementId == "#" + profileRef.ElementName) { OrdinalPosition = pos; return true; @@ -309,17 +309,9 @@ public bool JumpToNameReference(string nameReference) // //---------------------------------- - public Bookmark Bookmark() => new Bookmark(Current, OrdinalPosition); + public Bookmark Bookmark() => new(Current, OrdinalPosition); - public bool IsAtBookmark(Bookmark bookmark) - { - var elem = bookmark.data as ElementDefinition; - if (elem == null) - { - return OrdinalPosition == null; - } - return object.ReferenceEquals(Current, elem); - } + public bool IsAtBookmark(Bookmark bookmark) => bookmark.data is ElementDefinition elem ? ReferenceEquals(Current, elem) : OrdinalPosition == null; public bool ReturnToBookmark(Bookmark bookmark) { @@ -329,8 +321,7 @@ public bool ReturnToBookmark(Bookmark bookmark) return true; } - var elem = bookmark.data as ElementDefinition; - if (elem == null) { return false; } + if (bookmark.data is not ElementDefinition elem) { return false; } // If the bookmark has an index, then try to use it var index = bookmark.index.GetValueOrDefault(-1); @@ -535,7 +526,7 @@ public List ToListOfElements() // public override string ToString() [DebuggerBrowsable(DebuggerBrowsableState.Never)] - string DebuggerDisplay + private string DebuggerDisplay { get { diff --git a/src/Hl7.Fhir.Specification/Specification/Navigation/ProfileReference.cs b/src/Hl7.Fhir.Specification/Specification/Navigation/ProfileReference.cs index 94c68fdf2e..6cd849566e 100644 --- a/src/Hl7.Fhir.Specification/Specification/Navigation/ProfileReference.cs +++ b/src/Hl7.Fhir.Specification/Specification/Navigation/ProfileReference.cs @@ -6,6 +6,8 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ +#nullable enable + using System; namespace Hl7.Fhir.Specification.Navigation @@ -13,37 +15,46 @@ namespace Hl7.Fhir.Specification.Navigation // [WMR 20160802] NEW /// Represents a reference to an element type profile. - /// Useful to parse complex profile references of the form "canonicalUrl#elementName". - internal struct ProfileReference + /// Useful to parse complex profile references of the form "canonicalUrl#Type.elementName". + internal class ProfileReference { - ProfileReference(string url) + private ProfileReference(string url) { - if (url == null) { throw new ArgumentNullException("url"); } - var pos = url.IndexOf('#'); - if (pos > 0 && pos < url.Length) + if (url == null) { throw new ArgumentNullException(nameof(url)); } + + var parts = url.Split('#'); + + if (parts.Length == 1) { - CanonicalUrl = url.Substring(0, pos); - ElementName = url.Substring(pos + 1); + // Just the canonical, no '#' present + CanonicalUrl = parts[0]; + ElementName = null; } else { - CanonicalUrl = url; - ElementName = null; + // There's a '#', so both or just the element are present + CanonicalUrl = parts[0].Length > 0 ? parts[0] : null; + ElementName = parts[1].Length > 0 ? parts[1] : null; } } /// Initialize a new instance from the specified url. /// A resource reference to a profile. /// A new structure. - public static ProfileReference Parse(string url) => new ProfileReference(url); + public static ProfileReference Parse(string url) => new(url); /// Returns the canonical url of the profile. - public string CanonicalUrl { get; } + public string? CanonicalUrl { get; } /// Returns an optional profile element name, if included in the reference. - public string ElementName { get; } + public string? ElementName { get; } /// Returns true if the profile reference includes an element name, false otherwise. - public bool IsComplex => ElementName != null; + public bool IsComplex => ElementName is not null; + + /// + /// Returns true of the profile reference includes a canonical url. + /// + public bool IsAbsolute => CanonicalUrl is not null; } } diff --git a/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs b/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs index a86df395ac..c33616dda2 100644 --- a/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs +++ b/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs @@ -153,12 +153,10 @@ public IEnumerable Expand() return new[] { this }; else if (Current.Current.ContentReference != null) { - var name = Current.Current.ContentReference; - var reference = Current.ShallowCopy(); + if (!Current.TryFollowContentReference(s => Resolver.FindStructureDefinition(s), out var reference)) + throw new StructureDefinitionWalkerException($"The contentReference '{reference}' cannot be resolved."); - if (!reference.JumpToNameReference(name)) - throw new StructureDefinitionWalkerException($"Found a namereference '{name}' that cannot be resolved at '{Current.CanonicalPath()}'."); - return new[] { new StructureDefinitionWalker(reference, _resolver) }; + return new[] { new StructureDefinitionWalker(reference!, _resolver) }; } else if (Current.Current.Type.Count >= 1) { @@ -191,7 +189,7 @@ public IEnumerable OfType(string type) // types into their definitions: // 1) The root node of an SD for a type or constraint on the type => derive the base type // 2) At a non-root BackboneElement or Element => use the TypeRef.Type (After the expand, this can be only 1) - string? typeCanonical(ElementDefinitionNavigator nav) => + static string? typeCanonical(ElementDefinitionNavigator nav) => nav.Current.IsRootElement() ? nav.StructureDefinition.Type : nav.Current.Type.FirstOrDefault()?.Code; diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs index 368409cd4d..42f4b19027 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs @@ -82,7 +82,7 @@ public class MatchInfo public OperationOutcome.IssueComponent Issue; [DebuggerBrowsable(DebuggerBrowsableState.Never)] - string DebuggerDisplay => $"B:{BaseBookmark.DebuggerDisplay} <-- {Action} --> D:{DiffBookmark.DebuggerDisplay}"; + private string DebuggerDisplay => $"B:{BaseBookmark.DebuggerDisplay} <-- {Action} --> D:{DiffBookmark.DebuggerDisplay}"; } /// Match the children of the current element in diffNav to the children of the current element in snapNav. @@ -144,7 +144,7 @@ public static List Match(ElementDefinitionNavigator snapNav, ElementD /// Move snapNav to matching base element for diffNav, if it exists. Otherwise diffNav introduces a new element. /// true if snapNav is positioned on macthing base element, or false if diffNav introduces a new element. - static bool matchBase(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, List choiceNames) + private static bool matchBase(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, List choiceNames) { // First, match directly -> try to find the child in base with the same name as the path in the diff if (StringComparer.Ordinal.Equals(snapNav.PathName, diffNav.PathName) || snapNav.MoveToNext(diffNav.PathName)) @@ -182,7 +182,7 @@ static bool matchBase(ElementDefinitionNavigator snapNav, ElementDefinitionNavig /// /// /// - static List constructMatch(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav) + private static List constructMatch(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav) { // [WMR 20160802] snapNav and diffNav point to matching elements // Determine the associated action (Add, Merge, Slice) @@ -204,7 +204,7 @@ static List constructMatch(ElementDefinitionNavigator snapNav, Elemen if (isChoice) { - if (!TypeIsSubSetOf(diffNav, snapNav)) + if (!typeIsSubSetOf(diffNav, snapNav)) { // diff.Types is not a subset of snap.Types, which is not allowed throw Error.InvalidOperation($"Internal error in snapshot generator ({nameof(ElementMatcher)}.{nameof(constructMatch)}): choice type of diff does not occur in snap, snap = '{snapNav.Path}', diff = '{diffNav.Path}'."); @@ -269,7 +269,7 @@ static List constructMatch(ElementDefinitionNavigator snapNav, Elemen return result; } - private static bool TypeIsSubSetOf(ElementDefinitionNavigator diffNav, ElementDefinitionNavigator snapNav) + private static bool typeIsSubSetOf(ElementDefinitionNavigator diffNav, ElementDefinitionNavigator snapNav) => !diffNav.Current.Type.Except(snapNav.Current.Type, new TypeRefEqualityComparer()).Any(); private class TypeRefEqualityComparer : IEqualityComparer @@ -292,7 +292,7 @@ public int GetHashCode(ElementDefinition.TypeRefComponent obj) } // [WMR 20160902] Represents a new element definition with no matching base element (for core resource & datatype definitions) - static MatchInfo constructNew(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav) + private static MatchInfo constructNew(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav) { // Called by Match when the current diffNav does not match any following sibling of snapNav (base) // This happens when merging a core definition (e.g. Patient) with a base type (e.g. Resource) @@ -340,7 +340,7 @@ static MatchInfo constructNew(ElementDefinitionNavigator snapNav, ElementDefinit // However for sliced elements, we need to initialize the slice entry and all following named slices from the same base element. // Therefore we first clone the original, unmerged base element and it's children (recursively). // Now each slice match return a reference to the associated original base element, unaffected by further processing. - static ElementDefinitionNavigator initSliceBase(ElementDefinitionNavigator snapNav) + private static ElementDefinitionNavigator initSliceBase(ElementDefinitionNavigator snapNav) { var sliceBase = snapNav.CloneSubtree(); @@ -354,7 +354,7 @@ static ElementDefinitionNavigator initSliceBase(ElementDefinitionNavigator snapN return sliceBase; } - static List constructSliceMatch(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, ElementDefinitionNavigator sliceBase = null) + private static List constructSliceMatch(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, ElementDefinitionNavigator sliceBase = null) { var result = new List(); @@ -540,9 +540,9 @@ private static ElementDefinitionNavigator returnToOriginalSliceBase(ElementDefin } /// Returns true if the element has type Extension and also specifies a custom type profile. - static bool isExtensionSlice(ElementDefinition element) => isExtensionSlice(element.Type.FirstOrDefault()); + private static bool isExtensionSlice(ElementDefinition element) => isExtensionSlice(element.Type.FirstOrDefault()); - static bool isExtensionSlice(ElementDefinition.TypeRefComponent type) + private static bool isExtensionSlice(ElementDefinition.TypeRefComponent type) => type != null && type.Code == FHIRAllTypes.Extension.GetLiteral() && type.Profile != null; @@ -550,7 +550,7 @@ static bool isExtensionSlice(ElementDefinition.TypeRefComponent type) // Match current snapshot and differential slice elements // Returns an initialized MatchInfo with action = Merge | Add // defaultBase represents the base element for newly introduced slices - static void matchSlice(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, + private static void matchSlice(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, List discriminators, MatchInfo match) { Debug.Assert(match != null); // Caller should initialize match @@ -607,7 +607,7 @@ static void matchSlice(ElementDefinitionNavigator snapNav, ElementDefinitionNavi // Match current snapshot and differential extension slice elements on extension type profile // Returns an initialized MatchInfo with action = Merge | Add // defaultBase represents the base element for newly introduced slices - static void matchExtensionSlice(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, + private static void matchExtensionSlice(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, List discriminators, MatchInfo match) { // [WMR 20170110] Accept missing slicing component, e.g. to close the extension slice: Extension.extension { max = 0 } @@ -637,7 +637,7 @@ static void matchExtensionSlice(ElementDefinitionNavigator snapNav, ElementDefin // Match current snapshot and differential slice elements on @type = Element.Type.Code // Returns an initialized MatchInfo with action = Merge | Add - static void matchSliceByTypeCode(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, MatchInfo match) + private static void matchSliceByTypeCode(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, MatchInfo match) { var diffTypeCodes = diffNav.Current.Type.Select(t => t.Code).ToList(); if (diffTypeCodes.Count == 0) @@ -662,7 +662,7 @@ static void matchSliceByTypeCode(ElementDefinitionNavigator snapNav, ElementDefi // Match current snapshot and differential slice elements on @type|@profile = Element.Type.Code and Element.Type.Profile // Returns an initialized MatchInfo with action = Merge | Add - static void matchSliceByTypeProfile(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, MatchInfo match) + private static void matchSliceByTypeProfile(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, MatchInfo match) { matchSliceByTypeCode(snapNav, diffNav, match); if (match.Action == MatchAction.Merge) @@ -692,7 +692,7 @@ static void matchSliceByTypeProfile(ElementDefinitionNavigator snapNav, ElementD } /// Given an extension element, return the canonical url of the associated extension definition, or null. - static string getExtensionProfileUri(ElementDefinition elem) + private static string getExtensionProfileUri(ElementDefinition elem) { Debug.Assert(elem.IsExtension()); var elemTypes = elem.Type; @@ -703,13 +703,13 @@ static string getExtensionProfileUri(ElementDefinition elem) } /// Fixed default discriminator for slicing extension elements. - static readonly string UrlDiscriminator = "url"; + private static readonly string URLDISCRIMINATOR = "url"; /// Determines if the specified value equals the special predefined discriminator for slicing on element type profile. - static bool isProfileDiscriminator(ElementDefinition.DiscriminatorComponent discriminator) => discriminator?.Type == ElementDefinition.DiscriminatorType.Profile; + private static bool isProfileDiscriminator(ElementDefinition.DiscriminatorComponent discriminator) => discriminator?.Type == ElementDefinition.DiscriminatorType.Profile; /// Determines if the specified value equals the special predefined discriminator for slicing on element type. - static bool isTypeDiscriminator(ElementDefinition.DiscriminatorComponent discriminator) => discriminator?.Type == ElementDefinition.DiscriminatorType.Type; + private static bool isTypeDiscriminator(ElementDefinition.DiscriminatorComponent discriminator) => discriminator?.Type == ElementDefinition.DiscriminatorType.Type; //EK: Commented out since this combination is not valid/has never been valid? In any case we did not consider it //when composing the new DiscriminatorType valueset. @@ -717,11 +717,11 @@ static string getExtensionProfileUri(ElementDefinition elem) //static bool isTypeAndProfileDiscriminator(string discriminator) => StringComparer.Ordinal.Equals(discriminator, TypeAndProfileDiscriminator); /// Determines if the specified value equals the fixed default discriminator for slicing extension elements. - static bool isUrlDiscriminator(ElementDefinition.DiscriminatorComponent discriminator) => StringComparer.Ordinal.Equals(discriminator?.Path, UrlDiscriminator); + private static bool isUrlDiscriminator(ElementDefinition.DiscriminatorComponent discriminator) => StringComparer.Ordinal.Equals(discriminator?.Path, URLDISCRIMINATOR); // [WMR 20160801] // Determine if the specified discriminator(s) match on (type and) profile - static bool isTypeProfileDiscriminator(IEnumerable discriminators) + private static bool isTypeProfileDiscriminator(IEnumerable discriminators) { // [WMR 20170411] TODO: update for STU3? @@ -746,7 +746,7 @@ static bool isTypeProfileDiscriminator(IEnumerableList names of all following choice type elements ('[x]'). - static List listChoiceElements(ElementDefinitionNavigator nav) + private static List listChoiceElements(ElementDefinitionNavigator nav) { var bm = nav.Bookmark(); var result = new List(); @@ -767,7 +767,7 @@ static List listChoiceElements(ElementDefinitionNavigator nav) /// Find name of child element that represent a rename of the specified choice type element name. /// An instance. /// Original choice type element name ending with "[x]". - static string findRenamedChoiceElement(ElementDefinitionNavigator nav, string choiceName) + private static string findRenamedChoiceElement(ElementDefinitionNavigator nav, string choiceName) { var bm = nav.Bookmark(); var result = new List(); @@ -792,7 +792,7 @@ static string findRenamedChoiceElement(ElementDefinitionNavigator nav, string ch return null; } - static string previousElementName(ElementDefinitionNavigator nav) + private static string previousElementName(ElementDefinitionNavigator nav) { string result = null; diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs index e61dc5aecb..755db7349c 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs @@ -31,9 +31,6 @@ // Detect and fix invalid non-null sliceNames on root elements #define FIX_SLICENAMES_ON_ROOT_ELEMENTS -// [WMR 20180409] Resolve contentReference from core resource/datatype (StructureDefinition.type) -#define FIX_CONTENTREFERENCE - using Hl7.Fhir.Model; using Hl7.Fhir.Specification.Navigation; using Hl7.Fhir.Specification.Source; @@ -61,8 +58,8 @@ public sealed partial class SnapshotGenerator // TODO: Probably also need to share a common recursion stack... - readonly SnapshotGeneratorSettings _settings; - readonly SnapshotRecursionStack _stack = new SnapshotRecursionStack(); + private readonly SnapshotGeneratorSettings _settings; + private readonly SnapshotRecursionStack _stack = new(); /// /// Create a new instance of the class @@ -132,7 +129,7 @@ private static IAsyncResourceResolver verifySource(IAsyncResourceResolver resolv public SnapshotGeneratorSettings Settings => _settings; /// Returns a reference to the profile uri of the currently generating snapshot, or null. - string CurrentProfileUri => _stack.CurrentProfileUri; + private string CurrentProfileUri => _stack.CurrentProfileUri; /// /// (Re-)generate the component of the specified instance. @@ -480,7 +477,7 @@ void fixInvalidSliceNamesInSpecialization(StructureDefinition sd) #endif #if FIX_SLICENAMES_ON_ROOT_ELEMENTS - void fixInvalidSliceNameOnRootElement(ElementDefinition elem, StructureDefinition sd) + private void fixInvalidSliceNameOnRootElement(ElementDefinition elem, StructureDefinition sd) { if (elem != null) { @@ -522,24 +519,16 @@ private async T.Task expandElement(ElementDefinitionNavigator nav) if (!String.IsNullOrEmpty(defn.ContentReference)) { -#if FIX_CONTENTREFERENCE - // [WMR 20180409] NEW - // Resolve contentReference from core resource/datatype definition - // Specified by StructureDefinition.type == root element name - var coreStructure = await getStructureForContentReference(nav, true).ConfigureAwait(false); + // getStructureForContentReference emits issue if profile cannot be resolved if (coreStructure == null) { return false; } var sourceNav = ElementDefinitionNavigator.ForSnapshot(coreStructure); -#else - // [WMR 20180409] WRONG! - // Resolves contentReference from current StructureDef - // Recursive child items should NOT inherit constraints from parent in same profile - var sourceNav = new ElementDefinitionNavigator(nav); -#endif - if (!sourceNav.JumpToNameReference(defn.ContentReference)) + var profileRef = ProfileReference.Parse(defn.ContentReference); + + if (!sourceNav.JumpToNameReference("#" + profileRef.ElementName)) { addIssueInvalidNameReference(defn); return false; @@ -901,7 +890,7 @@ private async T.Task mergeElement(ElementDefinitionNavigator snap, ElementDefini // [WMR 20170105] New: determine wether to expand the current element // Notify client to allow overriding the default behavior - bool mustExpandElement(ElementDefinitionNavigator diffNav) + private bool mustExpandElement(ElementDefinitionNavigator diffNav) { var hasChildren = diffNav.HasChildren; bool mustExpand = hasChildren; @@ -913,7 +902,7 @@ bool mustExpandElement(ElementDefinitionNavigator diffNav) /// /// /// Determines if the snapshot should inherit Element.id values from the differential. - void mergeElementDefinition(ElementDefinition snap, ElementDefinition diff, bool mergeElementId) + private void mergeElementDefinition(ElementDefinition snap, ElementDefinition diff, bool mergeElementId) { // [WMR 20170421] Add parameter to control when (not) to inherit Element.id @@ -942,7 +931,7 @@ void mergeElementDefinition(ElementDefinition snap, ElementDefinition diff, bool // By default, use strategy (A): ignore custom type profile, merge from base // If mergeTypeProfiles is enabled, then first merge custom type profile before merging base - static readonly string DomainResource_Extension_Path = ModelInfo.FhirTypeToFhirTypeName(FHIRAllTypes.DomainResource) + ".extension"; + private static readonly string DomainResource_Extension_Path = ModelInfo.FhirTypeToFhirTypeName(FHIRAllTypes.DomainResource) + ".extension"; // Resolve the type profile of the currently selected element and merge into snapshot private async T.Task mergeTypeProfiles(ElementDefinitionNavigator snap, ElementDefinitionNavigator diff) @@ -1193,7 +1182,7 @@ private async T.Task mergeTypeProfiles(ElementDefinitionNavigator snap, El // However these properties are not constrained by the referencing profile itself, but inherited from the referenced extension definition. // So we actually should NOT emit these annotations on the referencing profile properties. // Call this method after merging an external extension definition to remove incorrect annotations from the target profile extension element - static void fixExtensionAnnotationsAfterMerge(ElementDefinition elem) + private static void fixExtensionAnnotationsAfterMerge(ElementDefinition elem) { if (IsEqualPath(elem.Base?.Path, DomainResource_Extension_Path)) { @@ -1208,7 +1197,7 @@ static void fixExtensionAnnotationsAfterMerge(ElementDefinition elem) /// Remove existing annotations, fix Base components /// // [WMR 20170501] OBSOLETE: notify listeners - moved to prepareTypeProfileChildren - bool copyChildren(ElementDefinitionNavigator nav, ElementDefinitionNavigator typeNav) // , StructureDefinition typeStructure) + private bool copyChildren(ElementDefinitionNavigator nav, ElementDefinitionNavigator typeNav) // , StructureDefinition typeStructure) { // [WMR 20170426] IMPORTANT! // Do NOT modify typeNav/typeStructure @@ -1269,7 +1258,7 @@ bool copyChildren(ElementDefinitionNavigator nav, ElementDefinitionNavigator typ /// For each element, raise the event /// and ensure that the element id is assigned. /// - void prepareMergedTypeProfileElements(ElementDefinitionNavigator snap, StructureDefinition typeProfile) + private void prepareMergedTypeProfileElements(ElementDefinitionNavigator snap, StructureDefinition typeProfile) { // Recursively re-generate IDs for elements inherited from external rebased type profile if (_settings.GenerateElementIds) @@ -1301,7 +1290,7 @@ void prepareMergedTypeProfileElements(ElementDefinitionNavigator snap, Structure // [WMR 20170713] NEW - for expandElementType // Raise OnPrepareElement event and provide matching base elements from typeNav // Cannot use prepareMergedTypeProfileElements, as the provided base element is incorrect in this case - void prepareExpandedTypeProfileElements(ElementDefinitionNavigator snap, ElementDefinitionNavigator typeNav) + private void prepareExpandedTypeProfileElements(ElementDefinitionNavigator snap, ElementDefinitionNavigator typeNav) { // Recursively re-generate IDs for elements inherited from external rebased type profile if (_settings.GenerateElementIds) @@ -1318,7 +1307,7 @@ void prepareExpandedTypeProfileElements(ElementDefinitionNavigator snap, Element } // [WMR 20170718] NEW - for addSlice - void prepareSliceElements(ElementDefinitionNavigator snap, ElementDefinitionNavigator sliceBase) + private void prepareSliceElements(ElementDefinitionNavigator snap, ElementDefinitionNavigator sliceBase) { if (MustRaisePrepareElement) { @@ -1328,7 +1317,7 @@ void prepareSliceElements(ElementDefinitionNavigator snap, ElementDefinitionNavi // Raise OnPrepareElement event for all elements in snap subtree // Recurse all elements and find matching base element in typeNav - void prepareExpandedElementsInternal(ElementDefinitionNavigator snap, ElementDefinitionNavigator typeNav, bool prepareRoot) + private void prepareExpandedElementsInternal(ElementDefinitionNavigator snap, ElementDefinitionNavigator typeNav, bool prepareRoot) { Debug.Assert(MustRaisePrepareElement); @@ -1417,7 +1406,7 @@ void prepareExpandedElementsInternal(ElementDefinitionNavigator snap, ElementDef } // Initialize [...].extension.url fixed url value, if missing - static void fixExtensionUrl(ElementDefinitionNavigator nav) + private static void fixExtensionUrl(ElementDefinitionNavigator nav) { // Case-insensitive comparison to match root "Extension" and child "extension" element if (StringComparer.OrdinalIgnoreCase.Equals("extension", nav.PathName) && nav.HasChildren) @@ -1549,7 +1538,7 @@ private async T.Task startSlice(ElementDefinitionNavigator snap, ElementDefiniti } } - static ElementDefinition getSliceLocation(ElementDefinitionNavigator diff, ElementDefinition location) + private static ElementDefinition getSliceLocation(ElementDefinitionNavigator diff, ElementDefinition location) { if (location == null) { @@ -1651,7 +1640,7 @@ private async T.Task addSlice(ElementDefinitionNavigator snap, ElementDefinition await mergeElement(snap, diff).ConfigureAwait(false); } - void addSliceBase(ElementDefinitionNavigator snap, ElementDefinitionNavigator diff, ElementDefinitionNavigator sliceBase) + private void addSliceBase(ElementDefinitionNavigator snap, ElementDefinitionNavigator diff, ElementDefinitionNavigator sliceBase) { var lastSlice = findSliceAddPosition(snap, diff); bool result = false; @@ -1684,7 +1673,7 @@ void addSliceBase(ElementDefinitionNavigator snap, ElementDefinitionNavigator di // Search snapshot slice group for suitable position to add new diff slice // If the snapshot contains a matching base slice element, then append after reslice group // Otherwise append after last slice - static Bookmark findSliceAddPosition(ElementDefinitionNavigator snap, ElementDefinitionNavigator diff) + private static Bookmark findSliceAddPosition(ElementDefinitionNavigator snap, ElementDefinitionNavigator diff) { var bm = snap.Bookmark(); var name = snap.PathName; @@ -1717,8 +1706,7 @@ static Bookmark findSliceAddPosition(ElementDefinitionNavigator snap, ElementDef return result; } - - static ElementDefinition createExtensionSlicingEntry(ElementDefinition baseExtensionElement) + private static ElementDefinition createExtensionSlicingEntry(ElementDefinition baseExtensionElement) { // Create the slicing entry by cloning the base Extension element var elem = baseExtensionElement != null ? (ElementDefinition)baseExtensionElement.DeepCopy() : new ElementDefinition(); @@ -1743,7 +1731,7 @@ static ElementDefinition createExtensionSlicingEntry(ElementDefinition baseExten return elem; } - void markChangedByDiff(Element element) + private void markChangedByDiff(Element element) { if (_settings.GenerateExtensionsOnConstraints) { @@ -1809,7 +1797,7 @@ private async T.Task getStructureForTypeRef(ElementDefiniti /// An reference. /// A reference. /// A instance, or null. - private async static T.Task getStructureDefinitionForTypeCode(IAsyncResourceResolver resolver, FhirUri typeCodeElement) + private static async T.Task getStructureDefinitionForTypeCode(IAsyncResourceResolver resolver, FhirUri typeCodeElement) { StructureDefinition sd = null; var typeCode = typeCodeElement.Value; @@ -1829,9 +1817,6 @@ private async static T.Task getStructureDefinitionForTypeCo return sd; } - -#if FIX_CONTENTREFERENCE - // [WMR 20180410] Resolve the defining target structure of a contentReference private async T.Task getStructureForContentReference(ElementDefinitionNavigator nav, bool ensureSnapshot) { Debug.Assert(nav != null); @@ -1858,9 +1843,8 @@ private async T.Task getStructureForContentReference(Elemen return null; } -#endif - bool verifyStructure(StructureDefinition sd, string profileUrl, string location = null) + private bool verifyStructure(StructureDefinition sd, string profileUrl, string location = null) { if (sd == null) { @@ -2091,10 +2075,10 @@ private async T.Task getSnapshotRootElement(StructureDefiniti } /// Determine if the specified element paths are equal. Performs an ordinal comparison. - static bool IsEqualPath(string path, string other) => StringComparer.Ordinal.Equals(path, other); + private static bool IsEqualPath(string path, string other) => StringComparer.Ordinal.Equals(path, other); /// Determine if the specified element names are equal. Performs an ordinal comparison. - static bool IsEqualName(string name, string other) => StringComparer.Ordinal.Equals(name, other); + private static bool IsEqualName(string name, string other) => StringComparer.Ordinal.Equals(name, other); // [WMR 20170227] NEW // TODO: diff --git a/src/Hl7.Fhir.Specification/Specification/Source/ResourceResolverExtensions.cs b/src/Hl7.Fhir.Specification/Specification/Source/ResourceResolverExtensions.cs index 1ff00e1cdc..62d5e97de8 100644 --- a/src/Hl7.Fhir.Specification/Specification/Source/ResourceResolverExtensions.cs +++ b/src/Hl7.Fhir.Specification/Specification/Source/ResourceResolverExtensions.cs @@ -23,7 +23,7 @@ public static class ResourceResolverExtensions [Obsolete("Using synchronous resolvers is not recommended anymore, use FindExtensionDefinitionAsync() instead.")] public static StructureDefinition FindExtensionDefinition(this IResourceResolver resolver, string uri) { - if (!(resolver.ResolveByCanonicalUri(uri) is StructureDefinition sd)) return null; + if (resolver.ResolveByCanonicalUri(uri) is not StructureDefinition sd) return null; if (!sd.IsExtension) throw Error.Argument(nameof(uri), $"Found StructureDefinition at '{uri}', but is not an extension"); @@ -38,7 +38,7 @@ public static StructureDefinition FindExtensionDefinition(this IResourceResolver /// Returns a StructureDefinition if it is resolvable and defines an extension, otherwise null. public static async T.Task FindExtensionDefinitionAsync(this IAsyncResourceResolver resolver, string uri) { - if (!(await resolver.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false) is StructureDefinition sd)) return null; + if (await resolver.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false) is not StructureDefinition sd) return null; if (!sd.IsExtension) throw Error.Argument(nameof(uri), $"Found StructureDefinition at '{uri}', but is not an extension"); @@ -63,9 +63,7 @@ public static async T.Task FindStructureDefinitionAsync(thi public static StructureDefinition FindStructureDefinitionForCoreType(this IResourceResolver resolver, string typename) { var url = Uri.IsWellFormedUriString(typename, UriKind.Absolute) ? typename : ModelInfo.CanonicalUriForFhirCoreType(typename); -#pragma warning disable CS0618 // Type or member is obsolete return resolver.FindStructureDefinition(url); -#pragma warning restore CS0618 // Type or member is obsolete } /// @@ -82,10 +80,7 @@ public static async T.Task FindStructureDefinitionForCoreTy /// [Obsolete("Using synchronous resolvers is not recommended anymore, use FindStructureDefinitionForCoreTypeAsync() instead.")] - public static StructureDefinition FindStructureDefinitionForCoreType(this IResourceResolver resolver, FHIRAllTypes type) -#pragma warning disable CS0618 // Type or member is obsolete - => resolver.FindStructureDefinitionForCoreType(ModelInfo.FhirTypeToFhirTypeName(type)); -#pragma warning restore CS0618 // Type or member is obsolete + public static StructureDefinition FindStructureDefinitionForCoreType(this IResourceResolver resolver, FHIRAllTypes type) => resolver.FindStructureDefinitionForCoreType(ModelInfo.FhirTypeToFhirTypeName(type)); /// /// Resolve the StructureDefinition for the FHIR-defined type given in . diff --git a/src/Hl7.Fhir.Specification/Specification/StructureDefinitionSummaryProvider.cs b/src/Hl7.Fhir.Specification/Specification/StructureDefinitionSummaryProvider.cs index a505536f16..ccf03d11f0 100644 --- a/src/Hl7.Fhir.Specification/Specification/StructureDefinitionSummaryProvider.cs +++ b/src/Hl7.Fhir.Specification/Specification/StructureDefinitionSummaryProvider.cs @@ -51,9 +51,9 @@ public async T.Task ProvideAsync(string canonical) } var sd = await _resolver.FindStructureDefinitionAsync(mappedCanonical).ConfigureAwait(false); - - return sd is null ? - null + + return sd is null ? + null : (IStructureDefinitionSummary)new StructureDefinitionComplexTypeSerializationInfo(ElementDefinitionNavigator.ForSnapshot(sd)); } @@ -175,7 +175,7 @@ internal ElementDefinitionSerializationInfo(ElementDefinitionNavigator nav) _definition = nav.Current; Order = nav.OrdinalPosition.Value; // cannot be null, since nav.Current != null - string noChoiceSuffix(string n) => n.EndsWith("[x]") ? n.Substring(0, n.Length - 3) : n; + static string noChoiceSuffix(string n) => n.EndsWith("[x]") ? n.Substring(0, n.Length - 3) : n; } private static ITypeSerializationInfo[] buildTypes(ElementDefinitionNavigator nav) @@ -186,6 +186,9 @@ private static ITypeSerializationInfo[] buildTypes(ElementDefinitionNavigator na { var reference = nav.ShallowCopy(); var name = nav.Current.ContentReference; + + // Note that these contentReferences MAY be absolute urls in profiles, but since this provider works on the + // core SDs only, we do not have to take this into account. if (!reference.JumpToNameReference(name)) throw Error.InvalidOperation($"StructureDefinition '{nav?.StructureDefinition?.Url}' " + $"has a namereference '{name}' on element '{nav.Current.Path}' that cannot be resolved."); @@ -210,21 +213,15 @@ public XmlRepresentation Representation { if (!_definition.Representation.Any()) return XmlRepresentation.XmlElement; - switch (_definition.Representation.First()) + return _definition.Representation.First() switch { - case ElementDefinition.PropertyRepresentation.XmlAttr: - return XmlRepresentation.XmlAttr; - case ElementDefinition.PropertyRepresentation.XmlText: - return XmlRepresentation.XmlText; - case ElementDefinition.PropertyRepresentation.TypeAttr: - return XmlRepresentation.TypeAttr; - case ElementDefinition.PropertyRepresentation.CdaText: - return XmlRepresentation.CdaText; - case ElementDefinition.PropertyRepresentation.Xhtml: - return XmlRepresentation.XHtml; - default: - return XmlRepresentation.XmlElement; - } + ElementDefinition.PropertyRepresentation.XmlAttr => XmlRepresentation.XmlAttr, + ElementDefinition.PropertyRepresentation.XmlText => XmlRepresentation.XmlText, + ElementDefinition.PropertyRepresentation.TypeAttr => XmlRepresentation.TypeAttr, + ElementDefinition.PropertyRepresentation.CdaText => XmlRepresentation.CdaText, + ElementDefinition.PropertyRepresentation.Xhtml => XmlRepresentation.XHtml, + _ => XmlRepresentation.XmlElement, + }; } } diff --git a/src/Hl7.Fhir.Specification/Validation/ElementDefinitionNavigatorExtensions.cs b/src/Hl7.Fhir.Specification/Validation/ElementDefinitionNavigatorExtensions.cs index c4c15c4155..6989b8400d 100644 --- a/src/Hl7.Fhir.Specification/Validation/ElementDefinitionNavigatorExtensions.cs +++ b/src/Hl7.Fhir.Specification/Validation/ElementDefinitionNavigatorExtensions.cs @@ -6,8 +6,11 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ +#nullable enable + using Hl7.Fhir.Model; using Hl7.Fhir.Specification.Navigation; +using System; using System.Linq; namespace Hl7.Fhir.Validation @@ -45,5 +48,36 @@ internal static string ConstraintDescription(this ElementDefinition.ConstraintCo return desc; } + + /// + /// Resolve a the contentReference in a navigator and returns a navigator that is located on the target of the contentReference. + /// + /// The current navigator must be located at an element that contains a contentReference. + public static bool TryFollowContentReference(this ElementDefinitionNavigator sourceNavigator, Func resolver, out ElementDefinitionNavigator? targetNavigator) + { + targetNavigator = null; + + var reference = sourceNavigator.Current.ContentReference; + if (reference is null) return false; + + var profileRef = ProfileReference.Parse(reference); + + if (profileRef.IsAbsolute && profileRef.CanonicalUrl != sourceNavigator.StructureDefinition.Url) + { + // an external reference (e.g. http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item) + + var profile = resolver(profileRef.CanonicalUrl!); + if (profile is null) return false; + targetNavigator = ElementDefinitionNavigator.ForSnapshot(profile); + } + else + { + // a local reference + targetNavigator = sourceNavigator.ShallowCopy(); + } + + return targetNavigator.JumpToNameReference("#" + profileRef.ElementName); + } + } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification/Validation/Validator.cs b/src/Hl7.Fhir.Specification/Validation/Validator.cs index b04bc9ebe9..1ffdb5da6a 100644 --- a/src/Hl7.Fhir.Specification/Validation/Validator.cs +++ b/src/Hl7.Fhir.Specification/Validation/Validator.cs @@ -137,12 +137,13 @@ internal OperationOutcome ValidateInternal( return outcome; - StructureDefinition profileResolutionNeeded(string canonical) => + } + private StructureDefinition profileResolutionNeeded(string canonical) => //TODO: Need to make everything async in 2.x validator #pragma warning disable CS0618 // Type or member is obsolete Settings.ResourceResolver?.FindStructureDefinition(canonical); #pragma warning restore CS0618 // Type or member is obsolete - } + internal OperationOutcome ValidateInternal(ITypedElement instance, ElementDefinitionNavigator definition, ValidationState state) => ValidateInternal(instance, new[] { definition }, state).RemoveDuplicateMessages(); @@ -159,7 +160,7 @@ internal OperationOutcome ValidateInternal(ITypedElement elementNav, IEnumerable { var allDefinitions = definitions.ToList(); - if (allDefinitions.Count() == 1) + if (allDefinitions.Count == 1) outcome.Add(startValidation(allDefinitions.Single(), instance, state)); else { @@ -280,7 +281,7 @@ private OperationOutcome validateElement(ElementDefinitionNavigator definition, outcome.Add(this.ValidateMinMaxValue(elementConstraints, instance)); outcome.Add(ValidateMaxLength(elementConstraints, instance)); outcome.Add(this.ValidateFp(definition.StructureDefinition.Url, elementConstraints, instance)); - outcome.Add(this.ValidateExtension(elementConstraints, instance, "http://hl7.org/fhir/StructureDefinition/regex")); + outcome.Add(this.validateExtension(elementConstraints, instance, "http://hl7.org/fhir/StructureDefinition/regex")); outcome.Add(this.ValidateBinding(elementConstraints, instance, context)); // If the report only has partial information, no use to show the hierarchy, so flatten it. @@ -296,7 +297,7 @@ private OperationOutcome validateElement(ElementDefinitionNavigator definition, } - private OperationOutcome ValidateExtension(IExtendable elementDef, ITypedElement instance, string uri) + private OperationOutcome validateExtension(IExtendable elementDef, ITypedElement instance, string uri) { var outcome = new OperationOutcome(); @@ -304,7 +305,7 @@ private OperationOutcome ValidateExtension(IExtendable elementDef, ITypedElement if (pattern != null) { var regex = new Regex(pattern); - var value = toStringRepresentation(instance); + var value = Validator.toStringRepresentation(instance); var success = Regex.Match(value, "^" + regex + "$").Success; if (!success) @@ -361,7 +362,7 @@ internal OperationOutcome ValidateBinding(ElementDefinition.ElementDefinitionBin ts = new LocalTerminologyService(Settings.ResourceResolver.AsAsync()); } - ValidationContext vc = new ValidationContext() { TerminologyService = ts }; + var vc = new ValidationContext() { TerminologyService = ts }; try { @@ -377,20 +378,19 @@ internal OperationOutcome ValidateBinding(ElementDefinition.ElementDefinitionBin } internal OperationOutcome ValidateNameReference( - ElementDefinition definition, - ElementDefinitionNavigator allDefinitions, + ElementDefinition _, + ElementDefinitionNavigator profile, ScopedNode instance, ValidationState state) { var outcome = new OperationOutcome(); + var definition = profile.Current; - if (definition.ContentReference != null) + if (profile.Current.ContentReference != null) { Trace(outcome, "Start validation of constraints referred to by nameReference '{0}'".FormatWith(definition.ContentReference), Issue.PROCESSING_PROGRESS, instance); - var referencedPositionNav = allDefinitions.ShallowCopy(); - - if (referencedPositionNav.JumpToNameReference(definition.ContentReference)) + if (profile.TryFollowContentReference(profileResolutionNeeded, out var referencedPositionNav)) outcome.Include(ValidateInternal(instance, referencedPositionNav, state)); else Trace(outcome, $"ElementDefinition uses a non-existing nameReference '{definition.ContentReference}'", Issue.PROFILE_ELEMENTDEF_INVALID_NAMEREFERENCE, instance); @@ -423,8 +423,8 @@ internal OperationOutcome VerifyPrimitiveContents(ElementDefinition definition, // type? Would it convert it to a .NET native type? How to check? // The spec has no regexes for the primitives mentioned below, so don't check them - return definition.Type.Count() == 1 - ? ValidateExtension(definition.Type.Single(), instance, "http://hl7.org/fhir/StructureDefinition/structuredefinition-regex") + return definition.Type.Count == 1 + ? validateExtension(definition.Type.Single(), instance, "http://hl7.org/fhir/StructureDefinition/structuredefinition-regex") : outcome; } @@ -471,12 +471,8 @@ internal OperationOutcome.IssueComponent Trace(OperationOutcome outcome, string : null; } - private string toStringRepresentation(ITypedElement vp) - { - return vp == null || vp.Value == null ? - null : - PrimitiveTypeConverter.ConvertTo(vp.Value); - } + private static string toStringRepresentation(ITypedElement vp) => + vp?.Value == null ? null : PrimitiveTypeConverter.ConvertTo(vp.Value); internal ITypedElement ExternalReferenceResolutionNeeded(string reference, OperationOutcome outcome, string path) { From b4376a069bf843f0b34b5081b99ce6d54685e063 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Wed, 20 Apr 2022 16:15:18 +0200 Subject: [PATCH 03/12] Fixing a few more messages --- .../Snapshot/SnapshotGeneratorTest.cs | 44 +++++++++---------- .../Navigation/ElementDefinitionNavigator.cs | 11 +---- .../Specification/Snapshot/ElementMatcher.cs | 5 +-- 3 files changed, 25 insertions(+), 35 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index 80992316cf..fa4daf5663 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -3665,7 +3665,7 @@ public async T.Task TestInvalidProfileExtensionTarget() // - Patient.identifier:B/2 => Patient.identifier:B in MyPatient // - Patient.identifier:C => Patient.identifier in MyPatient - private static StructureDefinition SlicedPatientProfile => new StructureDefinition() + private static StructureDefinition SlicedPatientProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -3783,7 +3783,7 @@ public async T.Task TestSliceBase_SlicedPatient() Assert.AreEqual("2", nav.Current.Max); } - private static StructureDefinition NationalPatientProfile => new StructureDefinition() + private static StructureDefinition NationalPatientProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -3806,7 +3806,7 @@ public async T.Task TestSliceBase_SlicedPatient() } }; - private static StructureDefinition SlicedNationalPatientProfile => new StructureDefinition() + private static StructureDefinition SlicedNationalPatientProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = "http://example.org/fhir/StructureDefinition/MyNationalPatient", @@ -3998,7 +3998,7 @@ public async T.Task TestSliceBase_SlicedNationalPatient() #endif } - private static StructureDefinition ReslicedNationalPatientProfile => new StructureDefinition() + private static StructureDefinition ReslicedNationalPatientProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = "http://example.org/fhir/StructureDefinition/MyNationalPatient", @@ -4407,7 +4407,7 @@ private static void dumpMappings(IList mappi // Ewout: type slices cannot contain renamed elements! - private static StructureDefinition PatientNonTypeSliceProfile => new StructureDefinition() + private static StructureDefinition PatientNonTypeSliceProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -4452,7 +4452,7 @@ public async T.Task TestPatientNonTypeSlice() } // Ewout: type slices cannot contain renamed elements! - private static StructureDefinition ObservationSimpleQuantityProfile => new StructureDefinition() + private static StructureDefinition ObservationSimpleQuantityProfile => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -4613,7 +4613,7 @@ public async T.Task TestProfileConstraintsOnComplexExtensionChildren() // [WMR 20170424] For debugging ElementIdGenerator - private static StructureDefinition TestQuestionnaireProfile => new StructureDefinition() + private static StructureDefinition TestQuestionnaireProfile => new() { Type = FHIRAllTypes.Questionnaire.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Questionnaire), @@ -4787,7 +4787,7 @@ private static void assertElementIds(ElementDefinition elem, ElementDefinition b } - private static StructureDefinition TestPatientTypeSliceProfile => new StructureDefinition() + private static StructureDefinition TestPatientTypeSliceProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -4855,7 +4855,7 @@ public async T.Task TestElementIds_PatientWithTypeSlice() // [WMR 20170616] NEW - Test custom element IDs - private static StructureDefinition TestSlicedPatientWithCustomIdProfile => new StructureDefinition() + private static StructureDefinition TestSlicedPatientWithCustomIdProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -5116,7 +5116,7 @@ private void dumpBaseDefId(StructureDefinition sd) private const string PatientIdentifierTypeValueSetUri = @"http://example.org/fhir/ValueSet/PatientIdentifierTypeValueSet"; // Identifier profile with valueset binding on child element Identifier.type - private static StructureDefinition PatientIdentifierProfile => new StructureDefinition() + private static StructureDefinition PatientIdentifierProfile => new() { Type = FHIRAllTypes.Identifier.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Identifier), @@ -5143,7 +5143,7 @@ private void dumpBaseDefId(StructureDefinition sd) // Patient profile with type profile constraint on Patient.identifier // Snapshot should pick up the valueset binding on Identifier.type - private static StructureDefinition PatientProfileWithIdentifierProfile => new StructureDefinition() + private static StructureDefinition PatientProfileWithIdentifierProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -5206,7 +5206,7 @@ public async T.Task TestTypeProfileWithChildElementBinding() Assert.IsFalse(nav.MoveToChild("type")); } - private static StructureDefinition QuestionnaireResponseWithSlice => new StructureDefinition() + private static StructureDefinition QuestionnaireResponseWithSlice => new() { Type = FHIRAllTypes.QuestionnaireResponse.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.QuestionnaireResponse), @@ -5312,7 +5312,7 @@ public async T.Task TestQRSliceChildrenBindings() // When expanding MyVitalSigns, the annotated base elements also include local diff constraints... WRONG! // As a result, Forge will not detect the existing local constraints (no yellow pen, excluded from output). - private static StructureDefinition MyDerivedObservation => new StructureDefinition() + private static StructureDefinition MyDerivedObservation => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -5376,7 +5376,7 @@ public async T.Task TestDerivedObservation() Assert.AreEqual(coreMethodElem.Short, baseElem.Short); } - private static StructureDefinition MyMoreDerivedObservation => new StructureDefinition() + private static StructureDefinition MyMoreDerivedObservation => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = MyDerivedObservation.Url, @@ -5458,7 +5458,7 @@ public async T.Task TestMoreDerivedObservation() } // [WMR 20170718] Test for slicing issue - private static StructureDefinition MySlicedDocumentReference => new StructureDefinition() + private static StructureDefinition MySlicedDocumentReference => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.DocumentReference), @@ -5553,7 +5553,7 @@ public async T.Task TestNamedSliceMinCardinality() // [WMR 20170718] NEW // Accept and handle derived profile constraints on existing slice entry in base profile - private static StructureDefinition MySlicedBasePatient => new StructureDefinition() + private static StructureDefinition MySlicedBasePatient => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -5580,7 +5580,7 @@ public async T.Task TestNamedSliceMinCardinality() } }; - private static StructureDefinition MyMoreDerivedPatient => new StructureDefinition() + private static StructureDefinition MyMoreDerivedPatient => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = MySlicedBasePatient.Url, @@ -5680,7 +5680,7 @@ public async T.Task TestDosage() } } - private static StructureDefinition MedicationStatementWithSimpleQuantitySlice => new StructureDefinition() + private static StructureDefinition MedicationStatementWithSimpleQuantitySlice => new() { Type = FHIRAllTypes.MedicationStatement.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.MedicationStatement), @@ -5756,7 +5756,7 @@ public async T.Task TestSimpleQuantitySlice() private const string SL_HumanNameTitleSuffixUri = @"http://example.org/fhir/StructureDefinition/SL-HumanNameTitleSuffix"; // Extension on complex datatype HumanName - private static StructureDefinition SL_HumanNameTitleSuffix => new StructureDefinition() + private static StructureDefinition SL_HumanNameTitleSuffix => new() { Type = FHIRAllTypes.Extension.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Extension), @@ -5789,7 +5789,7 @@ public async T.Task TestSimpleQuantitySlice() }; // Profile on complex datatype HumanName with extension element - private static StructureDefinition SL_HumanNameBasis => new StructureDefinition() + private static StructureDefinition SL_HumanNameBasis => new() { Type = FHIRAllTypes.HumanName.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.HumanName), @@ -5819,7 +5819,7 @@ public async T.Task TestSimpleQuantitySlice() }; // Profile on Patient referencing custom HumanName datatype profile - private static StructureDefinition SL_PatientBasis => new StructureDefinition() + private static StructureDefinition SL_PatientBasis => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -5849,7 +5849,7 @@ public async T.Task TestSimpleQuantitySlice() private const string SL_NameSuffixValueSetUri = @"http://fhir.de/ValueSet/deuev/anlage-7-namenszusaetze"; // Derived profile on Patient - private static StructureDefinition SL_PatientDerived => new StructureDefinition() + private static StructureDefinition SL_PatientDerived => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = SL_PatientBasis.Url, diff --git a/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs b/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs index cb8e19576b..4aab5dd3b6 100644 --- a/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs @@ -375,16 +375,7 @@ public bool InsertBefore(ElementDefinition sibling) return true; } - private bool canInsertSiblingHere() - { - // We're not positioned anywhere... - if (OrdinalPosition == null) return false; - - // Cannot insert a sibling to the unique root element - if (OrdinalPosition == 0) return false; - - return true; - } + private bool canInsertSiblingHere() => OrdinalPosition != null && OrdinalPosition != 0; /// diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs index 42f4b19027..b48ddc8724 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs @@ -770,7 +770,6 @@ private static List listChoiceElements(ElementDefinitionNavigator nav) private static string findRenamedChoiceElement(ElementDefinitionNavigator nav, string choiceName) { var bm = nav.Bookmark(); - var result = new List(); if (nav.MoveToFirstChild()) { @@ -832,7 +831,7 @@ public static void DumpMatches(this IEnumerable matche if (snapNav.Current != null && snapNav.Current.SliceName != null) bPos += $" '{snapNav.Current.SliceName}'"; if (diffNav.Current != null && diffNav.Current.SliceName != null) dPos += $" '{diffNav.Current.SliceName}'"; - Debug.WriteLine($"B:{bPos} <-- {match.Action.ToString()} --> D:{dPos}"); + Debug.WriteLine($"B:{bPos} <-- {match.Action} --> D:{dPos}"); } snapNav.ReturnToBookmark(sbm); @@ -857,7 +856,7 @@ public static void DumpMatch(this ElementMatcher.MatchInfo match, ElementDefinit if (snapNav.Current != null && snapNav.Current.SliceName != null) bPos += $" '{snapNav.Current.SliceName}'"; if (diffNav.Current != null && diffNav.Current.SliceName != null) dPos += $" '{diffNav.Current.SliceName}'"; - Debug.WriteLine($"B:{bPos} <-- {match.Action.ToString()} --> D:{dPos}"); + Debug.WriteLine($"B:{bPos} <-- {match.Action} --> D:{dPos}"); snapNav.ReturnToBookmark(sbm); diffNav.ReturnToBookmark(dbm); From 82b2ee3fc0d7b9955cfcc97c3e2b7bd1eb91fab8 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Thu, 21 Apr 2022 10:32:50 +0200 Subject: [PATCH 04/12] Made unit-tests work, added tests for new code. --- .../ProfileNavigationTest.cs | 44 ++++++++++++++++--- .../Navigation/ElementDefinitionNavigator.cs | 4 +- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs b/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs index 5f9aecdec2..93b517bc8f 100644 --- a/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs @@ -10,7 +10,9 @@ using Hl7.Fhir.Specification.Navigation; using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Utility; +using Hl7.Fhir.Validation; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; using System.Collections.Generic; using System.Linq; @@ -24,7 +26,10 @@ public class ProfileNavigationTest [TestInitialize] public void Setup() { - _source = new CachedResolver(new DirectorySource("TestData/validation")); + _source = new CachedResolver( + new MultiResolver( + new DirectorySource("TestData/validation"), + ZipSource.CreateValidationSource())); } @@ -422,19 +427,48 @@ public void CopyChildTree() } [TestMethod] - public void LocateElementByName() + public void LocateContentReference() { var nav = createTestNav(); + nav.StructureDefinition = new StructureDefinition() { Url = "http://nu.nl/test" }; nav.JumpToFirst("A.B.C1.D"); - nav.Current.ContentReference = "A-Named-Constraint"; + nav.Current.ElementId = "A-Named-Constraint"; nav.Reset(); - Assert.IsTrue(nav.JumpToNameReference("A-Named-Constraint")); + Assert.IsTrue(nav.JumpToNameReference("#A-Named-Constraint")); Assert.AreEqual(7, nav.OrdinalPosition); - Assert.IsFalse(nav.JumpToNameReference("IDontExist")); + Assert.IsTrue(nav.JumpToNameReference("http://nu.nl/test#A-Named-Constraint")); + Assert.AreEqual(7, nav.OrdinalPosition); + + Assert.IsFalse(nav.JumpToNameReference("#IDontExist")); + + Assert.ThrowsException(() => + nav.JumpToNameReference("http://then.nl/test#A-Named-Constraint")); } + [TestMethod] + public void LocateExternalContentReference() + { + var nav = createTestNav(); + nav.StructureDefinition = new StructureDefinition() { Url = "http://nu.nl/test" }; + nav.JumpToFirst("A.B.C1.D"); + nav.Current.ContentReference = "http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"; + + string lastHit = null; + + var qUrl = "http://hl7.org/fhir/StructureDefinition/Questionnaire"; + Assert.IsTrue(nav.TryFollowContentReference(resolver, out var target)); + Assert.AreEqual(lastHit, qUrl); + Assert.AreEqual(qUrl, target.StructureDefinition.Url); + Assert.AreEqual("Questionnaire.item", target.Current.Path); + + StructureDefinition resolver(string url) + { + lastHit = url; + return _source.FindStructureDefinition(url); + } + } [TestMethod] public void TestNodeDuplication() diff --git a/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs b/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs index 4aab5dd3b6..05f6bc8da3 100644 --- a/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs @@ -90,7 +90,7 @@ public static ElementDefinitionNavigator ForDifferential(StructureDefinition sd) } /// Returns a reference to the instance containing the navigated elements. - public StructureDefinition StructureDefinition { get; private set; } + public StructureDefinition StructureDefinition { get; internal set; } /// Indicates if the navigator has not yet been positioned on a node. /// true if equals null, or false otherwise. @@ -292,7 +292,7 @@ public bool JumpToNameReference(string nameReference) for (int pos = 0; pos < Count; pos++) { - if (Elements[pos].ElementId == "#" + profileRef.ElementName) + if (Elements[pos].ElementId == profileRef.ElementName) { OrdinalPosition = pos; return true; From f2e45c8366f2d9300356784cbd6f01ba84691599 Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Mon, 25 Apr 2022 10:55:34 +0200 Subject: [PATCH 05/12] fix unit test and remove warnings --- src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs | 2 ++ .../Validation/BasicValidationTests.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs b/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs index 93b517bc8f..682301e55b 100644 --- a/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs @@ -466,7 +466,9 @@ public void LocateExternalContentReference() StructureDefinition resolver(string url) { lastHit = url; +#pragma warning disable CS0618 // Type or member is obsolete return _source.FindStructureDefinition(url); +#pragma warning restore CS0618 // Type or member is obsolete } } diff --git a/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs b/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs index bad4212a42..4c4eb796e0 100644 --- a/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs @@ -1297,7 +1297,7 @@ public void ValidateAbsoluteContentReferences() { new Questionnaire.ItemComponent() { - LinkId = "1", + LinkId = "1.1", Type = Questionnaire.QuestionnaireItemType.String } } From 5e194a8545f811ba1c492a5e1676a663b332bf14 Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Mon, 25 Apr 2022 11:19:04 +0200 Subject: [PATCH 06/12] create unit test for generating absolute contenReferences in snapshots --- .../Snapshot/SnapshotGeneratorTest.cs | 80 +++++++++++++++++-- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index fa4daf5663..9e19489e56 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -7524,6 +7524,74 @@ public async T.Task CheckCardinalityOfProfiledType() sutCode.Min.Should().Be(sdCode.Min); } + [TestMethod] + public async T.Task TestAbsoluteContentReferenceGeneration() + { + //prepare + var zipSource = ZipSource.CreateValidationSource(); + var generator = new SnapshotGenerator(zipSource, SnapshotGeneratorSettings.CreateDefault()); + + + // Test if core resource has relative content references. + var coreQuestionnaire = await _testResolver.FindStructureDefinitionAsync("http://hl7.org/fhir/StructureDefinition/Questionnaire"); + var coreSnapshot = await generator.GenerateAsync(coreQuestionnaire); + coreSnapshot.Should().Contain(e => e.ContentReference == "#Questionnaire#Questionnaire.item"); + + + //Create profile for testing creation of absolute references. + var profile = new StructureDefinition + { + Url = "http://firely-sdk.org/fhir/StructureDefinition/content-reference-check", + Status = PublicationStatus.Draft, + FhirVersion = "3.0.2", + Kind = StructureDefinition.StructureDefinitionKind.Resource, + Abstract = false, + Type = "Questionnaire", + BaseDefinition = "http://hl7.org/fhir/StructureDefinition/Questionnaire", + Derivation = StructureDefinition.TypeDerivationRule.Constraint, + Differential = new StructureDefinition.DifferentialComponent + { + Element = new List + { + new ElementDefinition + { + ElementId = "Questionnaire.item", + Path = "Questionnaire.item", + Slicing = new ElementDefinition.SlicingComponent + { + Discriminator = new List + { + new ElementDefinition.DiscriminatorComponent + { + Type = ElementDefinition.DiscriminatorType.Value, + Path = "type" + } + }, + Rules = ElementDefinition.SlicingRules.Open + } + }, + new ElementDefinition + { + ElementId = "Questionnaire.item:booleanItem", + Path = "Questionnaire.item", + SliceName = "booleanItem", + Min = 1 + }, + new ElementDefinition + { + ElementId = "Questionnaire.item:booleanItem.type", + Path = "Questionnaire.item.type", + Fixed = new Code("boolean") + } + } + } + }; + + // test if profiles have absolute content references. + var profileSnapshot = await generator.GenerateAsync(profile); + profileSnapshot.Should().Contain(e => e.ContentReference == "http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"); + } + [TestMethod] public async T.Task DiscriminatorBaseElementWithExpansionTest() { @@ -7534,14 +7602,14 @@ public async T.Task DiscriminatorBaseElementWithExpansionTest() var generator = new SnapshotGenerator(_testResolver, _settings); generator.PrepareElement += delegate (object _, SnapshotElementEventArgs e) - { - e.Element.Should().NotBeNull(); + { + e.Element.Should().NotBeNull(); - if (e.Element.Annotation() != null) - e.Element.RemoveAnnotations(); + if (e.Element.Annotation() != null) + e.Element.RemoveAnnotations(); - e.Element.AddAnnotation(new TestAnnotation(e.BaseStructure, e.BaseElement)); - }; + e.Element.AddAnnotation(new TestAnnotation(e.BaseStructure, e.BaseElement)); + }; var elements = await generator.GenerateAsync(sd); From c433e41d90c1c6acd384199c703421ca88d31083 Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Mon, 25 Apr 2022 17:32:22 +0200 Subject: [PATCH 07/12] expanded test --- .../Snapshot/SnapshotGeneratorTest.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index 9e19489e56..a913f3155f 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -7532,10 +7532,10 @@ public async T.Task TestAbsoluteContentReferenceGeneration() var generator = new SnapshotGenerator(zipSource, SnapshotGeneratorSettings.CreateDefault()); - // Test if core resource has relative content references. + //Test if core resource has relative content references. var coreQuestionnaire = await _testResolver.FindStructureDefinitionAsync("http://hl7.org/fhir/StructureDefinition/Questionnaire"); var coreSnapshot = await generator.GenerateAsync(coreQuestionnaire); - coreSnapshot.Should().Contain(e => e.ContentReference == "#Questionnaire#Questionnaire.item"); + coreSnapshot.Should().Contain(e => e.ContentReference == "#Questionnaire.item"); //Create profile for testing creation of absolute references. @@ -7582,6 +7582,12 @@ public async T.Task TestAbsoluteContentReferenceGeneration() ElementId = "Questionnaire.item:booleanItem.type", Path = "Questionnaire.item.type", Fixed = new Code("boolean") + }, + new ElementDefinition + { + ElementId = "Questionnaire.item:booleanItem.item.type", + Path = "Questionnaire.item.item.type", + Fixed = new Code("string") } } } @@ -7589,7 +7595,13 @@ public async T.Task TestAbsoluteContentReferenceGeneration() // test if profiles have absolute content references. var profileSnapshot = await generator.GenerateAsync(profile); - profileSnapshot.Should().Contain(e => e.ContentReference == "http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"); + + var cref1 = profileSnapshot.Where(e => e.ElementId == "Questionnaire.item:booleanItem.item").FirstOrDefault(); + cref1.ContentReference.Should().Be("http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"); + + var cref2 = profileSnapshot.Where(e => e.ElementId == "Questionnaire.item:booleanItem.item.item").FirstOrDefault(); + cref2.ContentReference.Should().Be("http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"); + // profileSnapshot.Should().Contain(e => e.ContentReference == "http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"); } [TestMethod] From 009785809ce219e30ca1c7078ef182e99bd107a9 Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Mon, 25 Apr 2022 17:32:47 +0200 Subject: [PATCH 08/12] make sure contentReferences for profiles are absolute --- .../Snapshot/SnapshotGeneratorTest.cs | 3 +- .../Snapshot/SnapshotGenerator.cs | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index a913f3155f..3b820b73ff 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -7535,7 +7535,8 @@ public async T.Task TestAbsoluteContentReferenceGeneration() //Test if core resource has relative content references. var coreQuestionnaire = await _testResolver.FindStructureDefinitionAsync("http://hl7.org/fhir/StructureDefinition/Questionnaire"); var coreSnapshot = await generator.GenerateAsync(coreQuestionnaire); - coreSnapshot.Should().Contain(e => e.ContentReference == "#Questionnaire.item"); + var item = coreSnapshot.Where(e => e.Path == "Questionnaire.item.item").FirstOrDefault(); + item.ContentReference.Should().Be("#Questionnaire.item"); //Create profile for testing creation of absolute references. diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs index 755db7349c..d00cb91790 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs @@ -399,6 +399,7 @@ private async T.Task> generate(StructureDefinition struc // [WMR 20170424] Inherit existing base components, generate if missing await ensureBaseComponents(snapshot.Element, structure.BaseDefinition, false).ConfigureAwait(false); + // [WMR 20170208] Moved to *AFTER* ensureBaseComponents - emits annotations... // [WMR 20160915] Derived profiles should never inherit the ChangedByDiff extension from the base structure snapshot.Element.RemoveAllConstrainedByDiffExtensions(); @@ -411,6 +412,8 @@ private async T.Task> generate(StructureDefinition struc } nav = new ElementDefinitionNavigator(snapshot.Element, structure); + + } else { @@ -419,6 +422,8 @@ private async T.Task> generate(StructureDefinition struc nav = new ElementDefinitionNavigator(snapshot.Element, structure); } + + // var nav = new ElementDefinitionNavigator(snapshot.Element); #if CACHE_ROOT_ELEMDEF @@ -879,6 +884,11 @@ private async T.Task mergeElement(ElementDefinitionNavigator snap, ElementDefini // Now, recursively merge the children await merge(snap, diff).ConfigureAwait(false); + // [MS 20220425] Make sure newly added contentReferences (from bases, types, and contentReferences) are absolute. + // Except when the it's a core profile (Derivation is not a Constraint) + if (snap.StructureDefinition.Derivation == StructureDefinition.TypeDerivationRule.Constraint) + ensureAbsoluteContentReferences(snap, snap.StructureDefinition.BaseDefinition); + // [WMR 20160720] NEW // generate [...]extension.url/fixedUri if missing // Ewout: [...]extension.url may be missing from differential @@ -888,6 +898,34 @@ private async T.Task mergeElement(ElementDefinitionNavigator snap, ElementDefini } } + private static void ensureAbsoluteContentReferences(ElementDefinitionNavigator nav, string baseTypeUrl) + { + var bookmark = nav.Bookmark(); + + if (nav.MoveToFirstChild()) + { + do + { + if (!string.IsNullOrEmpty(nav.Current?.ContentReference)) + ensureAbsoluteContentReference(nav.Current?.ContentReferenceElement, baseTypeUrl); + } + while (nav.MoveToNext()); + } + + nav.ReturnToBookmark(bookmark); + } + + private static void ensureAbsoluteContentReference(FhirUri contentReferenceElement, string baseTypeUrl) + { + if (contentReferenceElement.Value?.StartsWith("#") == true) + { + var contentRefBase = baseTypeUrl.StartsWith("http://") ? baseTypeUrl : ModelInfo.FhirCoreProfileBaseUri.ToString() + baseTypeUrl; + contentReferenceElement.Value = contentRefBase + contentReferenceElement.Value; + } + } + + + // [WMR 20170105] New: determine wether to expand the current element // Notify client to allow overriding the default behavior private bool mustExpandElement(ElementDefinitionNavigator diffNav) From 6436da12dc3f9533b9f70b4a2a68215f0b25b08e Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Mon, 25 Apr 2022 17:58:02 +0200 Subject: [PATCH 09/12] disables warning --- .../Specification/Navigation/StructureDefinitionWalker.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs b/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs index c33616dda2..a3bdb1ff4a 100644 --- a/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs +++ b/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs @@ -153,8 +153,10 @@ public IEnumerable Expand() return new[] { this }; else if (Current.Current.ContentReference != null) { +#pragma warning disable CS0618 // Type or member is obsolete if (!Current.TryFollowContentReference(s => Resolver.FindStructureDefinition(s), out var reference)) throw new StructureDefinitionWalkerException($"The contentReference '{reference}' cannot be resolved."); +#pragma warning restore CS0618 // Type or member is obsolete return new[] { new StructureDefinitionWalker(reference!, _resolver) }; } From da1d33fbd9132e6af7308a1ba415aa084011650d Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Mon, 25 Apr 2022 18:03:38 +0200 Subject: [PATCH 10/12] remove item group --- .../Hl7.Fhir.Specification.Tests.csproj | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj b/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj index f2782d1c1e..c72b9ec9ad 100644 --- a/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj +++ b/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj @@ -32,46 +32,6 @@ PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 1ce31794932a1f3a935acf8fe96fcb9ab0254555 Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Tue, 26 Apr 2022 10:23:40 +0200 Subject: [PATCH 11/12] Derive base of the contentReference from StructureDefinition.type --- .../Specification/Snapshot/SnapshotGenerator.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs index d00cb91790..5ea9005feb 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs @@ -887,7 +887,8 @@ private async T.Task mergeElement(ElementDefinitionNavigator snap, ElementDefini // [MS 20220425] Make sure newly added contentReferences (from bases, types, and contentReferences) are absolute. // Except when the it's a core profile (Derivation is not a Constraint) if (snap.StructureDefinition.Derivation == StructureDefinition.TypeDerivationRule.Constraint) - ensureAbsoluteContentReferences(snap, snap.StructureDefinition.BaseDefinition); + await ensureAbsoluteContentReferences(snap, snap.StructureDefinition.Type).ConfigureAwait(false); + // [WMR 20160720] NEW // generate [...]extension.url/fixedUri if missing @@ -898,7 +899,7 @@ private async T.Task mergeElement(ElementDefinitionNavigator snap, ElementDefini } } - private static void ensureAbsoluteContentReferences(ElementDefinitionNavigator nav, string baseTypeUrl) + private async T.Task ensureAbsoluteContentReferences(ElementDefinitionNavigator nav, string baseTypeUrl) { var bookmark = nav.Bookmark(); @@ -907,7 +908,7 @@ private static void ensureAbsoluteContentReferences(ElementDefinitionNavigator n do { if (!string.IsNullOrEmpty(nav.Current?.ContentReference)) - ensureAbsoluteContentReference(nav.Current?.ContentReferenceElement, baseTypeUrl); + await ensureAbsoluteContentReference(nav.Current?.ContentReferenceElement, baseTypeUrl).ConfigureAwait(false); } while (nav.MoveToNext()); } @@ -915,15 +916,20 @@ private static void ensureAbsoluteContentReferences(ElementDefinitionNavigator n nav.ReturnToBookmark(bookmark); } - private static void ensureAbsoluteContentReference(FhirUri contentReferenceElement, string baseTypeUrl) + private async T.Task ensureAbsoluteContentReference(FhirUri contentReferenceElement, string baseTypeUrl) { if (contentReferenceElement.Value?.StartsWith("#") == true) { - var contentRefBase = baseTypeUrl.StartsWith("http://") ? baseTypeUrl : ModelInfo.FhirCoreProfileBaseUri.ToString() + baseTypeUrl; + string contentRefBase = baseTypeUrl.StartsWith("http://") ? baseTypeUrl : await getCanonicalUrlFromCoreType(baseTypeUrl).ConfigureAwait(false); contentReferenceElement.Value = contentRefBase + contentReferenceElement.Value; } } + private async T.Task getCanonicalUrlFromCoreType(string baseTypeUrl) + { + var coreType = await AsyncResolver.FindStructureDefinitionForCoreTypeAsync(baseTypeUrl).ConfigureAwait(false); + return coreType.Url; + } // [WMR 20170105] New: determine wether to expand the current element From f54faa84abd57040b6d3ccd67c6726c9fed5c0c0 Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Tue, 26 Apr 2022 11:32:20 +0200 Subject: [PATCH 12/12] remove comment --- .../Snapshot/SnapshotGeneratorTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index 159e54516f..b3392920f5 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -7662,7 +7662,6 @@ public async T.Task TestAbsoluteContentReferenceGeneration() var cref2 = profileSnapshot.Where(e => e.ElementId == "Questionnaire.item:booleanItem.item.item").FirstOrDefault(); cref2.ContentReference.Should().Be("http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"); - // profileSnapshot.Should().Contain(e => e.ContentReference == "http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"); } [TestMethod]