private static XmlNode HandleCaseOfNoAncestorChild(XmlMerger merger, XmlNode ours, XmlNode ourChild, XmlNode theirChild)
        {
            var mergeSituation         = merger.MergeSituation;
            var pathToFileInRepository = mergeSituation.PathToFileInRepository;
            var mergeStrategyForChild  = merger.MergeStrategies.GetElementStrategy(ourChild ?? theirChild);

            if (ourChild == null)
            {
                // they added child.
                merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, theirChild));
                // ReSharper disable once PossibleNullReferenceException -- if ours.OwnerDocument is null, we have other problems
                ours.AppendChild(ours.OwnerDocument.ImportNode(theirChild, true));
                return(ours);
            }
            if (theirChild == null)
            {
                // We added child.
                merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, ourChild));
                return(ours);
            }
            // Both added child.
            if (XmlUtilities.AreXmlElementsEqual(ourChild, theirChild))
            {
                // Both are the same.
                merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(pathToFileInRepository, ourChild));
                return(ours);
            }
            // Both are different.
            if (XmlUtilities.IsTextLevel(ourChild, theirChild, null))
            {
                if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin)
                {
                    merger.ConflictOccurred(new XmlTextBothAddedTextConflict(ourChild.Name, ourChild, theirChild, mergeSituation,
                                                                             mergeStrategyForChild, mergeSituation.AlphaUserId));
                }
                else
                {
                    merger.ConflictOccurred(new XmlTextBothAddedTextConflict(theirChild.Name, theirChild, ourChild, mergeSituation,
                                                                             mergeStrategyForChild, mergeSituation.BetaUserId));
                    XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, theirChild);
                }
            }
            else
            {
                if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin)
                {
                    var ourChildClone   = MakeClone(ourChild);
                    var theirChildClone = MakeClone(theirChild);
                    if (XmlUtilities.AreXmlElementsEqual(ourChildClone, theirChildClone))
                    {
                        merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, ourChild));
                        var ourChildReplacement = ourChild;
                        merger.MergeInner(ours, ref ourChildReplacement, theirChild, null);
                        if (!ReferenceEquals(ourChild, ourChildReplacement))
                        {
                            XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, ourChildReplacement);
                        }
                    }
                    else
                    {
                        merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(ourChild.Name, ourChild, theirChild,
                                                                                                        mergeSituation, mergeStrategyForChild,
                                                                                                        mergeSituation.AlphaUserId));
                    }
                }
                else
                {
                    var ourChildClone   = MakeClone(ourChild);
                    var theirChildClone = MakeClone(theirChild);
                    if (XmlUtilities.AreXmlElementsEqual(ourChildClone, theirChildClone))
                    {
                        merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, theirChild));
                        var ourChildReplacement = ourChild;
                        merger.MergeInner(ours, ref ourChildReplacement, theirChild, null);
                        if (!ReferenceEquals(ourChild, ourChildReplacement))
                        {
                            XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, ourChildReplacement);
                        }
                    }
                    else
                    {
                        merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(theirChild.Name, theirChild, ourChild,
                                                                                                        mergeSituation, mergeStrategyForChild,
                                                                                                        mergeSituation.BetaUserId));
                        XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, theirChild);
                    }
                }
            }
            return(ours);
        }
