// [WMR 20160720] NEW private void fixExtensionUrl(ElementNavigator nav) { var extElem = nav.Current; if (extElem.IsExtension() && nav.HasChildren) { // Resolve the canonical url of the extension definition from type[0]/profile[0] var primaryType = extElem.Type.FirstOrDefault(); if (primaryType != null) { var profile = primaryType.Profile.FirstOrDefault(); if (profile != null) { var snapExtPos = nav.Bookmark(); try { if (nav.MoveToChild("url")) { var urlElem = nav.Current; if (urlElem != null && urlElem.Fixed == null) { urlElem.Fixed = new FhirUri(profile); } } } finally { nav.ReturnToBookmark(snapExtPos); } } } } }
/// <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> private static List <MatchInfo> constructMatch(ElementNavigator snapNav, ElementNavigator 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 var match = new MatchInfo() { BaseBookmark = snapNav.Bookmark(), DiffBookmark = diffNav.Bookmark() }; bool baseIsSliced = snapNav.Current.Slicing != null; // [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) // bool diffIsExtension = diffNav.Current.IsExtension(); bool diffIsExtension = diffNav.Current.IsMappedExtension(); var nextDiffChildName = nextChildName(diffNav); bool diffIsSliced = diffIsExtension || nextDiffChildName == diffNav.PathName; bool diffIsTypeSlice = snapNav.Current.IsChoice() && snapNav.IsCandidateTypeSlice(diffNav.PathName); if (diffIsTypeSlice) { if (baseIsSliced) { // TODO...? throw Error.NotSupported("Cannot expand snapshot. Reslicing type slices is not yet supported (path = '{0}').", diffNav.Path); } // Only a single type slice? Then merge // e.g. base:value[x] <=> diff:valueString if (!snapNav.IsCandidateTypeSlice(nextDiffChildName)) { match.Action = MatchAction.Merge; return(new List <MatchInfo>() { match }); } // Multiple type slices // e.g. base:value[x] <=> diff:valueString + diff:valueBoolean return(constructTypeSliceMatch(snapNav, diffNav)); } else if (baseIsSliced || diffIsSliced) { // This is a slice match - process it separately return(constructSliceMatch(snapNav, diffNav)); } else { // Easiest case - one to one match, without slicing involved match.Action = MatchAction.Merge; return(new List <MatchInfo>() { match }); } }
private static int countChildNameRepeats(ElementNavigator diff) { //TODO: We use this function to determine whether an element is sliced...doing this by counting repeats of elements //in the diff. However, when reslicing, the diff doesn't need to have repeating elements, and you have to derive from the //base (snapshot) that the element is sliced. var repeats = 1; var currentPath = diff.PathName; var bm = diff.Bookmark(); while (diff.MoveToNext()) { // check whether the next sibling in the differential has the same name, // that means we're looking at a slice if (diff.PathName == currentPath) { repeats++; } else { break; } } diff.ReturnToBookmark(bm); return(repeats); }
private void makeSlice(ElementNavigator snap, ElementNavigator diff) { // diff is now located at the first repeat of a slice, which is normally the slice entry (Extension slices need // not have a slicing entry) // snap is located at the base definition of the element that will become sliced. But snap is not yet sliced. // Before we start, is the base element sliceable? if (!snap.Current.IsRepeating() && !snap.Current.IsChoice()) { throw Error.InvalidOperation("The slicing entry in the differential at {0} indicates an slice, but the base element is not a repeating or choice element", diff.Current.Path); } ElementDefinition slicingEntry = diff.Current; //Mmmm....no slicing entry in the differential. This is only alloweable for extension slices, as a shorthand notation. if (slicingEntry.Slicing == null) { if (slicingEntry.IsExtension()) { // In this case we create a "prefab" extension slice (with just slincing info) // that's simply merged with the original element in base slicingEntry = createExtensionSlicingEntry(); } else { throw Error.InvalidOperation("The slice group at {0} does not start with a slice entry element", diff.Current.Path); } } (new ElementDefnMerger(_markChanges)).Merge(snap.Current, slicingEntry); ////TODO: update / check the slice entry's min/max property to match what we've found in the slice group }
private void mergeElement(ElementNavigator snap, ElementNavigator diff) { (new ElementDefnMerger(_markChanges)).Merge(snap.Current, diff.Current); if (diff.HasChildren) { if (!snap.HasChildren) { // The differential moves into an element that has no children in the base. // This is allowable if the base's element has a nameReference or a TypeRef, // in which case needs to be expanded before we can move to the path indicated // by the differential // Note that since we merged the parent, a (shorthand) typeslice will already // have reduced the numer of types to 1. Still, if you don't do that, we cannot // accept constraints on children, need to select a single type first... if (snap.Current.Type.Count > 1) { throw new NotSupportedException("Differential has a constraint on a choice element {0}, but does so without using a type slice".FormatWith(diff.Path)); } ExpandElement(snap, _resolver); if (!snap.HasChildren) { // Snapshot's element turns out not to be expandable, so we can't move to the desired path throw Error.InvalidOperation("Differential has nested constraints for node {0}, but this is a leaf node in base", diff.Path); } } // Now, recursively merge the children merge(snap, diff); } }
/// <summary> /// Creates a differential structure with all "skipped" parents filled in. /// </summary> /// <param name="differential"></param> /// <returns>The full tree structure representing the differential</returns> /// <remarks>This operation will not touch the source differential, but instead will return a new structure.</remarks> public Profile.ConstraintComponent MakeTree() { var diff = (Profile.ConstraintComponent)_source.DeepCopy(); // We're going to modify the differential if (diff.Element == null || diff.Element.Count == 0) { return(diff); // nothing to do } var index = 0; var elements = diff.Element; while (index < elements.Count) { var thisPath = elements[index].Path; var prevPath = index > 0 ? elements[index - 1].Path : String.Empty; if (thisPath.IndexOf('.') == -1) { // I am a root node, just one segment of path, I need to be the first element if (index != 0) { throw Error.InvalidOperation("Differential has multiple roots"); } // Else, I am fine, proceed index++; } else if (ElementNavigator.IsSibling(thisPath, prevPath) || ElementNavigator.IsDirectChildPath(prevPath, thisPath)) { // The previous path is a sibling, or my direct parent, so everything is alright, proceed to next node index++; } else { var parentPath = ElementNavigator.GetParentPath(thisPath); if (prevPath == String.Empty || !prevPath.StartsWith(parentPath + ".")) { // We're missing a path part, insert an empty parent var parentElement = new Profile.ElementComponent() { Path = parentPath }; elements.Insert(index, parentElement); // Now, we're not sure this parent has parents, so proceed by checking the parent we have just inserted // so -> index is untouched } else { // So, my predecessor an I share ancestry, of which I am sure it has been inserted by this algorithm // before because of my predecessor, so we're fine. index++; } } } return(diff); }
private void merge(ElementNavigator snapNav, ElementNavigator diffNav) { var snapPos = snapNav.Bookmark(); var diffPos = diffNav.Bookmark(); try { var matches = (new ElementMatcher()).Match(snapNav, diffNav); //Debug.WriteLine("Matches for children of {0}".FormatWith(snapNav.Path)); //matches.DumpMatches(snapNav, diffNav); foreach (var match in matches) { if (!snapNav.ReturnToBookmark(match.BaseBookmark)) { throw Error.InvalidOperation("Internal merging error: bookmark {0} in snap is no longer available", match.BaseBookmark); } if (!diffNav.ReturnToBookmark(match.DiffBookmark)) { throw Error.InvalidOperation("Internal merging error: bookmark {0} in diff is no longer available", match.DiffBookmark); } if (match.Action == ElementMatcher.MatchAction.Add) { // TODO: move this logic to matcher, the Add should point to the last slice where // the new slice will be added after. // Find last entry in slice to add to the end var current = snapNav.Path; while (snapNav.Current.Path == current && snapNav.MoveToNext()) { ; } snapNav.MoveToPrevious(); // take one step back... var dest = snapNav.Bookmark(); snapNav.ReturnToBookmark(match.BaseBookmark); snapNav.DuplicateAfter(dest); markChange(snapNav.Current); mergeElement(snapNav, diffNav); snapNav.Current.Slicing = null; // Probably not good enough... } else if (match.Action == ElementMatcher.MatchAction.Merge) { mergeElement(snapNav, diffNav); } else if (match.Action == ElementMatcher.MatchAction.Slice) { makeSlice(snapNav, diffNav); } } } finally { snapNav.ReturnToBookmark(snapPos); diffNav.ReturnToBookmark(diffPos); } }
public void MakeDifferentialTree() { var struc = new Profile.ProfileStructureComponent(); struc.Differential = new Profile.ConstraintComponent(); struc.Differential.Element = new List <Profile.ElementComponent>(); var e = struc.Differential.Element; e.Add(new Profile.ElementComponent() { Path = "A.B.C1" }); e.Add(new Profile.ElementComponent() { Path = "A.B.C1" }); e.Add(new Profile.ElementComponent() { Path = "A.B.C2" }); e.Add(new Profile.ElementComponent() { Path = "A.B" }); e.Add(new Profile.ElementComponent() { Path = "A.B.C1.D" }); e.Add(new Profile.ElementComponent() { Path = "A.D.F" }); var tree = new DifferentialTreeConstructor(struc.Differential).MakeTree(); Assert.IsNotNull(tree); var nav = new ElementNavigator(tree); Assert.AreEqual(10, nav.Count); Assert.IsTrue(nav.MoveToChild("A")); Assert.IsTrue(nav.MoveToChild("B")); Assert.IsTrue(nav.MoveToChild("C1")); Assert.IsTrue(nav.MoveToNext("C1")); Assert.IsTrue(nav.MoveToNext("C2")); Assert.IsTrue(nav.MoveToParent()); // 1st A.B Assert.IsTrue(nav.MoveToNext() && nav.Path == "A.B"); // (now) 2nd A.B Assert.IsTrue(nav.MoveToChild("C1")); Assert.IsTrue(nav.MoveToChild("D")); Assert.IsTrue(nav.MoveToParent()); // A.B.C1 Assert.IsTrue(nav.MoveToParent()); // A.B (2nd) Assert.IsTrue(nav.MoveToNext() && nav.Path == "A.D"); Assert.IsTrue(nav.MoveToChild("F")); }
internal static bool ExpandElement(ElementNavigator nav, ArtifactResolver resolver) { if (resolver == null) { throw Error.ArgumentNull("source"); } if (nav.Current == null) { throw Error.ArgumentNull("Navigator is not positioned on an element"); } if (nav.HasChildren) { return(true); // already has children, we're not doing anything extra } var defn = nav.Current; if (!String.IsNullOrEmpty(defn.NameReference)) { var sourceNav = new ElementNavigator(nav); var success = sourceNav.JumpToNameReference(defn.NameReference); if (!success) { throw Error.InvalidOperation("Trying to navigate down a node that has a nameReference of '{0}', which cannot be found in the StructureDefinition".FormatWith(defn.NameReference)); } nav.CopyChildren(sourceNav); } else if (defn.Type != null && defn.Type.Count > 0) { if (defn.Type.Count > 1) { throw new NotSupportedException("Element at path {0} has a choice of types, cannot expand".FormatWith(nav.Path)); } else { var coreType = resolver.GetStructureDefinitionForCoreType(defn.Type[0].Code.ToString()); if (coreType == null) { throw Error.NotSupported("Trying to navigate down a node that has a declared base type of '{0}', which is unknown".FormatWith(defn.Type[0].Code)); } if (coreType.Snapshot == null) { throw Error.NotSupported("Found definition of base type '{0}', but is does not contain a snapshot representation".FormatWith(defn.Type[0].Code)); } generateBaseElements(coreType.Snapshot.Element); var sourceNav = new ElementNavigator(coreType.Snapshot.Element); sourceNav.MoveToFirstChild(); nav.CopyChildren(sourceNav); } } return(true); }
private void merge(ElementNavigator snapNav, ElementNavigator diffNav) { var snapPos = snapNav.Bookmark(); var diffPos = diffNav.Bookmark(); try { var matches = (new ElementMatcher()).Match(snapNav, diffNav); // Debug.WriteLine("Matches for children of " + snapNav.Path + (snapNav.Current != null && snapNav.Current.Name != null ? " '" + snapNav.Current.Name + "'" : null)); // matches.DumpMatches(snapNav, diffNav); foreach (var match in matches) { if (!snapNav.ReturnToBookmark(match.BaseBookmark)) { throw Error.InvalidOperation("Internal merging error: bookmark '{0}' in snap is no longer available", match.BaseBookmark); } if (!diffNav.ReturnToBookmark(match.DiffBookmark)) { throw Error.InvalidOperation("Internal merging error: bookmark '{0}' in diff is no longer available", match.DiffBookmark); } if (match.Action == ElementMatcher.MatchAction.Add) { // Add new slice after the last existing slice in base profile snapNav.MoveToLastSlice(); var lastSlice = snapNav.Bookmark(); // Initialize slice by duplicating base slice entry snapNav.ReturnToBookmark(match.BaseBookmark); snapNav.DuplicateAfter(lastSlice); // Important: explicitly clear the slicing node in the copy! snapNav.Current.Slicing = null; markChange(snapNav.Current); // Merge differential mergeElement(snapNav, diffNav); } else if (match.Action == ElementMatcher.MatchAction.Merge) { mergeElement(snapNav, diffNav); } else if (match.Action == ElementMatcher.MatchAction.Slice) { makeSlice(snapNav, diffNav); } } } finally { snapNav.ReturnToBookmark(snapPos); diffNav.ReturnToBookmark(diffPos); } }
// [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); }
/// <summary> /// Will match up the children of the current element in diffNav to the children of the element in snapNav. /// </summary> /// <param name="snapNav"></param> /// <param name="diffNav"></param> /// <returns>Returns a list of Bookmark combinations, the first bookmark pointing to an element in the base, /// the second a bookmark in the diff that matches the bookmark in the base.</returns> /// <remarks>Will match slices to base elements, re-sliced slices to slices and type-slice shorthands to choie elements. /// Note that this function may expand snapNav when it encounters paths in the differential that move into the complex types /// of one of snap's elements. (NO NEED, it just has to match direct children, not deeper) /// This function assumes the differential is not sparse: it must have parent nodes for all child constraint paths. /// </remarks> public List <MatchInfo> Match(ElementNavigator snapNav, ElementNavigator diffNav) { if (!snapNav.HasChildren) { throw Error.Argument("snapNav", "Cannot match base to diff: element '{0}' in snap has no children".FormatWith(snapNav.PathName)); } if (!diffNav.HasChildren) { throw Error.Argument("diffNav", "Cannot match base to diff: element '{0}' in diff has no children".FormatWith(diffNav.PathName)); } // These bookmarks are used only in the finally {} to make sure we don't alter the position of the navs when leaving the merger var baseStartBM = snapNav.Bookmark(); var diffStartBM = diffNav.Bookmark(); snapNav.MoveToFirstChild(); diffNav.MoveToFirstChild(); var choiceNames = listChoiceElements(snapNav); var result = new List <MatchInfo>(); try { do { // First, match directly -> try to find the child in base with the same name as the path in the diff if (snapNav.PathName != diffNav.PathName && !snapNav.MoveToNext(diffNav.PathName)) { // Not found, maybe this is a type slice shorthand, look if we have a matching choice prefix in snap var typeSliceShorthand = diffNav.PathName; // Try to match nameXXXXX to name[x] var matchingChoice = choiceNames.SingleOrDefault(prefix => isPossibleTypeSlice(prefix, typeSliceShorthand)); if (matchingChoice != null) { snapNav.MoveToNext(matchingChoice); } else { throw Error.InvalidOperation("Differential has a constraint for path '{0}', which does not exist in its base".FormatWith(diffNav.Path)); } } result.AddRange(constructMatch(snapNav, diffNav)); }while (diffNav.MoveToNext()); } finally { snapNav.ReturnToBookmark(baseStartBM); diffNav.ReturnToBookmark(diffStartBM); } return(result); }
private void expandBaseElement(ElementNavigator snap, ElementNavigator diff) { snap.ExpandElement(_resolver); if (!snap.HasChildren) { // Snapshot's element turns out not to be expandable, so we can't move to the desired path throw Error.InvalidOperation("Differential has nested constraints for node {0}, but this is a leaf node in base", diff.Path); } }
public void MakeDifferentialTree() { var e = new List <ElementDefinition>(); e.Add(new ElementDefinition() { Path = "A.B.C1" }); e.Add(new ElementDefinition() { Path = "A.B.C1" }); e.Add(new ElementDefinition() { Path = "A.B.C2" }); e.Add(new ElementDefinition() { Path = "A.B" }); e.Add(new ElementDefinition() { Path = "A.B.C1.D" }); e.Add(new ElementDefinition() { Path = "A.D.F" }); var tree = new DifferentialTreeConstructor(e).MakeTree(); Assert.IsNotNull(tree); var nav = new ElementNavigator(tree); Assert.AreEqual(10, nav.Count); Assert.IsTrue(nav.MoveToChild("A")); Assert.IsTrue(nav.MoveToChild("B")); Assert.IsTrue(nav.MoveToChild("C1")); Assert.IsTrue(nav.MoveToNext("C1")); Assert.IsTrue(nav.MoveToNext("C2")); Assert.IsTrue(nav.MoveToParent()); // 1st A.B Assert.IsTrue(nav.MoveToNext() && nav.Path == "A.B"); // (now) 2nd A.B Assert.IsTrue(nav.MoveToChild("C1")); Assert.IsTrue(nav.MoveToChild("D")); Assert.IsTrue(nav.MoveToParent()); // A.B.C1 Assert.IsTrue(nav.MoveToParent()); // A.B (2nd) Assert.IsTrue(nav.MoveToNext() && nav.Path == "A.D"); Assert.IsTrue(nav.MoveToChild("F")); }
public void CopyChildTree() { var dest = createTestNav(); var struc = new StructureDefinition(); struc.Snapshot = new StructureDefinition.SnapshotComponent(); struc.Snapshot.Element = new List <ElementDefinition>(); var e = struc.Snapshot.Element; e.Add(new ElementDefinition() { Path = "X" }); e.Add(new ElementDefinition() { Path = "X.Y1" }); e.Add(new ElementDefinition() { Path = "X.Y2" }); e.Add(new ElementDefinition() { Path = "X.Y2.Z1" }); e.Add(new ElementDefinition() { Path = "X.Y2.Z2" }); var source = new ElementNavigator(e); Assert.IsTrue(dest.JumpToFirst("A.D")); var dstPos = dest.OrdinalPosition; source.MoveToFirstChild(); var srcPos = source.OrdinalPosition; Assert.IsTrue(dest.CopyChildren(source)); Assert.AreEqual(srcPos, source.OrdinalPosition, "source did not remain on original position"); Assert.AreEqual(dstPos, dest.OrdinalPosition, "dest did not remain on original position"); Assert.IsTrue(dest.MoveToFirstChild()); Assert.AreEqual("Y1", dest.PathName); Assert.IsTrue(dest.MoveToNext()); Assert.AreEqual("Y2", dest.PathName); Assert.IsFalse(dest.MoveToNext()); Assert.IsTrue(dest.MoveToFirstChild()); Assert.AreEqual("Z1", dest.PathName); Assert.IsTrue(dest.MoveToNext()); Assert.AreEqual("Z2", dest.PathName); Assert.IsFalse(dest.MoveToNext()); }
private static string nextChildName(ElementNavigator nav) { string result = null; if (nav.MoveToNext()) { result = nav.PathName; nav.MoveToPrevious(); } return(result); }
private void merge(ElementNavigator snap, ElementNavigator diff) { (new ElementDefnMerger(_markChanges)).Merge(snap.Current, diff.Current); // If there are children, move into them, and recursively merge them if (diff.MoveToFirstChild()) { if (!snap.HasChildren) { // The differential moves into an element that has no children in the base. // This is allowable if the base's element has a nameReference or a TypeRef, // in which case needs to be expanded before we can move to the path indicated // by the differential if (snap.Current.Type.Count > 1) { throw new NotSupportedException("Differential has a constraint on a choice element {0}, but does so without using a type slice".FormatWith(diff.Path)); } expandBaseElement(snap, diff); } // Due to how MoveToFirstChild() works, we have to move to the first matching *child* // when entering the loop for the first time, after that we can look for the next // matching *sibling*. bool firstEntry = true; do { if ((firstEntry && !snap.MoveToChild(diff.PathName)) || (!firstEntry && !snap.MoveTo(diff.PathName))) // HACK: I don't think it should be allowed for a diff to list constraints in the wrong order... { throw Error.InvalidOperation("Differential has a constraint for path '{0}', which does not exist in its base", diff.Path); } firstEntry = false; // Child found in both, merge them if (countChildNameRepeats(diff) > 1 || diff.Current.IsExtension()) { // The child in the diff repeats or we recognize it as an extension slice -> we're on the first element of a slice! mergeSlice(snap, diff); } else { merge(snap, diff); } }while (diff.MoveToNext()); // After the merge, return the diff and snapho back to their original position diff.MoveToParent(); snap.MoveToParent(); } }
public XElement generateStructureTable(Profile.ProfileStructureComponent structure, bool diff, Profile profile) { HierarchicalTableGenerator gen = new HierarchicalTableGenerator(_pkp); var model = TableModel.CreateNormalTable(); // List<Profile.ElementComponent> list = diff ? structure.getDifferential().getElement() : structure.getSnapshot().getElement(); DSTU2 var list = structure.Element; var nav = new ElementNavigator(structure); nav.MoveToFirstChild(); genElement(gen, model.Rows, nav, profile, true); return(gen.generate(model)); }
private static List <MatchInfo> constructSliceMatch(ElementNavigator snapNav, ElementNavigator diffNav) { var result = new List <MatchInfo>(); var bm = snapNav.Bookmark(); var diffName = diffNav.PathName; bool baseIsSliced = snapNav.Current.Slicing != null; bool diffIsSliced = diffNav.Current.IsExtension() || nextChildName(diffNav) == diffNav.PathName; if (baseIsSliced) { throw Error.NotSupported("Cannot yet handle re-slicing found at diff {0}".FormatWith(diffNav.Path)); } // For the first entries with explicit slicing information (or implicit if this is an extension), // generate a match between the base's unsliced element and the first entry in the diff if (diffNav.Current.Slicing != null || diffNav.Current.IsExtension()) { // Differential has information for the slicing entry result.Add(new MatchInfo() { BaseBookmark = bm, DiffBookmark = diffNav.Bookmark(), Action = MatchAction.Slice }); if (!diffNav.Current.IsExtension()) { if (!diffNav.MoveToNext()) { throw Error.InvalidOperation("Differential has a slicing entry {0}, but no first actual slice", diffNav.Path); } } } // Then, generate a match between the base's unsliced element and the slicing entries in the diff // Note that the first entry may serve a double role and have to result matches (one for the constraints, one as a slicing entry) do { result.Add(new MatchInfo() { BaseBookmark = bm, DiffBookmark = diffNav.Bookmark(), Action = MatchAction.Add }); } while (nextChildName(diffNav) == diffName && diffNav.MoveToNext()); // Warning: Subtle use of short-cut evaluation return(result); }
public void TestExpandChild() { var loader = new StructureLoader(ArtifactResolver.CreateDefault()); var profStruct = loader.Locate(new Uri("http://hl7.org/fhir/Profile/Profile"), new Code("Profile")); var nav = new ElementNavigator(profStruct); nav.JumpToFirst("Profile.telecom"); Assert.IsTrue(nav.ExpandElement(loader)); Assert.IsTrue(nav.MoveToChild("period")); nav.JumpToFirst("Profile.extensionDefn.definition"); Assert.IsTrue(nav.ExpandElement(loader)); Assert.IsTrue(nav.MoveToChild("max")); }
public void TestExpandChild() { var loader = new StructureLoader(ArtifactResolver.CreateDefault()); var profStruct = loader.LocateStructure(new Uri("http://hl7.org/fhir/Profile/Profile"), new Code("Profile")); var nav = new ElementNavigator(profStruct.Snapshot); nav.JumpToFirst("Profile.telecom"); Assert.IsTrue(nav.ExpandElement(loader)); Assert.IsTrue(nav.MoveToChild("period")); nav.JumpToFirst("Profile.structure.differential"); Assert.IsTrue(nav.ExpandElement(loader)); Assert.IsTrue(nav.MoveToChild("element")); }
public XElement generateStructureTable(String defFile, Profile.ProfileStructureComponent structure, bool diff, String imageFolder, bool inlineGraphics, Profile profile, string profileUrl, String profileBaseFileName) { HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics); TableModel model = gen.initNormalTable(); // List<Profile.ElementComponent> list = diff ? structure.getDifferential().getElement() : structure.getSnapshot().getElement(); DSTU2 var list = structure.Element; var nav = new ElementNavigator(structure); nav.MoveToFirstChild(); genElement(defFile == null ? null : defFile + "#" + structure.Name + ".", gen, model.getRows(), nav, profile, diff, profileUrl, profileBaseFileName); return(gen.generate(model)); }
public void TestModificationResilience() { var nav = createTestNav(); Assert.IsTrue(nav.JumpToFirst("A.D")); var nav2 = new ElementNavigator(nav); // Delete A.D in nav Assert.IsTrue(nav.Delete()); // Should still be there in nav2 Assert.IsFalse(nav.JumpToFirst("A.D")); Assert.IsTrue(nav2.JumpToFirst("A.D")); }
private void mergeElement(ElementNavigator snap, ElementNavigator diff) { if (_settings.ExpandTypeProfiles) { ExpandTypeProfiles(snap, diff); } // [WMR 20160720] Changed, use SnapshotGeneratorSettings // (new ElementDefnMerger(_markChanges)).Merge(snap.Current, diff.Current); (new ElementDefnMerger(_settings.MarkChanges)).Merge(snap.Current, diff.Current); if (diff.HasChildren) { if (!snap.HasChildren) { // The differential moves into an element that has no children in the base. // This is allowable if the base's element has a nameReference or a TypeRef, // in which case needs to be expanded before we can move to the path indicated // by the differential // Note that since we merged the parent, a (shorthand) typeslice will already // have reduced the numer of types to 1. Still, if you don't do that, we cannot // accept constraints on children, need to select a single type first... if (snap.Current.Type.Count > 1) { throw Error.InvalidOperation("Differential has a constraint on a choice element '{0}', but does so without using a type slice", diff.Path); } expandElement(snap, _resolver, _settings); if (!snap.HasChildren) { // Snapshot's element turns out not to be expandable, so we can't move to the desired path throw Error.InvalidOperation("Differential has nested constraints for node '{0}', but this is a leaf node in base", diff.Path); } } // Now, recursively merge the children merge(snap, diff); // [WMR 20160720] NEW // generate [...]extension.url/fixedUri if missing // Ewout: [...]extension.url may be missing from differential // Information is redundant (same value as [...]extension/type/profile) // => snapshot generator should add this fixExtensionUrl(snap); } }
public void Generate(StructureDefinition structure) { if (structure.Differential == null) { throw Error.Argument("structure", "structure does not contain a differential specification"); } // [WMR 20160718] Also accept extension definitions (IsConstraint == false) if (!structure.IsConstraint && !structure.IsExtension) { throw Error.Argument("structure", "structure is not a constraint or extension"); } if (structure.Base == null) { throw Error.Argument("structure", "structure is a constraint, but no base has been specified"); } var differential = structure.Differential; var baseStructure = _resolver.GetStructureDefinition(structure.Base); if (baseStructure == null) { throw Error.InvalidOperation("Could not locate the base StructureDefinition for url " + structure.Base); } if (baseStructure.Snapshot == null) { throw Error.InvalidOperation("Snapshot generator required the base at '{0}' to have a snapshot representation", structure.Base); } var snapshot = (StructureDefinition.SnapshotComponent)baseStructure.Snapshot.DeepCopy(); generateBaseElements(snapshot.Element); var snapNav = new ElementNavigator(snapshot.Element); // Fill out the gaps (mostly missing parents) in the differential representation var fullDifferential = new DifferentialTreeConstructor(differential.Element).MakeTree(); var diffNav = new ElementNavigator(fullDifferential); merge(snapNav, diffNav); structure.Snapshot = new StructureDefinition.SnapshotComponent() { Element = snapNav.ToListOfElements() }; }
//private static void mergeStructure(Profile.ConstraintComponent snapshot, Profile.ConstraintComponent differential) //{ // if (differential.Name != null) snapshot.Name = differential.Name; // if (differential.Publish != null) snapshot.Publish = differential.Publish; // if (differential.Purpose != null) snapshot.Purpose = differential.Purpose; //} private void merge(ElementNavigator snap, ElementNavigator diff) { mergeElementAttributes(snap.Current, diff.Current); // If there are children, move into them, and recursively merge them if (diff.MoveToFirstChild()) { if (!snap.HasChildren) { // The differential moves into an element that has no children in the base. // This is allowable if the base's element has a nameReference or a TypeRef, // in which case needs to be expanded before we can move to the path indicated // by the differential expandBaseElement(snap, diff); } // Due to how MoveToFirstChild() works, we have to move to the first matching *child* // when entering the loop for the first time, after that we can look for the next // matching *sibling*. bool firstEntry = true; do { if ((firstEntry && !snap.MoveToChild(diff.PathName)) || (!firstEntry && !snap.MoveToNext(diff.PathName))) { throw Error.InvalidOperation("Differential has a constraint for path '{0}', which does not exist in its base", diff.PathName); } firstEntry = false; // Child found in both, merge them if (countChildNameRepeats(diff) > 1 || diff.Current.IsExtension()) { // The child in the diff repeats or we recognize it as an extension slice -> we're on the first element of a slice! mergeSlice(snap, diff); } else { merge(snap, diff); } }while (diff.MoveToNext()); // After the merge, return the diff and snapho back to their original position diff.MoveToParent(); snap.MoveToParent(); } }
/// <summary> /// List all names of nodes in the current navigator that are choice ('[x]') elements /// </summary> /// <param name="snapNav"></param> /// <returns></returns> private List <string> listChoiceElements(ElementNavigator snapNav) { var bm = snapNav.Bookmark(); var result = new List <string>(); do { if (snapNav.Current.IsChoice()) { result.Add(snapNav.PathName); } } while (snapNav.MoveToNext()); snapNav.ReturnToBookmark(bm); return(result); }
public void TestExpandChild() { var qStructDef = _source.GetStructureDefinition("http://hl7.org/fhir/StructureDefinition/Questionnaire"); Assert.IsNotNull(qStructDef); Assert.IsNotNull(qStructDef.Snapshot); var nav = new ElementNavigator(qStructDef.Snapshot.Element); nav.JumpToFirst("Questionnaire.telecom"); Assert.IsTrue(nav.ExpandElement(_source)); Assert.IsTrue(nav.MoveToChild("period"), "Did not move into complex datatype ContactPoint"); nav.JumpToFirst("Questionnaire.group"); Assert.IsTrue(nav.ExpandElement(_source)); Assert.IsTrue(nav.MoveToChild("title"), "Did not move into internally defined backbone element Group"); }
public void Expand(Profile.ProfileStructureComponent structure) { if (structure.Differential == null) { throw Error.Argument("structure", "structure does not contain a differential specification"); } var differential = structure.Differential; var baseStructure = _loader.LocateBaseStructure(structure.TypeElement); if (baseStructure == null) { throw Error.InvalidOperation("Could not locate the base profile for type {0}", structure.TypeElement.ToString()); } if (baseStructure.Snapshot == null) { throw Error.InvalidOperation("Base definition to use for expansion lacks a snapshot representation"); } // var baseUri = StructureLoader.BuildBaseStructureUri(structure.TypeElement).ToString(); var snapshot = (Profile.ConstraintComponent)baseStructure.Snapshot.DeepCopy(); //DSTU1 //snapshot.SetStructureForm(StructureForm.Snapshot); //snapshot.SetStructureBaseUri(baseUri.ToString()); //mergeStructure(snapshot, differential); var fullDifferential = new DifferentialTreeConstructor(differential).MakeTree(); var snapNav = new ElementNavigator(snapshot); snapNav.MoveToFirstChild(); var diffNav = new ElementNavigator(fullDifferential); diffNav.MoveToFirstChild(); merge(snapNav, diffNav); //TODO: Merge search params? snapNav.CommitChanges(); structure.Snapshot = snapNav.Elements; }
/// <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> private static List <MatchInfo> constructMatch(ElementNavigator snapNav, ElementNavigator diffNav) { var match = new MatchInfo() { BaseBookmark = snapNav.Bookmark(), DiffBookmark = diffNav.Bookmark() }; bool baseIsSliced = snapNav.Current.Slicing != null; bool diffIsSliced = diffNav.Current.IsExtension() || nextChildName(diffNav) == diffNav.PathName; // Easiest case - one to one match, without slicing involved if (!baseIsSliced && !diffIsSliced) { match.Action = MatchAction.Merge; return(new List <MatchInfo>() { match }); } // Check whether this is a type-slice shorthand - only the most common usecase // is supported for this case, otherwise us a normal type-slice // See also gForge issues #8974 and #8973 if (isPossibleTypeSlice(snapNav.PathName, diffNav.PathName)) { if (baseIsSliced) { throw Error.NotSupported("Using a slicing shorthand ({0}) is not supported for re-slicing".FormatWith(diffNav.Path)); } if (diffIsSliced) { throw Error.NotSupported("Using a slicing shorthand ({0}) can only be used to introduce a single slice".FormatWith(diffNav.Path)); } match.Action = MatchAction.Merge; return(new List <MatchInfo>() { match }); } // Else, this is a slice match - process it separately return(constructSliceMatch(snapNav, diffNav)); }
public void MakeDifferentialTree() { var struc = new Profile.ProfileStructureComponent(); struc.Differential = new Profile.ConstraintComponent(); struc.Differential.Element = new List<Profile.ElementComponent>(); var e = struc.Differential.Element; e.Add(new Profile.ElementComponent() { Path = "A.B.C1" }); e.Add(new Profile.ElementComponent() { Path = "A.B.C1" }); e.Add(new Profile.ElementComponent() { Path = "A.B.C2" }); e.Add(new Profile.ElementComponent() { Path = "A.B" }); e.Add(new Profile.ElementComponent() { Path = "A.B.C1.D" }); e.Add(new Profile.ElementComponent() { Path = "A.D.F" }); var tree = new DifferentialTreeConstructor(struc.Differential).MakeTree(); Assert.IsNotNull(tree); var nav = new ElementNavigator(tree); Assert.AreEqual(10, nav.Count); Assert.IsTrue(nav.MoveToChild("A")); Assert.IsTrue(nav.MoveToChild("B")); Assert.IsTrue(nav.MoveToChild("C1")); Assert.IsTrue(nav.MoveToNext("C1")); Assert.IsTrue(nav.MoveToNext("C2")); Assert.IsTrue(nav.MoveToParent()); // 1st A.B Assert.IsTrue(nav.MoveToNext() && nav.Path == "A.B"); // (now) 2nd A.B Assert.IsTrue(nav.MoveToChild("C1")); Assert.IsTrue(nav.MoveToChild("D")); Assert.IsTrue(nav.MoveToParent()); // A.B.C1 Assert.IsTrue(nav.MoveToParent()); // A.B (2nd) Assert.IsTrue(nav.MoveToNext() && nav.Path == "A.D"); Assert.IsTrue(nav.MoveToChild("F")); }
public void CopyChildTree() { var dest = createTestNav(); var struc = new StructureDefinition(); struc.Snapshot = new StructureDefinition.StructureDefinitionSnapshotComponent(); struc.Snapshot.Element = new List<ElementDefinition>(); var e = struc.Snapshot.Element; e.Add(new ElementDefinition() { Path = "X" }); e.Add(new ElementDefinition() { Path = "X.Y1" }); e.Add(new ElementDefinition() { Path = "X.Y2" }); e.Add(new ElementDefinition() { Path = "X.Y2.Z1" }); e.Add(new ElementDefinition() { Path = "X.Y2.Z2" }); var source = new ElementNavigator(struc); Assert.IsTrue(dest.JumpToFirst("A.D")); var dstPos = dest.OrdinalPosition; source.MoveToFirstChild(); var srcPos = source.OrdinalPosition; Assert.IsTrue(dest.CopyChildren(source)); Assert.AreEqual(srcPos, source.OrdinalPosition, "source did not remain on original position"); Assert.AreEqual(dstPos, dest.OrdinalPosition, "dest did not remain on original position"); Assert.IsTrue(dest.MoveToFirstChild()); Assert.AreEqual("Y1", dest.PathName); Assert.IsTrue(dest.MoveToNext()); Assert.AreEqual("Y2", dest.PathName); Assert.IsFalse(dest.MoveToNext()); Assert.IsTrue(dest.MoveToFirstChild()); Assert.AreEqual("Z1", dest.PathName); Assert.IsTrue(dest.MoveToNext()); Assert.AreEqual("Z2", dest.PathName); Assert.IsFalse(dest.MoveToNext()); }
public void MakeDifferentialTree() { var e = new List<ElementDefinition>(); e.Add(new ElementDefinition() { Path = "A.B.C1" }); e.Add(new ElementDefinition() { Path = "A.B.C1" }); e.Add(new ElementDefinition() { Path = "A.B.C2" }); e.Add(new ElementDefinition() { Path = "A.B" }); e.Add(new ElementDefinition() { Path = "A.B.C1.D" }); e.Add(new ElementDefinition() { Path = "A.D.F" }); var tree = new DifferentialTreeConstructor(e).MakeTree(); Assert.IsNotNull(tree); var nav = new ElementNavigator(tree); Assert.AreEqual(10, nav.Count); Assert.IsTrue(nav.MoveToChild("A")); Assert.IsTrue(nav.MoveToChild("B")); Assert.IsTrue(nav.MoveToChild("C1")); Assert.IsTrue(nav.MoveToNext("C1")); Assert.IsTrue(nav.MoveToNext("C2")); Assert.IsTrue(nav.MoveToParent()); // 1st A.B Assert.IsTrue(nav.MoveToNext() && nav.Path == "A.B"); // (now) 2nd A.B Assert.IsTrue(nav.MoveToChild("C1")); Assert.IsTrue(nav.MoveToChild("D")); Assert.IsTrue(nav.MoveToParent()); // A.B.C1 Assert.IsTrue(nav.MoveToParent()); // A.B (2nd) Assert.IsTrue(nav.MoveToNext() && nav.Path == "A.D"); Assert.IsTrue(nav.MoveToChild("F")); }