// [WMR 20170209] TODO: Merge global mapping components #if false /// <summary>Merge two lists of global <see cref="StructureDefinition.MappingComponent"/> definitions.</summary> public static List<StructureDefinition.MappingComponent> Merge(SnapshotGenerator generator, List<StructureDefinition.MappingComponent> snap, List<StructureDefinition.MappingComponent> diff) { var merger = new ElementDefnMerger(generator); // Merge global mapping definitions having the same (unique) mapping id return merger.mergeCollection(snap, diff, (a, b) => a.Identity == b.Identity); }
// Match current snapshot and differential extension slice elements on extension type profile // Returns an initialized MatchInfo with action = Merge | Add static void matchExtensionSlice(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, List <string> discriminators, MatchInfo match) { // [WMR 20170110] Accept missing slicing component, e.g. to close the extension slice: Extension.extension { max = 0 } // if (discriminators == null || discriminators.Count > 1 || discriminators.FirstOrDefault() != "url") if (discriminators != null && (discriminators.Count != 1 || discriminators.FirstOrDefault() != "url")) { // Invalid extension discriminator; generate issue and ignore Debug.WriteLine($"[{nameof(ElementMatcher)}.{nameof(matchExtensionSlice)}] Warning! Invalid discriminator for extension slice (path = '{diffNav.Path}') - must be 'url'."); match.Issue = SnapshotGenerator.CreateIssueInvalidExtensionSlicingDiscriminator(diffNav.Current); } // Ignore the specified discriminator, always match on url var snapExtensionUri = getExtensionProfileUri(snapNav.Current); var diffExtensionUri = getExtensionProfileUri(diffNav.Current); // if (snapExtensionUri == diffExtensionUri) if (StringComparer.Ordinal.Equals(snapExtensionUri, diffExtensionUri)) { match.BaseBookmark = snapNav.Bookmark(); match.Action = MatchAction.Merge; } else { match.Action = MatchAction.Add; } }
// Match current snapshot and differential slice elements on @type = Element.Type.Code // Returns an initialized MatchInfo with action = Merge | Add // defaultBase represents the base element for newly introduced slices static MatchInfo matchSliceByTypeCode(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, Bookmark defaultBase) { var match = new MatchInfo() { DiffBookmark = diffNav.Bookmark() }; var diffTypeCodes = diffNav.Current.Type.Select(t => t.Code).ToList(); if (diffTypeCodes.Count == 0) { Debug.Print($"[{nameof(ElementMatcher)}.{nameof(matchSliceByTypeCode)}] Error! Element '{diffNav.Path}' is part of a @type slice group, but the element itself has no type."); match.BaseBookmark = defaultBase; match.Action = MatchAction.Invalid; match.Issue = SnapshotGenerator.CreateIssueTypeSliceWithoutType(diffNav.Current); return(match); } var snapTypeCodes = snapNav.Current.Type.Select(t => t.Code); if (snapTypeCodes.SequenceEqual(diffTypeCodes)) { match.BaseBookmark = snapNav.Bookmark(); match.Action = MatchAction.Merge; return(match); } match.BaseBookmark = defaultBase; match.Action = MatchAction.Add; return(match); }
// 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 MatchInfo matchSlice(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, List <string> discriminators, Bookmark defaultBase) { Debug.Assert(diffNav.Current.Slicing == null); // Caller must handle reslicing var match = new MatchInfo() { DiffBookmark = diffNav.Bookmark() }; // 1. If the diff slice has a name, than match base slice by name var diffSliceName = diffNav.Current.Name; if (!string.IsNullOrEmpty(diffSliceName)) { // if (snapNav.PathName == diffSliceName) if (StringComparer.Ordinal.Equals(snapNav.Current.Name, diffSliceName)) { match.BaseBookmark = snapNav.Bookmark(); match.Action = MatchAction.Merge; return(match); } else { match.BaseBookmark = defaultBase; match.Action = MatchAction.Add; } return(match); } // Slice has no name // Allowed for: // - Extensions => discriminator = url // - type slices => discriminator = @type / @profile if (diffNav.Current.IsExtension()) { // Discriminator = url => match on ElementDefinition.Type[0].Profile return(matchExtensionSlice(snapNav, diffNav, discriminators, defaultBase)); } else if (discriminators.Count == 1 && isTypeDiscriminator(discriminators[0])) { // Discriminator = @type => match on ElementDefinition.Type[0].Code return(matchSliceByTypeCode(snapNav, diffNav, defaultBase)); } else if (isTypeProfileDiscriminator(discriminators)) { // Discriminator = type@profile, { @type, @profile } return(matchSliceByTypeProfile(snapNav, diffNav, defaultBase)); } // Error! Unsupported discriminator => slices must be named match.BaseBookmark = defaultBase; match.Action = MatchAction.Invalid; match.Issue = SnapshotGenerator.CreateIssueSliceWithoutName(diffNav.Current); return(match); }
// [WMR 20160801] NEW // Try to find matching slice element in base profile // Assume snapNav is positioned on slicing entry node // Assume diffNav is positioned on a resliced element node // Returns true when match is found, matchingSlice points to match in base (merge here) // Returns false otherwise, matchingSlice points to current node in base // Maintain snapNav current position private static bool FindBaseSlice(ElementNavigator snapNav, ElementNavigator diffNav, out Bookmark matchingSlice) { var slicing = snapNav.Current.Slicing; Debug.Assert(slicing != null); var slicingIntro = matchingSlice = snapNav.Bookmark(); var result = false; // url, type@profile, @type + @profile if (IsTypeProfileDiscriminator(slicing.Discriminator)) { // [WMR 20160802] Handle complex extension constraints // e.g. sdc-questionnaire, Path = 'Questionnaire.group.question.extension.extension', name = 'question' // Type.Profile = 'http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen#question' // snapNav has already expanded target extension definition 'questionnaire-enableWhen' // => Match to base profile on child element with name 'question' var diffProfiles = diffNav.Current.Type.FirstOrDefault().Profile.ToArray(); if (diffProfiles == null || diffProfiles.Length == 0) { throw Error.InvalidOperation("Differential is reslicing on url, but resliced element has no type profile (path = '{0}').", diffNav.Path); } if (diffProfiles.Length > 1) { throw Error.NotSupported("Cannot expand snapshot. Reslicing on complex discriminator is not supported (path = '{0}').", diffNav.Path); } var diffProfile = diffProfiles.FirstOrDefault(); string profileUrl, elementName; var isComplex = SnapshotGenerator.IsComplexProfileReference(diffProfile, out profileUrl, out elementName); while (snapNav.MoveToNext(snapNav.PathName)) { var baseProfiles = snapNav.Current.Type.FirstOrDefault().Profile; result = isComplex // Match on element name ? snapNav.Current.Name == elementName // Match on profile(s) : baseProfiles.SequenceEqual(diffProfiles); if (result) { matchingSlice = snapNav.Bookmark(); break; } } } // TODO: Support other discriminators else { throw Error.NotSupported("Cannot expand snapshot. Reslicing on discriminator '{0}' is not supported yet (path = '{1}').", string.Join("|", slicing.Discriminator), snapNav.Path); } snapNav.ReturnToBookmark(slicingIntro); return(result); }
private static void generateSnapshotAndCompare(StructureDefinition original, ArtifactResolver source) { var generator = new SnapshotGenerator(source, markChanges: false); var expanded = (StructureDefinition)original.DeepCopy(); Assert.IsTrue(original.IsExactly(expanded)); generator.Generate(expanded); var areEqual = original.IsExactly(expanded); if (!areEqual) { File.WriteAllText("c:\\temp\\snapshotgen-source.xml", FhirSerializer.SerializeResourceToXml(original)); File.WriteAllText("c:\\temp\\snapshotgen-dest.xml", FhirSerializer.SerializeResourceToXml(expanded)); } Assert.IsTrue(areEqual); }
ElementDefnMerger(SnapshotGenerator generator) { _generator = generator; }
/// <summary>Merge two <see cref="ElementDefinition"/> instances. Existing diff properties override associated snap properties.</summary> public static void Merge(SnapshotGenerator generator, ElementDefinition snap, ElementDefinition diff, bool mergeElementId) { var merger = new ElementDefnMerger(generator); merger.merge(snap, diff, mergeElementId); }
/// <summary> /// Creates matches between the elements pointed to by snapNav and diffNav. After returning, both /// navs will be located on the last element that was matched (e.g. in a slicing group) /// </summary> /// <param name="snapNav"></param> /// <param name="diffNav"></param> /// <returns></returns> static List <MatchInfo> constructMatch(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav) { // [WMR 20160802] snapNav and diffNav point to matching elements // Determine the associated action (Add, Merge, Slice) // If this represents a slice, then also process all the slice elements // Note that in case of a slice, both snapNav and diffNav will always point to the first element in the slice group // [WMR 20160801] Only emit slicing entry for actual extension elements (with extension profile url) // Do not emit slicing entry for abstract Extension base element definition (inherited from external profiles) // [WMR 20160906] WRONG! Also need to handle complex extensions var diffIsExtension = diffNav.Current.IsExtension() && ( diffNav.Current.PrimaryTypeProfile() != null || // Extension element in a profile ElementDefinitionNavigator.GetPathRoot(diffNav.Path) == "Extension" // Complex extension child element ); bool baseIsSliced = snapNav.Current.Slicing != null; bool diffIsSliced = diffIsExtension || diffNav.Current.Slicing != null; if (baseIsSliced || diffIsSliced) { // This is a slice match - process it separately return(constructSliceMatch(snapNav, diffNav)); } var match = new MatchInfo() { Action = MatchAction.Merge, BaseBookmark = snapNav.Bookmark(), DiffBookmark = diffNav.Bookmark() }; // Verify type slice constraints (e.g. value[x] => valueString) // - base element has a type choice (value[x]) // - Single derived element is constrained to single type and renamed (valueString) // - This is NOT a type slice => derived profile cannot contain multiple renamed elements! var result = new List <MatchInfo>() { match }; if (snapNav.Current.IsChoice()) { var bm = diffNav.Bookmark(); // [WMR 20170308] NEW - Clone slice base element var sliceBase = initSliceBase(snapNav); while (diffNav.MoveToNext()) { if (snapNav.IsRenamedChoiceTypeElement(diffNav.PathName)) { match = new MatchInfo() { #if MULTIPLE_RENAMED_CHOICE_TYPES Action = MatchAction.Add, #else Action = MatchAction.Invalid, #endif BaseBookmark = snapNav.Bookmark(), DiffBookmark = diffNav.Bookmark(), // [WMR 20170308] NEW - Slice base element SliceBase = sliceBase, Issue = SnapshotGenerator.CreateIssueInvalidChoiceConstraint(diffNav.Current) }; result.Add(match); bm = diffNav.Bookmark(); } else { diffNav.ReturnToBookmark(bm); break; } } } return(result); }