Exemple #2
0
        /// <summary>
        /// Remove from ancestorKeepers any node that does not correspond to anything (both deleted)
        /// Remove from ancestorKeepers and theirKeepers any pair that correspond to each other but not to anything in ours. Report conflict (delete/edit) if pair not identical.
        /// Remove from ancestorKeepers and ourKeepers any pair that correspond to each other and are identical, but don't correspond to anything in theirs (they deleted)
        /// Report conflict (edit/delete) on any pair that correspond in ours and ancestor, but nothing in theirs, and that are NOT identical. (but keep them...we win)
        /// </summary>
        private void DoDeletions()
        {
            // loop over a copy of the list, since we may modify ancestorKeepers.
            List <XmlNode> loopSource    = new List <XmlNode>(_childrenOfAncestorKeepers);
            var            ourChildSet   = new HashSet <XmlNode>(_ours == null ? new XmlNode[0] : _ours.ChildNodes.Cast <XmlNode>());
            var            theirChildSet = new HashSet <XmlNode>(_theirs == null ? new XmlNode[0] : _theirs.ChildNodes.Cast <XmlNode>());

            foreach (XmlNode ancestorChild in loopSource)
            {
                ElementStrategy  mergeStrategy = _merger.MergeStrategies.GetElementStrategy(ancestorChild);
                IFindNodeToMerge finder        = mergeStrategy.MergePartnerFinder;
                XmlNode          ourChild      = finder.GetNodeToMerge(ancestorChild, _ours, ourChildSet);
                XmlNode          theirChild    = finder.GetNodeToMerge(ancestorChild, _theirs, theirChildSet);

                var extantNode = ancestorChild ?? ourChild ?? theirChild;
                if (extantNode is XmlCharacterData)
                {
                    return;                     // Already done.
                }
                if (XmlUtilities.IsTextLevel(ourChild, theirChild, ancestorChild))
                {
                    new MergeTextNodesMethod(_merger, mergeStrategy, _skipInnerMergeFor,
                                             ref ourChild, _childrenOfOurKeepers,
                                             theirChild, _childrenOfTheirKeepers,
                                             ancestorChild, _childrenOfAncestorKeepers).DoDeletions();
                }
                else if (ourChild == null)
                {
                    // We deleted it.
                    if (theirChild == null)
                    {
                        // We both deleted it. Forget it ever existed.
                        // Route tested: MergeChildrenMethodTests.
                        _merger.EventListener.ChangeOccurred(new XmlBothDeletionChangeReport(_merger.MergeSituation.PathToFileInRepository, ancestorChild));
                        _childrenOfAncestorKeepers.Remove(ancestorChild);
                    }
                    else
                    {
                        if (!XmlUtilities.AreXmlElementsEqual(ancestorChild, theirChild))
                        {
                            // We deleted, they modified, report conflict.
                            if (theirChild.NodeType == XmlNodeType.Element)
                            {
                                // Route tested (XmlMergerTests).
                                _merger.ConflictOccurred(
                                    new RemovedVsEditedElementConflict(theirChild.Name, null,
                                                                       theirChild, ancestorChild,
                                                                       _merger.MergeSituation,
                                                                       _merger.MergeStrategies.GetElementStrategy(theirChild),
                                                                       _merger.MergeSituation.BetaUserId),
                                    theirChild);
                                _skipInnerMergeFor.Add(theirChild);
                            }
                            else
                            {
                                // Never used. But then, there isn't plain text in an xml file.
                                _merger.ConflictOccurred(
                                    new RemovedVsEditedTextConflict(null, theirChild,
                                                                    ancestorChild,
                                                                    _merger.MergeSituation,
                                                                    _merger.MergeSituation.BetaUserId));
                                _skipInnerMergeFor.Add(theirChild);
                            }
                            _childrenOfAncestorKeepers.Remove(ancestorChild);                            //review hatton added dec 2009, wanting whoever edited it to win (previously "we" always won)
                        }
                        else
                        {
                            //We deleted it, they didn't edit it. So just make it go away.
                            // Route tested in TextElementMergeTests, MergeChildrenMethod_DiffOnlyTests, XmlMergerTests
                            _merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(_merger.MergeSituation.PathToFileInRepository, ancestorChild, theirChild));
                            _childrenOfAncestorKeepers.Remove(ancestorChild);
                            _childrenOfTheirKeepers.Remove(theirChild);
                        }
                    }
                }
                else if (theirChild == null)
                {
                    // they deleted it (and we didn't)
                    if (XmlUtilities.AreXmlElementsEqual(ancestorChild, ourChild))
                    {
                        // We didn't touch it, allow their deletion to go forward, forget it existed.
                        // Route tested (XmlMergerTests).
                        _merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(_merger.MergeSituation.PathToFileInRepository, ancestorChild, ourChild));
                        _childrenOfAncestorKeepers.Remove(ancestorChild);
                        _childrenOfOurKeepers.Remove(ourChild);
                    }
                    else
                    {
                        // We changed it, ignore their deletion and report conflict.
                        if (ourChild.NodeType == XmlNodeType.Element)
                        {
                            // Route tested (XmlMergerTests).
                            _merger.ConflictOccurred(
                                new EditedVsRemovedElementConflict(ourChild.Name, ourChild, null, ancestorChild,
                                                                   _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(ourChild), _merger.MergeSituation.AlphaUserId),
                                ourChild);
                            _skipInnerMergeFor.Add(ourChild);
                        }
                        else
                        {
                            // Never used. But then, there isn't plain text in an xml file.
                            _merger.ConflictOccurred(
                                new EditedVsRemovedTextConflict(ourChild, null, ancestorChild, _merger.MergeSituation, _merger.MergeSituation.AlphaUserId));
                            _skipInnerMergeFor.Add(ourChild);
                        }
                    }
                }
            }
        }
        public static void Run(XmlMerger merger, ElementStrategy strategy, ref XmlNode ours, XmlNode theirs, XmlNode ancestor)
        {
// All routes tested in this method.
            Guard.AgainstNull(merger, "merger");             // Route tested.
            Guard.AgainstNull(strategy, "strategy");         // Route tested.
            if (ours == null && theirs == null && ancestor == null)
            {
                throw new ArgumentNullException();                 // Route tested.
            }
            if (XmlUtilities.IsTextLevel(ours, theirs, ancestor))
            {
                // Route tested.
                new MergeTextNodesMethod(merger, merger.MergeStrategies.GetElementStrategy(ours ?? theirs ?? ancestor),
                                         new HashSet <XmlNode>(),
                                         ref ours, new List <XmlNode>(),
                                         theirs, new List <XmlNode>(),
                                         ancestor, new List <XmlNode>()).Run();
                return;
            }

            List <XmlNode> ourChildren;
            List <XmlNode> theirChildren;
            List <XmlNode> ancestorChildren;

            switch (strategy.NumberOfChildren)
            {
            default:
                throw new InvalidOperationException("Using strategy with NumberOfChildren property of NumberOfChildrenAllowed.ZeroOrMore is not legal.");                         // Route tested.

            case NumberOfChildrenAllowed.Zero:
                ourChildren = GetElementChildren(ours).ToList();
                if (ourChildren.Any())
                {
                    throw new InvalidOperationException("Using strategy with NumberOfChildren property of NumberOfChildrenAllowed.Zero is not legal, when there are child element nodes.");                             // Route tested.
                }
                theirChildren = GetElementChildren(theirs).ToList();
                if (theirChildren.Any())
                {
                    throw new InvalidOperationException("Using strategy with NumberOfChildren property of NumberOfChildrenAllowed.Zero is not legal, when there are child element nodes.");                             // Route tested.
                }
                ancestorChildren = GetElementChildren(ancestor).ToList();
                if (ancestorChildren.Any())
                {
                    throw new InvalidOperationException("Using strategy with NumberOfChildren property of NumberOfChildrenAllowed.Zero is not legal, when there are child element nodes.");                             // Route tested.
                }
                // Don't merge deeper than merging the attributes, since there aren't supposed to be any children.
                // Already done by caller MergeXmlAttributesService.MergeAttributes(merger, ref ours, theirs, ancestor);
                // Route tested.
                break;

            case NumberOfChildrenAllowed.ZeroOrOne:
                ourChildren = GetElementChildren(ours).ToList();
                if (ourChildren.Count > 1)
                {
                    throw new InvalidOperationException("Using strategy with NumberOfChildren property of NumberOfChildrenAllowed.ZeroOrOne is not legal, when there are multiple child nodes.");                             // Route tested.
                }
                theirChildren = GetElementChildren(theirs).ToList();
                if (theirChildren.Count > 1)
                {
                    throw new InvalidOperationException("Using strategy with NumberOfChildren property of NumberOfChildrenAllowed.ZeroOrOne is not legal, when there are multiple child nodes.");                             // Route tested.
                }
                ancestorChildren = GetElementChildren(ancestor).ToList();
                if (ancestorChildren.Count > 1)
                {
                    throw new InvalidOperationException("Using strategy with NumberOfChildren property of NumberOfChildrenAllowed.ZeroOrOne is not legal, when there are child element nodes.");                             // Route tested.
                }
                // Already done by caller MergeXmlAttributesService.MergeAttributes(merger, ref ours, theirs, ancestor);

                if (!ourChildren.Any() && !theirChildren.Any() && ancestor != null)
                {
                    return;                             // Route tested.
                }
                // The return value of Run may be the original 'ours', a replacement for it, or null.
                ours = Run(merger, ours, theirs, ancestor);                         // Route tested.
                break;
            }
        }
