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); } }
/// <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; }
public static void DumpMatches(this IEnumerable<ElementMatcher.MatchInfo> matches, ElementNavigator snapNav, ElementNavigator diffNav) { var sbm = snapNav.Bookmark(); var dbm = diffNav.Bookmark(); foreach(var match in matches) { if (!snapNav.ReturnToBookmark(match.BaseBookmark) || !diffNav.ReturnToBookmark(match.DiffBookmark)) throw Error.InvalidOperation("Found unreachable bookmark in matches"); var bPos = snapNav.Path + "[{0}]".FormatWith(snapNav.OrdinalPosition); var dPos = diffNav.Path + "[{0}]".FormatWith(diffNav.OrdinalPosition); Debug.WriteLine("B:{0} <--{1}--> D:{2}".FormatWith(bPos, match.Action.ToString(), dPos)); } snapNav.ReturnToBookmark(sbm); diffNav.ReturnToBookmark(dbm); }
private static int countChildNameRepeats(ElementNavigator diff) { 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; }
/// <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; }
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; }
/// <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); }
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; }