Example #1
0
        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);
            }
        }
Example #2
0
        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);
            }
        }
Example #3
0
        // [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);
        }
Example #4
0
        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);
        }
Example #5
0
        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);
        }
Example #6
0
        // [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);
                        }
                    }
                }
            }
        }
Example #7
0
        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);

                // [WMR 20160719] Add name, if not null
                if (snapNav.Current.Name != null)
                {
                    bPos += " '{0}'".FormatWith(snapNav.Current.Name);
                }
                if (diffNav.Current.Name != null)
                {
                    dPos += " '{0}'".FormatWith(diffNav.Current.Name);
                }

                Debug.WriteLine("B:{0} <--{1}--> D:{2}".FormatWith(bPos, match.Action.ToString(), dPos));
            }

            snapNav.ReturnToBookmark(sbm);
            diffNav.ReturnToBookmark(dbm);
        }
Example #8
0
        /// <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
                });
            }
        }
Example #9
0
        /// <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);
        }
Example #10
0
        // [WMR 20160720] NEW
        // Handle type slices
        // Difference with regular slices:
        // - Don't need to handle extensions
        // - Match renamed elements, i.e. value[x] => valueBoolean
        private static List <MatchInfo> constructTypeSliceMatch(ElementNavigator snapNav, ElementNavigator diffNav)
        {
            var result = new List <MatchInfo>();

            var bm       = snapNav.Bookmark();
            var diffName = diffNav.PathName;

            Debug.Assert(snapNav.Current.Slicing == null);

            // For the first entries with explicit slicing information,
            // generate a match between the base's unsliced element and the first entry in the diff
            if (diffNav.Current.Slicing != null)
            {
                // Differential has information for the slicing entry
                result.Add(new MatchInfo()
                {
                    BaseBookmark = bm,
                    DiffBookmark = diffNav.Bookmark(),
                    Action       = MatchAction.Slice
                });

                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 (diffNav.MoveToNextTypeSlice(snapNav.PathName));

            return(result);
        }
Example #11
0
        /// <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);
        }
Example #12
0
        /// <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)
        {
            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);
        }
Example #14
0
        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;

            // 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

            bool isExtension  = diffNav.Current.IsExtension();
            bool diffIsSliced = diffNav.Current.Slicing != null;

            if (diffIsSliced || isExtension)
            {
                // Differential has information for the slicing entry
                result.Add(new MatchInfo()
                {
                    BaseBookmark = bm,
                    DiffBookmark = diffNav.Bookmark(),
                    Action       = MatchAction.Slice
                });

                // Skip any existing slicing entry in the differential; below we process the actual slices
                if (diffIsSliced)
                {
                    // If the differential contains a slicing entry, then it should also define at least a single slice.
                    if (!diffNav.MoveToNext())
                    {
                        throw Error.InvalidOperation("Differential has a slicing entry for path '{0}', but no first actual slice", diffNav.Path);
                    }
                }
            }

            // Then, generate a match between the base element(s) 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
            {
                // Find matching slice in base profile
                if (!baseIsSliced)
                {
                    result.Add(new MatchInfo()
                    {
                        BaseBookmark = bm,
                        DiffBookmark = diffNav.Bookmark(),
                        Action       = MatchAction.Add
                    });
                }
                else
                {
                    Bookmark matchingSlice;
                    if (FindBaseSlice(snapNav, diffNav, out matchingSlice))
                    {
                        result.Add(new MatchInfo()
                        {
                            BaseBookmark = matchingSlice,
                            DiffBookmark = diffNav.Bookmark(),
                            Action       = MatchAction.Merge,
                        });
                    }
                    else
                    {
                        result.Add(new MatchInfo()
                        {
                            // BaseBookmark = bm,
                            BaseBookmark = matchingSlice,
                            DiffBookmark = diffNav.Bookmark(),
                            Action       = MatchAction.Add
                        });
                    }
                }
            } while (diffNav.MoveToNext(diffName));

            return(result);
        }