Exemple #4
0
        /// <summary>
        /// This method does the actual work for the various public entry points of XmlMerge
        /// and from the various Method-type classes, as it processes child nodes, if any.
        /// </summary>
        internal void MergeInner(XmlNode ourParent, ref XmlNode ours, XmlNode theirs, XmlNode ancestor)
        {
            SendMergeHeartbeat();
            _oursContext     = ours;
            _theirsContext   = theirs;
            _ancestorContext = ancestor;

            var elementStrat = MergeStrategies.GetElementStrategy(ours ?? theirs ?? ancestor);

            // Step 0: Do anything special the strategy wants to do before regular merging. This may modify the nodes.
            // For instance clinets may want to ensure 'our' and 'their' have the latest date stamp available.
            elementStrat.Premerger.Premerge(EventListener, ref ours, theirs, ancestor);

            // Step 0.1: Set up a context, if available.
            // Listeners are set to use NullContextDescriptor as the default context,
            var contextDescriptorGenerator = elementStrat.ContextDescriptorGenerator;             // May be null, which is fine in code, below, that uses it.
            //review: question: does this not get called at levels below the entry?
            //this would seem to fail at, say, a sense. I'm confused. (JH 30june09)
            var descriptor = GetContextDescriptor(ours, contextDescriptorGenerator);

            EventListener.EnteringContext(descriptor);             // TODO: If the context is ever redone as a stack, then this call with push the context onto the stack.
            _htmlContextGenerator = (contextDescriptorGenerator as IGenerateHtmlContext) ?? new SimpleHtmlGenerator();

            // Step 1: If the current set of nodes are immutable,
            // then make sure no changes took place (among a few other things).
            if (elementStrat.IsImmutable)
            {
                ImmutableElementMergeService.DoMerge(this, ourParent, ref ours, theirs, ancestor);
                return;                 // Don't go any further, since it is immutable.
            }

            // Step 2: If the current set of elements is 'atomic'
            // (only one user can make changes to the node, or anything it contains),
            // then make sure only set of changes have been made.
            if (elementStrat.IsAtomic)
            {
                if (elementStrat.AllowAtomicTextMerge && XmlUtilities.IsTextLevel(ours, theirs, ancestor))
                {
                    DoTextMerge(ref ours, theirs, ancestor, elementStrat);
                    return;
                }
                MergeAtomicElementService.Run(this, ourParent, ref ours, theirs, ancestor);
                return;
            }

            // Step 3: Go ahead and merge the attributes, as needed.
            MergeXmlAttributesService.MergeAttributes(this, ref ours, theirs, ancestor);

            // Step 4: Hmm, trouble is, a node might be required to have one or more child nodes.
            // I suppose a real validator would have picked up on that, and not allowed
            // the first commit, so we wouldn't then be doing a merge operation.
            // So, not to worry here, if the validator doesn't care enough to prevent the first commit.
            // It could be possible for the elements to have no children, in which case, there is nothing more to merge, so just return.
            if (ours != null && !ours.HasChildNodes && theirs != null && !theirs.HasChildNodes && ancestor != null && !ancestor.HasChildNodes)
            {
                return;
            }

            // Step 5: Do some kind of merge on the child node.
            if (XmlUtilities.IsTextLevel(ours, theirs, ancestor))
            {
                // Step 5A: Merge the text element.
                DoTextMerge(ref ours, theirs, ancestor, elementStrat);
            }
            else
            {
                switch (elementStrat.NumberOfChildren)
                {
                case NumberOfChildrenAllowed.Zero:
                case NumberOfChildrenAllowed.ZeroOrOne:
                    // Step 5B: Merge the "special needs" nodes.
                    MergeLimitedChildrenService.Run(this, elementStrat, ref ours, theirs, ancestor);
                    break;

                case NumberOfChildrenAllowed.ZeroOrMore:
                    // Step 5B:
                    // Q: is this a level of the xml file that would consitute the minimal unit conflict-understanding
                    // from a user perspecitve?
                    // e.g., in a dictionary, this is the lexical entry.  In a text, it might be  a paragraph.
                    // A (RandyR): Definitely it may not be such a level of node.
                    new MergeChildrenMethod(ours, theirs, ancestor, this).Run();
                    break;
                }
            }

            // At some point, it may be necessary here to restore the pre-existing values of
            // _oursContext, _theirsContext, _ancestorContext, and _htmlContextGenerator.
            // and somehow restore the EventListener's Context.
            // Currently however no client generates further conflicts after calling MergeChildren.

            // Step 6: TODO: If the context is ever redone as a stack, then pop the stack here to return to the outer context via some new LeavingContext method on EventListener.
        }