public static ElementNavigator JumpToNameReference(this Profile.ProfileStructureComponent structure, string nameReference) { var nav = new ElementNavigator(structure); //TODO: In the current DSTU1 base profiles, nameReference is actually a path, not a name (to Element.Name) //this is a problem, since when doing slicing, the path may no longer point to a single set of constraints //so, we need to (temporarily) watch out for this if (nameReference.Contains(".")) { // An incorrectly used nameReference, containing a Path, not a name if (nav.JumpToFirst(nameReference)) { return(nav); } else { return(null); } } else { if (nav.JumpToNameReference(nameReference)) { return(nav); } else { return(null); } } }
public Profile.ProfileStructureComponent Expand(Profile.ProfileStructureComponent differential) { var baseStructure = _loader.LocateBaseStructure(differential.TypeElement); if (baseStructure == null) { throw Error.InvalidOperation("Could not locate the base profile for type {0}", differential.TypeElement.ToString()); } var baseUri = StructureLoader.BuildBaseStructureUri(differential.TypeElement).ToString(); var snapshot = (Profile.ProfileStructureComponent)baseStructure.DeepCopy(); 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(); return(snapshot); }
/// <summary> /// Insert the children of the current source node under the node pointed to by the destination. /// </summary> /// <param name="dest"></param> /// <param name="source"></param> /// <returns></returns> public static bool CopyChildren(this BaseElementNavigator dest, ElementNavigator source) { if (dest.HasChildren) return false; // Protect children from being overwritten if (!source.MoveToFirstChild()) return true; // Nothing to copy, but successful anyway bool firstChild = true; do { var copiedChild = (Profile.ElementComponent)source.Current.DeepCopy(); if (firstChild) { // The first time, create a new child in the destination dest.InsertFirstChild(copiedChild); firstChild = false; } else // Then insert other childs after that dest.InsertAfter(copiedChild); // If there are nested children in the source, insert them under // the newly inserted node in the destination if (source.HasChildren) dest.CopyChildren(source); } while (source.MoveToNext()); // Bring both source & destination back one step to the original parents source.MoveToParent(); dest.MoveToParent(); return true; }
public Profile.ProfileStructureComponent Expand(Profile.ProfileStructureComponent differential) { var baseStructure = _loader.LocateBaseStructure(differential.TypeElement); if (baseStructure == null) throw Error.InvalidOperation("Could not locate the base profile for type {0}", differential.TypeElement.ToString()); var baseUri = StructureLoader.BuildBaseStructureUri(differential.TypeElement).ToString(); var snapshot = (Profile.ProfileStructureComponent)baseStructure.DeepCopy(); 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(); return snapshot; }
public void MakeDifferentialTree() { var struc = new Profile.ProfileStructureComponent(); struc.Element = new List<Profile.ElementComponent>(); struc.Element.Add(new Profile.ElementComponent() { Path = "A.B.C1" }); struc.Element.Add(new Profile.ElementComponent() { Path = "A.B.C1" }); struc.Element.Add(new Profile.ElementComponent() { Path = "A.B.C2" }); struc.Element.Add(new Profile.ElementComponent() { Path = "A.B" }); struc.Element.Add(new Profile.ElementComponent() { Path = "A.B.C1.D" }); struc.Element.Add(new Profile.ElementComponent() { Path = "A.D.F" }); var tree = new DifferentialTreeConstructor(struc).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")); }
/// <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.ProfileStructureComponent MakeTree() { var diff = (Profile.ProfileStructureComponent)_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 expandBaseElement(ElementNavigator snap, ElementNavigator diff) { snap.ExpandElement(_loader); 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 static bool ExpandElement(this ElementNavigator nav, StructureLoader source) { if (source == null) { throw Error.ArgumentNull("source"); } if (nav.Current == null) { throw Error.ArgumentNull("Navigator is not positioned on an element"); } if (nav.Current.Definition == null) { throw Error.Argument("Cannot move down into element {0} since it has no element definition information", nav.Path); } if (nav.HasChildren) { return(true); // already has children, we're not doing anything extra } if (nav.Current.Definition != null) { var defn = nav.Current.Definition; if (!String.IsNullOrEmpty(defn.NameReference)) { var sourceNav = resolveNameReference(nav.Structure, defn.NameReference); nav.CopyChildren(sourceNav); } else if (defn.Type != null && defn.Type.Count > 0) { if (defn.Type.Count > 1) { throw new NotImplementedException("Don't know how to implement navigation into choice types yet at node " + nav.Path); } else { var sourceNav = resolveStructureReference(source, defn.Type[0].CodeElement); if (sourceNav != null) { sourceNav.MoveToFirstChild(); nav.CopyChildren(sourceNav); } else { throw new FileNotFoundException("Cannot locate base-structure for datatype " + defn.Type[0].Code); } } } return(true); } return(false); }
private static bool childNameRepeats(ElementNavigator diff) { var isSliced = false; var currentPath = diff.PathName; if (diff.MoveToNext()) { // check whether the next sibling in the differential has the same name, // that means we're looking at a slice isSliced = diff.PathName == currentPath; diff.MoveToPrevious(); } return(isSliced); }
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 (childNameRepeats(diff) || 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> /// Rewrites the Path's of the elements in a structure so they are based on the given path: the root /// of the given structure will become the given path, it's children will be relocated below that path /// </summary> /// <param name="root">The structure that will be rebased on the path</param> /// <param name="path">The path to rebase the structure on</param> public static void Rebase(this Profile.ProfileStructureComponent root, string path) { var nav = new ElementNavigator(root); if (nav.MoveToFirstChild()) { var newPaths = new List<string>(); newPaths.Add(path); rebaseChildren(nav, path, newPaths); // Can only change the paths after navigating the tree, otherwise the // navigation functions (which are based on the paths) won't function correctly for (var i = 0; i < root.Element.Count; i++) root.Element[i].Path = newPaths[i]; } }
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 (childNameRepeats(diff) || 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> /// Rewrites the Path's of the elements in a structure so they are based on the given path: the root /// of the given structure will become the given path, it's children will be relocated below that path /// </summary> /// <param name="root">The structure that will be rebased on the path</param> /// <param name="path">The path to rebase the structure on</param> public static void Rebase(this Profile.ProfileStructureComponent root, string path) { var nav = new ElementNavigator(root); if (nav.MoveToFirstChild()) { var newPaths = new List <string>(); newPaths.Add(path); rebaseChildren(nav, path, newPaths); // Can only change the paths after navigating the tree, otherwise the // navigation functions (which are based on the paths) won't function correctly for (var i = 0; i < root.Element.Count; i++) { root.Element[i].Path = newPaths[i]; } } }
/// <summary> /// Insert the children of the current source node under the node pointed to by the destination. /// </summary> /// <param name="dest"></param> /// <param name="source"></param> /// <returns></returns> public static bool CopyChildren(this BaseElementNavigator dest, ElementNavigator source) { if (dest.HasChildren) { return(false); // Protect children from being overwritten } if (!source.MoveToFirstChild()) { return(true); // Nothing to copy, but successful anyway } bool firstChild = true; do { var copiedChild = (Profile.ElementComponent)source.Current.DeepCopy(); if (firstChild) { // The first time, create a new child in the destination dest.InsertFirstChild(copiedChild); firstChild = false; } else { // Then insert other childs after that dest.InsertAfter(copiedChild); } // If there are nested children in the source, insert them under // the newly inserted node in the destination if (source.HasChildren) { dest.CopyChildren(source); } }while (source.MoveToNext()); // Bring both source & destination back one step to the original parents source.MoveToParent(); dest.MoveToParent(); return(true); }
public static ElementNavigator JumpToNameReference(this Profile.ProfileStructureComponent structure, string nameReference) { var nav = new ElementNavigator(structure); //TODO: In the current DSTU1 base profiles, nameReference is actually a path, not a name (to Element.Name) //this is a problem, since when doing slicing, the path may no longer point to a single set of constraints //so, we need to (temporarily) watch out for this if (nameReference.Contains(".")) { // An incorrectly used nameReference, containing a Path, not a name if (nav.JumpToFirst(nameReference)) return nav; else return null; } else { if (nav.JumpToNameReference(nameReference)) return nav; else return null; } }
public void CopyChildTree() { var dest = createTestNav(); var struc = new Profile.ProfileStructureComponent(); struc.Element = new List<Profile.ElementComponent>(); struc.Element.Add(new Profile.ElementComponent() { Path = "X" }); struc.Element.Add(new Profile.ElementComponent() { Path = "X.Y1" }); struc.Element.Add(new Profile.ElementComponent() { Path = "X.Y2" }); struc.Element.Add(new Profile.ElementComponent() { Path = "X.Y2.Z1" }); struc.Element.Add(new Profile.ElementComponent() { 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 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")); }
public static string GetParentNameFromPath(this Profile.ElementComponent element) { return(ElementNavigator.GetParentPath(element.Path)); }
private static bool childNameRepeats(ElementNavigator diff) { var isSliced = false; var currentPath = diff.PathName; if (diff.MoveToNext()) { // check whether the next sibling in the differential has the same name, // that means we're looking at a slice isSliced = diff.PathName == currentPath; diff.MoveToPrevious(); } return isSliced; }
public ElementNavigator(ElementNavigator other) { setupElems(other._elements); Structure = other.Structure; OrdinalPosition = other.OrdinalPosition; }
private void mergeSlice(ElementNavigator snap, ElementNavigator diff) { // diff is now located at the first repeat of a slice, which is (possibly) the slice 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() && !isSlicedToOne(diff.Current)) throw Error.InvalidOperation("The slicing entry in the differential at {0} indicates an unbounded slice, but the base element is not a repeating element", diff.Current.Path); Profile.ElementComponent slicingEntry; // Yes, so, first, add the slicing entry to the snapshot. if (diff.Current.Slicing != null) { slicingEntry = createSliceEntry(snap.Current, diff.Current); snap.InsertBefore(slicingEntry); if (!diff.MoveToNext(diff.PathName)) throw Error.InvalidOperation("Slicing has no elements beyond the slicing entry"); // currently impossible to happen } else { // Mmmm....no slicing entry in the differential. This is only alloweable for extension slices, as a shorthand notation. if (!snap.Current.IsExtension()) throw Error.InvalidOperation("The slice group at {0} does not start with a slice entry element", diff.Current.Path); // In this case we insert a "prefab" extension slice. slicingEntry = createExtensionSlicingEntry(snap.Path); snap.InsertBefore(slicingEntry); } snap.MoveToNext(); // The differential and the snapshot are now both positioned on the first "real" slicing content element // Start by getting an unaltered copy of the current base definition, we need to re-insert a fresh copy // of it every time we encounter a new slice in the differential var slicingTemplate = (Profile.ElementComponent)snap.Current.DeepCopy(); var slicingName = snap.PathName; var first = true; do { if(first) { // The first time, we still have the original base definition available to slice first = false; } else { snap.InsertAfter((Profile.ElementComponent)slicingTemplate.DeepCopy()); //snap.MoveToNext(); } merge(snap, diff); } while (diff.MoveToNext(slicingName)); if (slicingEntry.Slicing.Rules != Profile.SlicingRules.Closed) { // Slices that are open in some form need to repeat the original "base" definition, // so that the open slices have a place to "fit in" snap.InsertAfter((Profile.ElementComponent)slicingTemplate.DeepCopy()); } //TODO: update/check the slice entry's min/max property to match what we've found in the slice group }
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")); }
private void mergeSlice(ElementNavigator snap, ElementNavigator diff) { // diff is now located at the first repeat of a slice, which is (possibly) the slice 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() && !isSlicedToOne(diff.Current)) { throw Error.InvalidOperation("The slicing entry in the differential at {0} indicates an unbounded slice, but the base element is not a repeating element", diff.Current.Path); } Profile.ElementComponent slicingEntry; // Yes, so, first, add the slicing entry to the snapshot. if (diff.Current.Slicing != null) { slicingEntry = createSliceEntry(snap.Current, diff.Current); snap.InsertBefore(slicingEntry); if (!diff.MoveToNext(diff.PathName)) { throw Error.InvalidOperation("Slicing has no elements beyond the slicing entry"); // currently impossible to happen } } else { // Mmmm....no slicing entry in the differential. This is only alloweable for extension slices, as a shorthand notation. if (!snap.Current.IsExtension()) { throw Error.InvalidOperation("The slice group at {0} does not start with a slice entry element", diff.Current.Path); } // In this case we insert a "prefab" extension slice. slicingEntry = createExtensionSlicingEntry(snap.Path); snap.InsertBefore(slicingEntry); } snap.MoveToNext(); // The differential and the snapshot are now both positioned on the first "real" slicing content element // Start by getting an unaltered copy of the current base definition, we need to re-insert a fresh copy // of it every time we encounter a new slice in the differential var slicingTemplate = (Profile.ElementComponent)snap.Current.DeepCopy(); var slicingName = snap.PathName; var first = true; do { if (first) { // The first time, we still have the original base definition available to slice first = false; } else { snap.InsertAfter((Profile.ElementComponent)slicingTemplate.DeepCopy()); //snap.MoveToNext(); } merge(snap, diff); }while (diff.MoveToNext(slicingName)); if (slicingEntry.Slicing.Rules != Profile.SlicingRules.Closed) { // Slices that are open in some form need to repeat the original "base" definition, // so that the open slices have a place to "fit in" snap.InsertAfter((Profile.ElementComponent)slicingTemplate.DeepCopy()); } //TODO: update/check the slice entry's min/max property to match what we've found in the slice group }