private static XmlNode HandleOurChildNotPresent(XmlMerger merger, XmlNode ours, XmlNode ancestor, XmlNode theirChild,
                                                 string pathToFileInRepository, XmlNode ancestorChild,
                                                 MergeSituation mergeSituation, IElementDescriber mergeStrategyForChild)
 {
     if (theirChild == null)
     {
         // Both deleted it.
         merger.EventListener.ChangeOccurred(new XmlBothDeletionChangeReport(pathToFileInRepository, ancestorChild));
     }
     else
     {
         if (XmlUtilities.AreXmlElementsEqual(ancestorChild, theirChild))
         {
             // We deleted it. They did nothing.
             merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(pathToFileInRepository, ancestor, ancestorChild));
         }
         else
         {
             // delete vs edit conflict.
             merger.ConflictOccurred(new RemovedVsEditedElementConflict(theirChild.Name, theirChild, null, ancestorChild,
                                                                        mergeSituation, mergeStrategyForChild,
                                                                        mergeSituation.BetaUserId));
             // ReSharper disable once PossibleNullReferenceException -- if ours.OwnerDocument is null, we have other problems
             ours.AppendChild(ours.OwnerDocument.ImportNode(theirChild, true));
         }
     }
     return(ours);
 }
Exemple #2
0
 public MergeChildrenMethod(XmlNode ours, XmlNode theirs, XmlNode ancestor, XmlMerger merger)
 {
     _ours     = ours;
     _theirs   = theirs;
     _ancestor = ancestor;
     _merger   = merger;
 }
 private static XmlNode HandleOurChildNotPresent(XmlMerger merger, XmlNode ours, XmlNode ancestor, XmlNode theirChild,
                                                 string pathToFileInRepository, XmlNode ancestorChild,
                                                 MergeSituation mergeSituation, IElementDescriber mergeStrategyForChild)
 {
     if (theirChild == null)
     {
         // Both deleted it.
         merger.EventListener.ChangeOccurred(new XmlBothDeletionChangeReport(pathToFileInRepository, ancestorChild));
     }
     else
     {
         if (XmlUtilities.AreXmlElementsEqual(ancestorChild, theirChild))
         {
             // We deleted it. They did nothing.
             merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(pathToFileInRepository, ancestor, ancestorChild));
         }
         else
         {
             // delete vs edit conflict.
             merger.ConflictOccurred(new RemovedVsEditedElementConflict(theirChild.Name, theirChild, null, ancestorChild,
                                                                        mergeSituation, mergeStrategyForChild,
                                                                        mergeSituation.BetaUserId));
             ours.AppendChild(theirChild);
         }
     }
     return(ours);
 }
        /// <summary>
        /// Produce a string that represents the 3-way merger of the given three elements.
        /// </summary>
        public ChorusNotesAnnotationMergingStrategy(MergeOrder order)
        {
            _annotationMerger = new XmlMerger(order.MergeSituation)
                {
                    EventListener = order.EventListener
                };

            SetupElementStrategies();
        }
 internal static void DoMerge(MergeOrder mergeOrder, XmlMerger merger)
 {
     XmlNode ours;
     XmlNode theirs;
     XmlNode common;
     DoPreMerge(mergeOrder, out ours, out theirs, out common);
     var results = merger.Merge(ours, theirs, common);
     DoPostMerge(mergeOrder.pathToOurs, results.MergedNode);
 }
 internal static XmlMerger CreateXmlMergerForFieldWorksData(MergeOrder mergeOrder, MetadataCache mdc)
 {
     var merger = new XmlMerger(mergeOrder.MergeSituation)
         {
             EventListener = mergeOrder.EventListener
         };
     BootstrapSystem(mdc, merger);
     return merger;
 }
		public void Do3WayMerge(MetadataCache mdc, MergeOrder mergeOrder)
		{
			var merger = new XmlMerger(mergeOrder.MergeSituation)
				{
					EventListener = mergeOrder.EventListener
				};
			CustomLayoutMergeStrategiesMethod.AddElementStrategies(merger.MergeStrategies);
			CustomLayoutMergeService.DoMerge(mergeOrder, merger);
		}
		public void FixtureSetup()
		{
			_mdc = MetadataCache.TestOnlyNewCache;
			var mergeOrder = new MergeOrder(null, null, null, new NullMergeSituation())
			{
				EventListener = new ListenerForUnitTests()
			};
			_merger = FieldWorksMergeServices.CreateXmlMergerForFieldWorksData(mergeOrder, _mdc);
		}
        /// <summary>
        /// Produce a string that represents the 3-way merger of the given three elements.
        /// </summary>
        public LiftEntryMergingStrategy(MergeOrder mergeOrder)
        {
            _entryMerger = new XmlMerger(mergeOrder.MergeSituation)
                           	{
                           		MergeStrategies = {ElementToMergeStrategyKeyMapper = new LiftElementToMergeStrategyKeyMapper()},
                                EventListener = mergeOrder.EventListener
                           	};

            LiftElementStrategiesMethod.AddLiftElementStrategies(_entryMerger.MergeStrategies);
        }
		public override void TestSetup()
		{
			base.TestSetup();
			Mdc.UpgradeToVersion(MetadataCache.MaximumModelVersion);
			var mergeOrder = new MergeOrder(null, null, null, new NullMergeSituation())
			{
				EventListener = new ListenerForUnitTests()
			};
			_merger = FieldWorksMergeServices.CreateXmlMergerForFieldWorksData(mergeOrder, Mdc);
		}
		internal static void DoMerge(MergeOrder mergeOrder, XmlMerger merger)
		{
			XmlNode ours;
			XmlNode theirs;
			XmlNode common;
			DoPreMerge(mergeOrder, out ours, out theirs, out common);
			// The document element is being returned here, so our parent isn't relevant and won't be used by the merge
			var results = merger.Merge(null, ours, theirs, common);
			DoPostMerge(mergeOrder.pathToOurs, results.MergedNode);
		}
 /// <summary>
 /// Gets the collection of element merge strategies.
 /// </summary>
 public MergeStrategies GetStrategies()
 {
     var merger = new XmlMerger(new MergeSituation(null, null, null, null, null, MergeOrder.ConflictHandlingModeChoices.WeWin));
     var def = new ElementStrategy(true)
               	{
               		MergePartnerFinder = new FindByEqualityOfTree()
               	};
     merger.MergeStrategies.SetStrategy("def", def);
     return merger.MergeStrategies;
 }
        /// <summary>
        /// Constructor
        /// </summary>
        public LiftRangesMergingStrategy(MergeOrder mergeOrder)
        {
            _merger = new XmlMerger(mergeOrder.MergeSituation)
                {
                    EventListener = mergeOrder.EventListener
                };

            LiftBasicElementStrategiesMethod.AddLiftBasicElementStrategies(_merger.MergeStrategies);
            LiftRangesElementStrategiesMethod.AddLiftRangeElementStrategies(_merger.MergeStrategies);
        }
Exemple #14
0
 internal ChildOrderer(List <XmlNode> primary, List <XmlNode> others, Dictionary <XmlNode, XmlNode> correspondences,
                       XmlMerger merger, ChildOrder parentOrder)
 {
     _primary         = primary;
     _others          = others;
     _correspondences = correspondences;
     _positions       = new PositionRecord[others.Count];
     Debug.Assert(merger != null);
     _merger      = merger;
     _parentOrder = parentOrder;
 }
        /// <summary>
        /// Gets the collection of element merge strategies.
        /// </summary>
        public MergeStrategies GetStrategies()
        {
            var merger = new XmlMerger(new MergeSituation(null, null, null, null, null, MergeOrder.ConflictHandlingModeChoices.WeWin));
            var def    = new ElementStrategy(true)
            {
                MergePartnerFinder = new FindByEqualityOfTree()
            };

            merger.MergeStrategies.SetStrategy("def", def);
            return(merger.MergeStrategies);
        }
        internal ChildOrderer(List<XmlNode> primary, List<XmlNode> others, Dictionary<XmlNode, XmlNode> correspondences,
			XmlMerger merger, ChildOrder parentOrder)
        {
            _primary = primary;
            _others = others;
            _correspondences = correspondences;
            _positions = new PositionRecord[others.Count];
            Debug.Assert(merger != null);
            _merger = merger;
            _parentOrder = parentOrder;
        }
        public void AllHadProperty_ButNothingElse()
        {
            const string commonAncestor =
            @"<Lexicon>
            <LexEntry guid='c1ed94c5-e382-11de-8a39-0800200c9a66'>
            <Etymology>
            </Etymology>
            </LexEntry>
            </Lexicon>";

            const string ours =
            @"<Lexicon>
            <LexEntry guid='c1ed94c5-e382-11de-8a39-0800200c9a66'>
            <Etymology>
            </Etymology>
            </LexEntry>
            </Lexicon>";

            const string theirs =
            @"<Lexicon>
            <LexEntry guid='c1ed94c5-e382-11de-8a39-0800200c9a66'>
            <Etymology>
            </Etymology>
            </LexEntry>
            </Lexicon>";

            var listener = new ListenerForUnitTests();
            var merger = new XmlMerger(new NullMergeSituation())
            {
                EventListener = listener
            };
            merger.MergeStrategies.SetStrategy("Lexicon", ElementStrategy.CreateSingletonElement());

            var strat = ElementStrategy.CreateForKeyedElement("guid", false);
            strat.AttributesToIgnoreForMerging.Add("guid");
            merger.MergeStrategies.SetStrategy("LexEntry", strat);

            strat = ElementStrategy.CreateSingletonElement();
            strat.NumberOfChildren = NumberOfChildrenAllowed.ZeroOrOne;
            merger.MergeStrategies.SetStrategy("Etymology", strat);

            strat = ElementStrategy.CreateForKeyedElement("guid", false);
            strat.AttributesToIgnoreForMerging.Add("guid");
            merger.MergeStrategies.SetStrategy("LexEtymology", strat);

            XmlTestHelper.DoMerge(merger.MergeStrategies, merger.MergeSituation,
                                  commonAncestor, ours, theirs,
                                  new[] { "Lexicon/LexEntry/Etymology" },
                                  null,
                                  0, new List<Type>(),
                                  0, new List<Type>());
        }
Exemple #18
0
        /// <summary>
        /// Add conflict. If RecordContextInConflict fails to set a context, and nodeToFindGeneratorFrom is non-null,
        /// attempt to add a context based on the argument.
        /// </summary>
        public static void AddConflictToListener(IMergeEventListener listener, IConflict conflict, XmlNode oursContext,
												 XmlNode theirsContext, XmlNode ancestorContext,
												 IGenerateHtmlContext htmlContextGenerator, XmlMerger merger, XmlNode nodeToFindGeneratorFrom)
        {
            // NB: All these steps are crucially ordered.
            listener.RecordContextInConflict(conflict);
            if ((conflict.Context == null || conflict.Context is NullContextDescriptor) && nodeToFindGeneratorFrom != null)
            {
                // We are too far up the stack for the listener to have been told a context.
                // Make one out of the current node.
                conflict.Context = merger.GetContext(nodeToFindGeneratorFrom);
            }
            conflict.MakeHtmlDetails(oursContext, theirsContext, ancestorContext, htmlContextGenerator);
            listener.ConflictOccurred(conflict);
        }
Exemple #19
0
 /// <summary>
 /// This is for regular three-way merges.
 /// </summary>
 public MergeTextNodesMethod(XmlMerger merger, IElementDescriber elementDescriber,
                             HashSet <XmlNode> skipInnerMergeFor,
                             ref XmlNode ours, List <XmlNode> ourKeepers,
                             XmlNode theirs, List <XmlNode> theirKeepers,
                             XmlNode ancestor, List <XmlNode> ancestorKeepers)
 {
     _ours              = ours;
     _ourKeepers        = ourKeepers;
     _theirs            = theirs;
     _theirKeepers      = theirKeepers;
     _ancestor          = ancestor;
     _ancestorKeepers   = ancestorKeepers;
     _merger            = merger;
     _elementDescriber  = elementDescriber;
     _skipInnerMergeFor = skipInnerMergeFor;
 }
        /// <summary>
        /// This is for regular three-way merges.
        /// </summary>
        public MergeTextNodesMethod(XmlMerger merger, IElementDescriber elementDescriber,
			HashSet<XmlNode> skipInnerMergeFor,
			ref XmlNode ours, List<XmlNode> ourKeepers,
			XmlNode theirs, List<XmlNode> theirKeepers,
			XmlNode ancestor, List<XmlNode> ancestorKeepers)
        {
            _ours = ours;
            _ourKeepers = ourKeepers;
            _theirs = theirs;
            _theirKeepers = theirKeepers;
            _ancestor = ancestor;
            _ancestorKeepers = ancestorKeepers;
            _merger = merger;
            _elementDescriber = elementDescriber;
            _skipInnerMergeFor = skipInnerMergeFor;
        }
        private static XmlNode HandleTheirsNotPresent(XmlMerger merger, XmlNode ancestor, XmlNode ours)
        {
            // They deleted it,
            var mergeSituation = merger.MergeSituation;

            if (XmlUtilities.AreXmlElementsEqual(ancestor, ours))
            {
                // and we did nothing
                // Route tested, but the MergeChildrenMethod adds the change report for us.
                merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(mergeSituation.PathToFileInRepository, ancestor, ours));
                return(null);
            }

            // but we edited it.
            merger.ConflictOccurred(new RemovedVsEditedElementConflict(ancestor.Name, ours, null, ancestor, mergeSituation, merger.MergeStrategies.GetElementStrategy(ancestor), mergeSituation.AlphaUserId));
            return(ours);
        }
		/// <summary>
		/// Bootstrap a merger for the new-styled (nested) files.
		/// </summary>
		/// <remarks>
		/// 1. A generic 'header' element will be handled, although it may not appear in the file.
		/// 2. All classes will be included.
		/// 3. Merge strategies for class properties (regular or custom) will have keys of "classname+propname" to make them unique, system-wide.
		/// </remarks>
		private static void BootstrapSystem(MetadataCache metadataCache, XmlMerger merger)
		{
			merger.MergeStrategies.ElementToMergeStrategyKeyMapper = new FieldWorksElementToMergeStrategyKeyMapper();

			var sharedElementStrategies = new Dictionary<string, ElementStrategy>();
			CreateSharedElementStrategies(sharedElementStrategies);

			var strategiesForMerger = merger.MergeStrategies;
			ContextGen.MergeStrategies = strategiesForMerger;

			foreach (var sharedKvp in sharedElementStrategies)
				strategiesForMerger.SetStrategy(sharedKvp.Key, sharedKvp.Value);

			var customPropDefnStrat = new ElementStrategy(false)
							{
								MergePartnerFinder = new FindByMultipleKeyAttributes(new List<string> { SharedConstants.Name, SharedConstants.Class }),
								ContextDescriptorGenerator = new FieldWorksCustomPropertyContextGenerator(),
								IsAtomic = true,
								NumberOfChildren = NumberOfChildrenAllowed.Zero
							};
			strategiesForMerger.SetStrategy(SharedConstants.CustomField, customPropDefnStrat);

			var headerStrategy = CreateSingletonElementType(false);
			headerStrategy.ContextDescriptorGenerator = ContextGen;
			strategiesForMerger.SetStrategy(SharedConstants.Header, headerStrategy);

			// There are two abstract class names used: CmAnnotation and DsChart.
			// Chorus knows how to find the matching element for these, as they use <CmAnnotation class='concreteClassname'.
			// So, add a keyed strategy for each of them.
			var keyedStrat = ElementStrategy.CreateForKeyedElement(SharedConstants.GuidStr, false);
			keyedStrat.AttributesToIgnoreForMerging.Add(SharedConstants.Class);
			keyedStrat.AttributesToIgnoreForMerging.Add(SharedConstants.GuidStr);
			strategiesForMerger.SetStrategy(SharedConstants.CmAnnotation, keyedStrat);

			keyedStrat = ElementStrategy.CreateForKeyedElement(SharedConstants.GuidStr, false);
			keyedStrat.AttributesToIgnoreForMerging.Add(SharedConstants.Class);
			keyedStrat.AttributesToIgnoreForMerging.Add(SharedConstants.GuidStr);
			strategiesForMerger.SetStrategy(SharedConstants.DsChart, keyedStrat);

			foreach (var classInfo in metadataCache.AllConcreteClasses)
			{
				MakeClassStrategy(strategiesForMerger, classInfo, ContextGen);
				AddPropertyStrategiesForClass(strategiesForMerger, classInfo);
			}
		}
 private static XmlNode HandleTheirChildNotPresent(XmlMerger merger, XmlNode ours, XmlNode ancestor,
                                                   XmlNode ancestorChild, XmlNode ourChild, MergeSituation mergeSituation,
                                                   IElementDescriber mergeStrategyForChild, string pathToFileInRepository)
 {
     if (XmlUtilities.AreXmlElementsEqual(ancestorChild, ourChild))
     {
         // They deleted it. We did nothing.
         merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(pathToFileInRepository, ancestor, ancestorChild));
     }
     else
     {
         // edit vs delete conflict.
         merger.ConflictOccurred(new EditedVsRemovedElementConflict(ourChild.Name, ourChild, null, ancestorChild,
                                                                    mergeSituation, mergeStrategyForChild,
                                                                    mergeSituation.AlphaUserId));
     }
     return(ours);
 }
 public void DuplicateWritingSystemsElementsAreRemoved()
 {
     const string badData =
     @"<ProjectLexiconSettings>
     <WritingSystems addToSldr='true' />
     <WritingSystems addToSldr='true' goner='true' />
     </ProjectLexiconSettings>";
     var doc = new XmlDocument();
     var badRootNode = XmlUtilities.GetDocumentNodeFromRawXml(badData, doc);
     var merger = new XmlMerger(new NullMergeSituation());
     merger.EventListener = new ListenerForUnitTests();
     ProjectLexiconSettingsFileHandler.SetupElementStrategies(merger);
     var oldValue = XmlMergeService.RemoveAmbiguousChildNodes;
     XmlMergeService.RemoveAmbiguousChildNodes = true;
     XmlMergeService.RemoveAmbiguousChildren(merger.EventListener, merger.MergeStrategies, badRootNode);
     XmlMergeService.RemoveAmbiguousChildNodes = oldValue;
     var childNodes = badRootNode.SelectNodes("WritingSystems");
     Assert.IsNotNull(childNodes);
     Assert.IsTrue(childNodes.Count == 1);
     Assert.IsNull(childNodes[0].Attributes["goner"]);
 }
 public void DuplicateSpecialElementsAreRemoved()
 {
     const string badData =
     @"<ldml>
     <special xmlns:palaso='urn://palaso.org/ldmlExtensions/v1' />
     <special xmlns:palaso='urn://palaso.org/ldmlExtensions/v1' goner='true' />
     <special xmlns:palaso2='urn://palaso.org/ldmlExtensions/v2' />
     <special xmlns:palaso2='urn://palaso.org/ldmlExtensions/v2' goner='true' />
     <special xmlns:fw='urn://fieldworks.sil.org/ldmlExtensions/v1' />
     <special xmlns:fw='urn://fieldworks.sil.org/ldmlExtensions/v1' goner='true' />
     </ldml>";
     var doc = new XmlDocument();
     var badRootNode = XmlUtilities.GetDocumentNodeFromRawXml(badData, doc);
     var merger = new XmlMerger(new NullMergeSituation());
     merger.EventListener = new ListenerForUnitTests();
     LdmlFileHandler.SetupElementStrategies(merger);
     var oldValue = XmlMergeService.RemoveAmbiguousChildNodes;
     XmlMergeService.RemoveAmbiguousChildNodes = true;
     XmlMergeService.RemoveAmbiguousChildren(merger.EventListener, merger.MergeStrategies, badRootNode);
     XmlMergeService.RemoveAmbiguousChildNodes = oldValue;
     var childNodes = badRootNode.SelectNodes("special");
     Assert.IsTrue(childNodes.Count == 3);
     for (var idx = 0; idx < 3; ++idx)
     {
         XmlNode currentNode = childNodes[idx];
         switch (idx)
         {
             case 0:
                 Assert.IsNotNull(currentNode.Attributes["xmlns:palaso"]);
                 break;
             case 1:
                 Assert.IsNotNull(currentNode.Attributes["xmlns:palaso2"]);
                 break;
             case 2:
                 Assert.IsNotNull(currentNode.Attributes["xmlns:fw"]);
                 break;
         }
         Assert.IsNull(currentNode.Attributes["goner"]);
     }
 }
        /// <summary>
        /// Do a 3-file merge, placing the result over the "ours" file and returning an error status
        /// </summary>
        /// <remarks>Implementations can exit with an exception, which the caller will catch and deal with.
        /// The must not have any UI, no interaction with the user.</remarks>
        public void Do3WayMerge(MergeOrder mergeOrder)
        {
            Guard.AgainstNull(mergeOrder, "mergeOrder");

            if (mergeOrder == null)
                throw new ArgumentNullException("mergeOrder");

            var merger = new XmlMerger(mergeOrder.MergeSituation);
            SetupElementStrategies(merger);

            merger.EventListener = mergeOrder.EventListener;

            using(var oursXml = new HtmlFileForMerging(mergeOrder.pathToOurs))
            using(var theirsXml = new HtmlFileForMerging(mergeOrder.pathToTheirs))
            using (var ancestorXml = new HtmlFileForMerging(mergeOrder.pathToCommonAncestor))
            {
                var result = merger.MergeFiles(oursXml.GetPathToXHtml(), theirsXml.GetPathToXHtml(), ancestorXml.GetPathToXHtml());

                CarefullyWriteOutResultingXml(oursXml, result);

                //now convert back to html
                oursXml.SaveHtml();
            }
        }
Exemple #27
0
 private ChangeAndConflictAccumulator CheckOneWay(string ours, string theirs, string ancestor, params string[] xpaths)
 {
     XmlMerger m = new XmlMerger(new NullMergeSituation());
     m.MergeStrategies.ElementStrategies.Add("a", ElementStrategy.CreateForKeyedElementInList("key"));
     m.MergeStrategies.ElementStrategies.Add("b", ElementStrategy.CreateForKeyedElementInList("key"));
     m.MergeStrategies.ElementStrategies.Add("c", ElementStrategy.CreateForKeyedElementInList("key"));
     m.MergeStrategies.ElementStrategies.Add("d", ElementStrategy.CreateForKeyedElementInList("key"));
     var result = m.Merge(ours, theirs, ancestor);
     foreach (string xpath in xpaths)
     {
         XmlTestHelper.AssertXPathMatchesExactlyOne(result.MergedNode, xpath);
     }
     return result;
 }
        internal static void MergeAttributes(XmlMerger merger, ref XmlNode ours, XmlNode theirs, XmlNode ancestor)
        {
            // Review: What happens if 'ours' is null?
            // My (RandyR) theory is that deletions are handled in the MergeChildrenMethod code, as are additions.
            //	That being the case, then that code will only call back to the XmlMerger MergerInner code when all three nodes are present,
            //	and will thus never get here.

            var skipProcessingInOurs = new HashSet<string>();

            // Deletions from ancestor, no matter who did it.
            foreach (var ancestorAttr in XmlUtilities.GetAttrs(ancestor))
            {
                var ourAttr = XmlUtilities.GetAttributeOrNull(ours, ancestorAttr.Name);
                var theirAttr = XmlUtilities.GetAttributeOrNull(theirs, ancestorAttr.Name);
                if (theirAttr == null)
                {
                    if (ourAttr == null)
                    {
                        // Both deleted.
                        // Route tested (x1).
                        merger.EventListener.ChangeOccurred(new XmlAttributeBothDeletedReport(merger.MergeSituation.PathToFileInRepository, ancestorAttr));
                        ancestor.Attributes.Remove(ancestorAttr);
                        continue;
                    }
                    if (ourAttr.Value != ancestorAttr.Value)
                    {
                        // They deleted, but we changed, so we win under the principle of
                        // least data loss (an attribute can be a huge text element).
                        // Route tested (x1).
                        merger.ConflictOccurred(new EditedVsRemovedAttributeConflict(ourAttr.Name, ourAttr.Value, null, ancestorAttr.Value, merger.MergeSituation, merger.MergeSituation.AlphaUserId));
                        continue;
                    }
                    // They deleted. We did zip.
                    // Route tested (x1).
                    if(theirs != null) //if there is no theirs node, then attributes weren't actually removed
                    {
                        //Route tested (x1)
                        merger.EventListener.ChangeOccurred(new XmlAttributeDeletedReport(merger.MergeSituation.PathToFileInRepository, ancestorAttr));
                        ancestor.Attributes.Remove(ancestorAttr);
                        ours.Attributes.Remove(ourAttr);
                    }
                    continue;
                }
                if (ourAttr != null)
                    continue; // Route used.

                // ourAttr == null
                if (ancestorAttr.Value != theirAttr.Value)
                {
                    // We deleted it, but at the same time, they changed it. So just add theirs in, under the principle of
                    // least data loss (an attribute can be a huge text element)
                    // Route tested (x1).
                    skipProcessingInOurs.Add(theirAttr.Name); // Make sure we don't process it again in 'ours' loop, below.
                    var importedAttribute = (XmlAttribute)ours.OwnerDocument.ImportNode(theirAttr.CloneNode(true), true);
                    ours.Attributes.Append(importedAttribute);
                    merger.ConflictOccurred(new RemovedVsEditedAttributeConflict(theirAttr.Name, null, theirAttr.Value, ancestorAttr.Value, merger.MergeSituation, merger.MergeSituation.BetaUserId));
                    continue;
                }
                // We deleted it. They did nothing.
                // Route tested (x1).
                merger.EventListener.ChangeOccurred(new XmlAttributeDeletedReport(merger.MergeSituation.PathToFileInRepository, ancestorAttr));
                ancestor.Attributes.Remove(ancestorAttr);
                theirs.Attributes.Remove(theirAttr);
            }

            var extantNode = ours ?? theirs ?? ancestor;
            foreach (var theirAttr in XmlUtilities.GetAttrs(theirs))
            {
                // Will never return null, since it will use the default one, if it can't find a better one.
                var mergeStrategy = merger.MergeStrategies.GetElementStrategy(extantNode);
                var ourAttr = XmlUtilities.GetAttributeOrNull(ours, theirAttr.Name);
                var ancestorAttr = XmlUtilities.GetAttributeOrNull(ancestor, theirAttr.Name);

                if (ourAttr == null)
                {
                    if (ancestorAttr == null)
                    {
                        // Route tested (x1).
                        skipProcessingInOurs.Add(theirAttr.Name); // Make sure we don't process it again in 'ours loop, below.
                        var importedAttribute = (XmlAttribute)ours.OwnerDocument.ImportNode(theirAttr.CloneNode(true), true);
                        ours.Attributes.Append(importedAttribute);
                        merger.EventListener.ChangeOccurred(new XmlAttributeAddedReport(merger.MergeSituation.PathToFileInRepository, theirAttr));
                    }
                    // NB: Deletes are all handled above in first loop.
                    continue;
                }

                if (ancestorAttr == null) // Both introduced this attribute
                {
                    if (ourAttr.Value == theirAttr.Value)
                    {
                        // Route tested (x1).
                        merger.EventListener.ChangeOccurred(new XmlAttributeBothAddedReport(merger.MergeSituation.PathToFileInRepository, ourAttr));
                        continue;
                    }

                    // Both added, but not the same.
                    if (!mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name))
                    {
                        if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin)
                        {
                            // Route tested (x1).
                            merger.ConflictOccurred(new BothAddedAttributeConflict(theirAttr.Name, ourAttr.Value, theirAttr.Value, merger.MergeSituation,
                                merger.MergeSituation.AlphaUserId));
                        }
                        else
                        {
                            // Route tested (x1).
                            ourAttr.Value = theirAttr.Value;
                            merger.ConflictOccurred(new BothAddedAttributeConflict(theirAttr.Name, ourAttr.Value, theirAttr.Value, merger.MergeSituation,
                                merger.MergeSituation.BetaUserId));
                        }
                    }
                    continue; // Route used.
                }

                if (ancestorAttr.Value == ourAttr.Value)
                {
                    if (ourAttr.Value == theirAttr.Value)
                    {
                        // Route used.
                        continue; // Nothing to do.
                    }

                    // They changed.
                    if (!mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name))
                    {
                        // Route tested (x1).
                        skipProcessingInOurs.Add(theirAttr.Name);
                        merger.EventListener.ChangeOccurred(new XmlAttributeChangedReport(merger.MergeSituation.PathToFileInRepository, theirAttr));
                        ourAttr.Value = theirAttr.Value;
                    }
                    continue;
                }

                if (ourAttr.Value == theirAttr.Value)
                {
                    // Both changed to same value
                    if (skipProcessingInOurs.Contains(theirAttr.Name))
                        continue; // Route used.
                    // Route tested (x1).
                    merger.EventListener.ChangeOccurred(new XmlAttributeBothMadeSameChangeReport(merger.MergeSituation.PathToFileInRepository, ourAttr));
                    continue;
                }
                if (ancestorAttr.Value == theirAttr.Value)
                {
                    // We changed the value. They did nothing.
                    if (!mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name))
                    {
                        // Route tested (x1).
                        merger.EventListener.ChangeOccurred(new XmlAttributeChangedReport(merger.MergeSituation.PathToFileInRepository, ourAttr));
                    }
                    continue;
                }

                if (mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name))
                    continue; // Route used (FileLevelMergeTests)

                //for unit test see Merge_RealConflictPlusModDateConflict_ModDateNotReportedAsConflict()
                if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin)
                {
                    // Route tested (x1).
                    merger.ConflictOccurred(new BothEditedAttributeConflict(theirAttr.Name, ourAttr.Value,
                                                                            theirAttr.Value,
                                                                            ancestorAttr.Value,
                                                                            merger.MergeSituation,
                                                                            merger.MergeSituation.AlphaUserId));
                }
                else
                {
                    // Route tested (x1).
                    ourAttr.Value = theirAttr.Value;
                    merger.ConflictOccurred(new BothEditedAttributeConflict(theirAttr.Name, ourAttr.Value,
                                                                            theirAttr.Value,
                                                                            ancestorAttr.Value,
                                                                            merger.MergeSituation,
                                                                            merger.MergeSituation.BetaUserId));
                }
            }

            foreach (var ourAttr in XmlUtilities.GetAttrs(ours))
            {
                if (skipProcessingInOurs.Contains(ourAttr.Name))
                    continue;

                var theirAttr = XmlUtilities.GetAttributeOrNull(theirs, ourAttr.Name);
                var ancestorAttr = XmlUtilities.GetAttributeOrNull(ancestor, ourAttr.Name);
                if (ancestorAttr != null || theirAttr != null)
                    continue;
                merger.EventListener.ChangeOccurred(new XmlAttributeAddedReport(merger.MergeSituation.PathToFileInRepository, ourAttr));
            }
        }
        public void MergeChildren_UsesNodeToGenerateContextDescriptorIfPossible()
        {
            string ancestor = @"<a>
                                <b key='one'>
                                    <c key='a'>first</c>
                               </b>
                            </a>";

            string red = ancestor;

            string blue = @"<a>
                                <b key='one'>
                                    <c key='a'>first</c>
                                    <c key='b'>second</c>
                                </b>
                            </a>";
            XmlMerger m = new XmlMerger(new NullMergeSituation());
            m.MergeStrategies.ElementStrategies.Add("a", ElementStrategy.CreateForKeyedElementInList("key"));
            var strategy = ElementStrategy.CreateForKeyedElementInList("key");
            var contextGenerator = new MockContextGenerator();
            strategy.ContextDescriptorGenerator = contextGenerator;
            m.MergeStrategies.ElementStrategies.Add("b", strategy);
            m.MergeStrategies.ElementStrategies.Add("c", ElementStrategy.CreateForKeyedElementInList("key"));
            m.MergeStrategies.ElementStrategies.Add("d", ElementStrategy.CreateForKeyedElementInList("key"));
            m.Merge(red, blue, ancestor);
            Assert.That(contextGenerator.InputNode, Is.Not.Null);
            Assert.That(contextGenerator.InputNode.Name, Is.EqualTo("b"));
        }
 public void AllFilesMissingThrows()
 {
     var merger = new XmlMerger(new NullMergeSituation());
     Assert.Throws<InvalidOperationException>(() => merger.MergeFiles(null, null, null));
 }
        internal static void MergeAttributes(XmlMerger merger, ref XmlNode ours, XmlNode theirs, XmlNode ancestor)
        {
            // Review: What happens if 'ours' is null?
            // My (RandyR) theory is that deletions are handled in the MergeChildrenMethod code, as are additions.
            //	That being the case, then that code will only call back to the XmlMerger MergerInner code when all three nodes are present,
            //	and will thus never get here.

            var skipProcessingInOurs = new HashSet <string>();

            // Deletions from ancestor, no matter who did it.
            foreach (var ancestorAttr in XmlUtilities.GetAttrs(ancestor))
            {
                var ourAttr   = XmlUtilities.GetAttributeOrNull(ours, ancestorAttr.Name);
                var theirAttr = XmlUtilities.GetAttributeOrNull(theirs, ancestorAttr.Name);
                if (theirAttr == null)
                {
                    if (ourAttr == null)
                    {
                        // Both deleted.
                        // Route tested (x1).
                        merger.EventListener.ChangeOccurred(new XmlAttributeBothDeletedReport(merger.MergeSituation.PathToFileInRepository, ancestorAttr));
                        ancestor.Attributes.Remove(ancestorAttr);
                        continue;
                    }
                    if (ourAttr.Value != ancestorAttr.Value)
                    {
                        // They deleted, but we changed, so we win under the principle of
                        // least data loss (an attribute can be a huge text element).
                        // Route tested (x1).
                        merger.ConflictOccurred(new EditedVsRemovedAttributeConflict(ourAttr.Name, ourAttr.Value, null, ancestorAttr.Value, merger.MergeSituation, merger.MergeSituation.AlphaUserId));
                        continue;
                    }
                    // They deleted. We did zip.
                    // Route tested (x1).
                    if (theirs != null)                    //if there is no theirs node, then attributes weren't actually removed
                    {
                        //Route tested (x1)
                        merger.EventListener.ChangeOccurred(new XmlAttributeDeletedReport(merger.MergeSituation.PathToFileInRepository, ancestorAttr));
                        ancestor.Attributes.Remove(ancestorAttr);
                        ours.Attributes.Remove(ourAttr);
                    }
                    continue;
                }
                if (ourAttr != null)
                {
                    continue;                     // Route used.
                }
                // ourAttr == null
                if (ancestorAttr.Value != theirAttr.Value)
                {
                    // We deleted it, but at the same time, they changed it. So just add theirs in, under the principle of
                    // least data loss (an attribute can be a huge text element)
                    // Route tested (x1).
                    skipProcessingInOurs.Add(theirAttr.Name);                     // Make sure we don't process it again in 'ours' loop, below.
                    var importedAttribute = (XmlAttribute)ours.OwnerDocument.ImportNode(theirAttr.CloneNode(true), true);
                    ours.Attributes.Append(importedAttribute);
                    merger.ConflictOccurred(new RemovedVsEditedAttributeConflict(theirAttr.Name, null, theirAttr.Value, ancestorAttr.Value, merger.MergeSituation, merger.MergeSituation.BetaUserId));
                    continue;
                }
                // We deleted it. They did nothing.
                // Route tested (x1).
                merger.EventListener.ChangeOccurred(new XmlAttributeDeletedReport(merger.MergeSituation.PathToFileInRepository, ancestorAttr));
                ancestor.Attributes.Remove(ancestorAttr);
                theirs.Attributes.Remove(theirAttr);
            }

            var extantNode = ours ?? theirs ?? ancestor;

            foreach (var theirAttr in XmlUtilities.GetAttrs(theirs))
            {
                // Will never return null, since it will use the default one, if it can't find a better one.
                var mergeStrategy = merger.MergeStrategies.GetElementStrategy(extantNode);
                var ourAttr       = XmlUtilities.GetAttributeOrNull(ours, theirAttr.Name);
                var ancestorAttr  = XmlUtilities.GetAttributeOrNull(ancestor, theirAttr.Name);

                if (ourAttr == null)
                {
                    if (ancestorAttr == null)
                    {
                        // Route tested (x1).
                        skipProcessingInOurs.Add(theirAttr.Name);                         // Make sure we don't process it again in 'ours loop, below.
                        var importedAttribute = (XmlAttribute)ours.OwnerDocument.ImportNode(theirAttr.CloneNode(true), true);
                        ours.Attributes.Append(importedAttribute);
                        merger.EventListener.ChangeOccurred(new XmlAttributeAddedReport(merger.MergeSituation.PathToFileInRepository, theirAttr));
                    }
                    // NB: Deletes are all handled above in first loop.
                    continue;
                }

                if (ancestorAttr == null)                 // Both introduced this attribute
                {
                    if (ourAttr.Value == theirAttr.Value)
                    {
                        // Route tested (x1).
                        merger.EventListener.ChangeOccurred(new XmlAttributeBothAddedReport(merger.MergeSituation.PathToFileInRepository, ourAttr));
                        continue;
                    }

                    // Both added, but not the same.
                    if (!mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name))
                    {
                        if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin)
                        {
                            // Route tested (x1).
                            merger.ConflictOccurred(new BothAddedAttributeConflict(theirAttr.Name, ourAttr.Value, theirAttr.Value, merger.MergeSituation,
                                                                                   merger.MergeSituation.AlphaUserId));
                        }
                        else
                        {
                            // Route tested (x1).
                            ourAttr.Value = theirAttr.Value;
                            merger.ConflictOccurred(new BothAddedAttributeConflict(theirAttr.Name, ourAttr.Value, theirAttr.Value, merger.MergeSituation,
                                                                                   merger.MergeSituation.BetaUserId));
                        }
                    }
                    continue;                     // Route used.
                }

                if (ancestorAttr.Value == ourAttr.Value)
                {
                    if (ourAttr.Value == theirAttr.Value)
                    {
                        // Route used.
                        continue;                         // Nothing to do.
                    }

                    // They changed.
                    if (!mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name))
                    {
                        // Route tested (x1).
                        skipProcessingInOurs.Add(theirAttr.Name);
                        merger.EventListener.ChangeOccurred(new XmlAttributeChangedReport(merger.MergeSituation.PathToFileInRepository, theirAttr));
                        ourAttr.Value = theirAttr.Value;
                    }
                    continue;
                }

                if (ourAttr.Value == theirAttr.Value)
                {
                    // Both changed to same value
                    if (skipProcessingInOurs.Contains(theirAttr.Name))
                    {
                        continue;                         // Route used.
                    }
                    // Route tested (x1).
                    merger.EventListener.ChangeOccurred(new XmlAttributeBothMadeSameChangeReport(merger.MergeSituation.PathToFileInRepository, ourAttr));
                    continue;
                }
                if (ancestorAttr.Value == theirAttr.Value)
                {
                    // We changed the value. They did nothing.
                    if (!mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name))
                    {
                        // Route tested (x1).
                        merger.EventListener.ChangeOccurred(new XmlAttributeChangedReport(merger.MergeSituation.PathToFileInRepository, ourAttr));
                    }
                    continue;
                }

                if (mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name))
                {
                    continue;                     // Route used (FileLevelMergeTests)
                }
                //for unit test see Merge_RealConflictPlusModDateConflict_ModDateNotReportedAsConflict()
                if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin)
                {
                    // Route tested (x1).
                    merger.ConflictOccurred(new BothEditedAttributeConflict(theirAttr.Name, ourAttr.Value,
                                                                            theirAttr.Value,
                                                                            ancestorAttr.Value,
                                                                            merger.MergeSituation,
                                                                            merger.MergeSituation.AlphaUserId));
                }
                else
                {
                    // Route tested (x1).
                    ourAttr.Value = theirAttr.Value;
                    merger.ConflictOccurred(new BothEditedAttributeConflict(theirAttr.Name, ourAttr.Value,
                                                                            theirAttr.Value,
                                                                            ancestorAttr.Value,
                                                                            merger.MergeSituation,
                                                                            merger.MergeSituation.BetaUserId));
                }
            }

            foreach (var ourAttr in XmlUtilities.GetAttrs(ours))
            {
                if (skipProcessingInOurs.Contains(ourAttr.Name))
                {
                    continue;
                }

                var theirAttr    = XmlUtilities.GetAttributeOrNull(theirs, ourAttr.Name);
                var ancestorAttr = XmlUtilities.GetAttributeOrNull(ancestor, ourAttr.Name);
                if (ancestorAttr != null || theirAttr != null)
                {
                    continue;
                }
                merger.EventListener.ChangeOccurred(new XmlAttributeAddedReport(merger.MergeSituation.PathToFileInRepository, ourAttr));
            }
        }
Exemple #32
0
        internal static void SetupElementStrategies(XmlMerger merger)
        {
            merger.MergeStrategies.ElementToMergeStrategyKeyMapper = new LdmlElementToMergeStrategyKeyMapper();

            // See: Palaso repo: SIL.WritingSystems\LdmlDataMapper.cs
            var strategy = ElementStrategy.CreateSingletonElement();
            strategy.ContextDescriptorGenerator = new LdmlContextGenerator();
            merger.MergeStrategies.SetStrategy("ldml", strategy);
            // Child elements of ldml root.

            merger.MergeStrategies.SetStrategy("identity", ElementStrategy.CreateSingletonElement());
            // Child elements of "identity".
            merger.MergeStrategies.SetStrategy("version", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("generation", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("language", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("script", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("territory", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("variant", ElementStrategy.CreateSingletonElement());

            // sil:special can occur several times throughout the file
            merger.MergeStrategies.SetStrategy("special_xmlns:sil", new ElementStrategy(false)
            {
                MergePartnerFinder = new FindByMatchingAttributeNames(new HashSet<string> { "xmlns:sil" })
            });
            merger.MergeStrategies.SetStrategy("sil:identity", ElementStrategy.CreateSingletonElement());

            merger.MergeStrategies.SetStrategy("localeDisplayNames", ElementStrategy.CreateSingletonElement());

            merger.MergeStrategies.SetStrategy("layout", ElementStrategy.CreateSingletonElement());
            // Child element of "layout".
            merger.MergeStrategies.SetStrategy("orientation", new ElementStrategy(false)
            {
                IsAtomic = true,
                MergePartnerFinder = new FindFirstElementWithSameName()
            });

            merger.MergeStrategies.SetStrategy("contextTransforms", ElementStrategy.CreateSingletonElement());

            merger.MergeStrategies.SetStrategy("characters", new ElementStrategy(false)
            {
                IsAtomic = true,
                MergePartnerFinder = new FindFirstElementWithSameName()
            });

            merger.MergeStrategies.SetStrategy("delimiters", new ElementStrategy(false)
            {
                IsAtomic = true,
                MergePartnerFinder = new FindFirstElementWithSameName()
            });

            merger.MergeStrategies.SetStrategy("dates", ElementStrategy.CreateSingletonElement());

            merger.MergeStrategies.SetStrategy("numbers", new ElementStrategy(false)
            {
                IsAtomic = true,
                MergePartnerFinder = new FindFirstElementWithSameName()
            });

            merger.MergeStrategies.SetStrategy("units", ElementStrategy.CreateSingletonElement());

            merger.MergeStrategies.SetStrategy("listPatterns", ElementStrategy.CreateSingletonElement());

            merger.MergeStrategies.SetStrategy("collations", ElementStrategy.CreateSingletonElement());
            // Child element of collations
            strategy = new ElementStrategy(false)
            {
                IsAtomic = true, // I (RBR) think it would be suicidal to try and merge this element.
                MergePartnerFinder = new FindByKeyAttribute("type")
            };
            merger.MergeStrategies.SetStrategy("collation", strategy);
            // Child of 'collation' element (They exist, but we don't care what they are, as long as the parent is 'atomic'.

            merger.MergeStrategies.SetStrategy("posix", ElementStrategy.CreateSingletonElement());

            merger.MergeStrategies.SetStrategy("segmentations", ElementStrategy.CreateSingletonElement());

            merger.MergeStrategies.SetStrategy("rbnf", ElementStrategy.CreateSingletonElement());

            merger.MergeStrategies.SetStrategy("metadata", ElementStrategy.CreateSingletonElement());

            // See: Palaso repo: SIL.WritingSystems\LdmlDataMapper.cs
            // There currently are up to three 'special' child elements of the 'ldml' root element.
            // Special "xmlns:palaso" attr
            strategy = new ElementStrategy(false)
            {
                IsAtomic = true, // May not be needed...
                MergePartnerFinder = new FindByMatchingAttributeNames(new HashSet<string> { "xmlns:palaso" })
            };
            merger.MergeStrategies.SetStrategy("special_xmlns:palaso", strategy);
            /* Not needed, as long as the parent is 'atomic'.
            // Children of 'special' xmlns:palaso
            // palaso:abbreviation
            merger.MergeStrategies.SetStrategy("palaso:abbreviation", ElementStrategy.CreateSingletonElement());
            // palaso:defaultFontFamily
            merger.MergeStrategies.SetStrategy("palaso:defaultFontFamily", ElementStrategy.CreateSingletonElement());
            // palaso:defaultFontSize
            merger.MergeStrategies.SetStrategy("palaso:defaultFontSize", ElementStrategy.CreateSingletonElement());
            // palaso:defaultKeyboard
            merger.MergeStrategies.SetStrategy("palaso:defaultKeyboard", ElementStrategy.CreateSingletonElement());
            // palaso:isLegacyEncoded
            merger.MergeStrategies.SetStrategy("palaso:isLegacyEncoded", ElementStrategy.CreateSingletonElement());
            // palaso:languageName
            merger.MergeStrategies.SetStrategy("palaso:languageName", ElementStrategy.CreateSingletonElement());
            // palaso:spellCheckingId
            merger.MergeStrategies.SetStrategy("palaso:spellCheckingId", ElementStrategy.CreateSingletonElement());
            // palaso:version
            merger.MergeStrategies.SetStrategy("palaso:version", ElementStrategy.CreateSingletonElement());
            */

            // See: Palaso repo: SIL.WritingSystems\LdmlDataMapper.cs
            // special "xmlns:palaso2" attr: want to merge knownKeyboards child. So the root element is not atomic.
            strategy = new ElementStrategy(false)
            {
                MergePartnerFinder = new FindByMatchingAttributeNames(new HashSet<string> {"xmlns:palaso2"})
            };
            merger.MergeStrategies.SetStrategy("special_xmlns:palaso2", strategy);
            // Children of 'strategy' xmlns:palaso2
            // palaso2:knownKeyboards:
            merger.MergeStrategies.SetStrategy("palaso2:knownKeyboards", ElementStrategy.CreateSingletonElement());
            // Multiple children of "palaso2:knownKeyboards" element
            strategy = new ElementStrategy(false)
            {
                MergePartnerFinder = new FindByMultipleKeyAttributes(new List<string> {"layout", "locale"})
            };
            merger.MergeStrategies.SetStrategy("palaso2:keyboard", strategy);
            merger.MergeStrategies.SetStrategy("palaso2:version", ElementStrategy.CreateSingletonElement());

            // Special "xmlns:fw" attr (See FW source file: Src\Common\CoreImpl\PalasoWritingSystemManager.cs
            strategy = new ElementStrategy(false)
            {
                IsAtomic = true, // Really is needed. At least it is for some child elements.
                MergePartnerFinder = new FindByMatchingAttributeNames(new HashSet<string> { "xmlns:fw" })
            };
            merger.MergeStrategies.SetStrategy("special_xmlns:fw", strategy);
            /* Not needed, as long as the parent is 'atomic'.
            // Children for 'special' xmlns:fw
            merger.MergeStrategies.SetStrategy("fw:defaultFontFeatures", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("fw:graphiteEnabled", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("fw:legacyMapping", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("fw:matchedPairs", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("fw:punctuationPatterns", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("fw:quotationMarks", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("fw:regionName", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("fw:scriptName", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("fw:validChars", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("fw:variantName", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("fw:windowsLCID", ElementStrategy.CreateSingletonElement());
            */

            // Children for top level 'special' xmlns:sil
            merger.MergeStrategies.SetStrategy("sil:external-resources", ElementStrategy.CreateSingletonElement());
            merger.MergeStrategies.SetStrategy("sil:kbd", new ElementStrategy(false)
                {
                    IsAtomic = true,
                    MergePartnerFinder = new FindByMultipleKeyAttributes(new List<string>{"id", "alt"})
            }
            );
            merger.MergeStrategies.SetStrategy("sil:font", new ElementStrategy(false)
                {
                    IsAtomic = true,
                    MergePartnerFinder = new FindByMultipleKeyAttributes(new List<string>{"name", "alt"})
                }
            );
            merger.MergeStrategies.SetStrategy("sil:spellcheck", new ElementStrategy(false)
                {
                    IsAtomic = true,
                    MergePartnerFinder = new FindByMultipleKeyAttributes(new List<string>{"type", "alt"})
                }
            );
            merger.MergeStrategies.SetStrategy("sil:transform", new ElementStrategy(false)
                {
                    IsAtomic = true,
                    MergePartnerFinder = new FindByMultipleKeyAttributes(new List<string> { "from", "to", "type", "direction", "function", "alt" })
                }
            );
        }
 private static XmlMerger GetMerger(MergeSituation mergeSituation, out ListenerForUnitTests listener)
 {
     var elementStrategy = new ElementStrategy(false)
     {
         IsAtomic = true
     };
     var merger = new XmlMerger(mergeSituation);
     merger.MergeStrategies.SetStrategy("topatomic", elementStrategy);
     listener = new ListenerForUnitTests();
     merger.EventListener = listener;
     return merger;
 }
Exemple #34
0
 /// <summary>
 /// Use this one for a diff of one xml node against another
 /// </summary>
 public MergeChildrenMethod(XmlNode after, XmlNode before, XmlMerger merger)
     : this(after, before, before, merger)
 {
 }
        internal static void DoMerge(XmlMerger merger, ref XmlNode ours, XmlNode theirs, XmlNode ancestor)
        {
            if (ours == null && theirs == null && ancestor == null)
            {
                return;                 // I don't think this is possible, but....
            }
            if (ancestor == null)
            {
                // Somebody added it.
                if (ours == null && theirs != null)
                {
                    // They added it.
                    merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(merger.MergeSituation.PathToFileInRepository, theirs));
                    ours = theirs;
                    return;
                }
                if (theirs == null && ours != null)
                {
                    // We added it.
                    merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(merger.MergeSituation.PathToFileInRepository, ours));
                    return;
                }

                // Both added.
                if (XmlUtilities.AreXmlElementsEqual(ours, theirs))
                {
                    // Both added the same thing.
                    merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(merger.MergeSituation.PathToFileInRepository, ours));
                    return;
                }

                // Both added it, but it isn't the same thing.
                if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin)
                {
                    // We win.
                    merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(ours.Name, ours, theirs, merger.MergeSituation, merger.MergeStrategies.GetElementStrategy(ours), merger.MergeSituation.AlphaUserId));
                }
                else
                {
                    // They win.
                    merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(ours.Name, theirs, ours, merger.MergeSituation, merger.MergeStrategies.GetElementStrategy(theirs), merger.MergeSituation.BetaUserId));
                    ours = theirs;
                    return;
                }
                return;
            }

            // ancestor is not null here.
            if (ours == null)
            {
                if (theirs == null)
                {
                    // Both deleted it. Add change report.
                    merger.EventListener.ChangeOccurred(new XmlBothDeletionChangeReport(merger.MergeSituation.PathToFileInRepository, ancestor));
                }
                else
                {
                    // We deleted. We don't care if they made any changes, since such changes aren't legal.
                    merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(merger.MergeSituation.PathToFileInRepository, ancestor.ParentNode, ancestor));
                }
                return;
            }
            if (theirs == null)
            {
                // They deleted.  We don't care if we made any changes, since such changes aren't legal.
                merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(merger.MergeSituation.PathToFileInRepository, ancestor.ParentNode, ancestor));
                ours = null;
                return;
            }

            // Nothing is null here.
            if (!XmlUtilities.AreXmlElementsEqual(ours, ancestor))
            {
                ours = ancestor;                 // Restore ours to ancestor, since ours was changed by some buggy client code or by a hand edit.
            }
            // At this point, we don't need to test if ancestor and theirs are the same, or not,
            // since we have restored current (ours) to the original, or ours was the same as ancestor.
            // There is probably no point to adding some kind of warning.
        }
        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);
        }
        private static XmlNode HandleCaseOfNoAncestor(XmlMerger merger, XmlNode ours, XmlNode theirs)
        {
            var mainNodeStrategy       = merger.MergeStrategies.GetElementStrategy(ours ?? theirs);
            var mergeSituation         = merger.MergeSituation;
            var pathToFileInRepository = mergeSituation.PathToFileInRepository;

            if (ours == null)
            {
                // They added, we did nothing.
// Route tested, but the MergeChildrenMethod adds the change report for us.
                // So, theory has it one can't get here from any normal place.
                // But, keep it, in case MergeChildrenMethod gets 'fixed'.
                merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, theirs));
                return(theirs);
            }
            if (theirs == null)
            {
                // We added, they did nothing.
// Route tested.
                merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, ours));
                return(ours);
            }

            // Both added the special containing node.
            // Remove children nodes to see if main containing nodes are the same.
            if (XmlUtilities.AreXmlElementsEqual(ours, theirs))
            {
// Route tested.
                // Same content.
                merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(pathToFileInRepository, ours));
            }
            else
            {
                // Different content.
                var ourChild   = GetElementChildren(ours).FirstOrDefault();
                var theirChild = GetElementChildren(theirs).FirstOrDefault();
                var ourClone   = MakeClone(ours);
                var theirClone = MakeClone(theirs);
                if (XmlUtilities.AreXmlElementsEqual(ourClone, theirClone))
                {
                    // new main elements are the same, but not the contained child
                    merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(pathToFileInRepository, ourClone));
                    if (ourChild == null && theirChild == null)
                    {
                        return(ours);                        // Nobody added the child node.
                    }
                    if (ourChild == null)
                    {
                        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)
                    {
                        merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, ourChild));
                        return(ours);
                    }
                    // both children exist, but are different.
                    var mergeStrategyForChild = merger.MergeStrategies.GetElementStrategy(ourChild);
                    if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin)
                    {
                        // Do the clone thing on the two child nodes to see if the diffs are in the child or lower down.
                        var ourChildClone   = MakeClone(ourChild);
                        var theirChildClone = MakeClone(theirChild);
                        if (XmlUtilities.AreXmlElementsEqual(ourChildClone, theirChildClone))
                        {
                            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
                    {
                        // Do the clone thing on the two child nodes to see if the diffs are in the child or lower down.
                        var ourChildClone   = MakeClone(ourChild);
                        var theirChildClone = MakeClone(theirChild);
                        if (XmlUtilities.AreXmlElementsEqual(ourChildClone, theirChildClone))
                        {
                            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));
                            XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, theirChild);
                        }
                    }
                    return(ours);
                }

                // Main containing element is not the same. Not to worry about child
                if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin)
                {
                    merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(ours.Name, ours, theirs,
                                                                                                    mergeSituation, mainNodeStrategy,
                                                                                                    mergeSituation.AlphaUserId));
                }
                else
                {
                    merger.ConflictOccurred(new XmlTextBothAddedTextConflict(theirs.Name, theirs, ours, mergeSituation,
                                                                             mainNodeStrategy, mergeSituation.BetaUserId));
                    XmlUtilities.ReplaceOursWithTheirs(ours.ParentNode, ref ours, theirs);
                }
            }
            return(ours);
        }
        private static XmlNode Run(XmlMerger merger, XmlNode ours, XmlNode theirs, XmlNode ancestor)
        {
            if (ours == null && theirs == null && ancestor == null)
            {
                return(null);
            }

            if (ancestor == null)
            {
                return(HandleCaseOfNoAncestor(merger, ours, theirs));
            }
            // ancestor is not null at this point.
            var mergeSituation         = merger.MergeSituation;
            var pathToFileInRepository = mergeSituation.PathToFileInRepository;

            if (ours == null && theirs == null)
            {
                // We both deleted main node.
// Route tested, but the MergeChildrenMethod adds the change report for us.
                merger.EventListener.ChangeOccurred(new XmlBothDeletionChangeReport(pathToFileInRepository, ancestor));
                return(null);
            }
            if (ours == null)
            {
                return(HandleOursNotPresent(merger, ancestor, theirs));
            }
            if (theirs == null)
            {
                return(HandleTheirsNotPresent(merger, ancestor, ours));
            }
            // End of checking main parent node.

            // ancestor, ours, and theirs all exist here.
            var ourChild      = GetElementChildren(ours).FirstOrDefault();
            var theirChild    = GetElementChildren(theirs).FirstOrDefault();
            var ancestorChild = GetElementChildren(ancestor).FirstOrDefault();

            if (ourChild == null && theirChild == null && ancestorChild == null)
            {
                return(ours);                // All three are childless.
            }

            if (ancestorChild == null)
            {
                return(HandleCaseOfNoAncestorChild(merger, ours, ourChild, theirChild));
            }
            var mergeStrategyForChild = merger.MergeStrategies.GetElementStrategy(ancestorChild);

            if (ourChild == null)
            {
                return(HandleOurChildNotPresent(merger, ours, ancestor, theirChild, pathToFileInRepository, ancestorChild, mergeSituation, mergeStrategyForChild));
            }
            if (theirChild == null)
            {
                return(HandleTheirChildNotPresent(merger, ours, ancestor, ancestorChild, ourChild, mergeSituation, mergeStrategyForChild, pathToFileInRepository));
            }

            // ancestorChild, ourChild, and theirChild all exist.
            // But, it could be that we or they deleted and added something.
            // Check for edit vs delete+add, or there can be two items in ours, which is not legal.
            var match = mergeStrategyForChild.MergePartnerFinder.GetNodeToMerge(ourChild, ancestor, SetFromChildren.Get(ancestor));

            if (match == null)
            {
                // we deleted it and added a new one.
                if (XmlUtilities.AreXmlElementsEqual(theirChild, ancestorChild))
                {
                    // Our delete+add wins, since they did nothing.
                    merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(pathToFileInRepository, ancestor, ancestorChild));
                    merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, ourChild));
                    return(ours);
                }

                // They edited old one, so they win over our delete+add.
                merger.ConflictOccurred(new RemovedVsEditedElementConflict(theirChild.Name, theirChild, null, ancestorChild,
                                                                           mergeSituation, mergeStrategyForChild,
                                                                           mergeSituation.BetaUserId));
                XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, theirChild);
                return(ours);
            }
            match = mergeStrategyForChild.MergePartnerFinder.GetNodeToMerge(theirChild, ancestor, SetFromChildren.Get(ancestor));
            if (match == null)
            {
                // they deleted it and added a new one.
                if (XmlUtilities.AreXmlElementsEqual(ourChild, ancestorChild))
                {
                    // Their delete+add wins, since we did nothing.
                    merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(pathToFileInRepository, ancestor, ancestorChild));
                    merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, theirChild));
                    XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, theirChild);
                    return(ours);
                }

                // We edited old one, so we win over their delete+add.
                merger.ConflictOccurred(new RemovedVsEditedElementConflict(ourChild.Name, ourChild, null, ancestorChild,
                                                                           mergeSituation, mergeStrategyForChild,
                                                                           mergeSituation.AlphaUserId));
                return(ours);
            }
            merger.MergeInner(ours, ref ourChild, theirChild, ancestorChild);
// Route tested. (UsingWith_NumberOfChildrenAllowed_ZeroOrOne_DoesNotThrowWhenParentHasOneChildNode)
            return(ours);
        }
Exemple #39
0
 protected override void AddMergeStrategies(XmlMerger m)
 {
     m.MergeStrategies.ElementStrategies.Add("a", ElementStrategy.CreateForKeyedElementInList("key"));
     m.MergeStrategies.ElementStrategies.Add("b", ElementStrategy.CreateForKeyedElementInList("key"));
     m.MergeStrategies.ElementStrategies.Add("c", ElementStrategy.CreateForKeyedElementInList("key"));
     m.MergeStrategies.ElementStrategies.Add("d", ElementStrategy.CreateForKeyedElementInList("key"));
 }
        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;
            }
        }
        public void DeleteAtomicElementVsModifyHasConflict()
        {
            const string commonAncestor =
            @"<Lexicon>
            <LexEntry guid='ffdc58c9-5cc3-469f-9118-9f18c0138d02'>
            <MorphoSyntaxAnalyses>
            <MoStemMsa
                guid='33adabe9-a02e-42cb-b942-277a7be5c841'>
                <PartOfSpeech>
                    <objsur
                        guid='e72dbc59-e93f-4df2-b6bd-39a53e331201'
                        t='r' />
                </PartOfSpeech>
            </MoStemMsa>
            </MorphoSyntaxAnalyses>
            <Senses/>
            </LexEntry>
            </Lexicon>";
            const string matthew =
            @"<Lexicon>
            <LexEntry guid='ffdc58c9-5cc3-469f-9118-9f18c0138d02'>
            <MorphoSyntaxAnalyses>
            <MoStemMsa
                guid='33adabe9-a02e-42cb-b942-277a7be5c841'>
                <PartOfSpeech>
                    <objsur
                        guid='f92dbc59-e93f-4df2-b6bd-39a53e331201'
                        t='r' />
                </PartOfSpeech>
            </MoStemMsa>
            </MorphoSyntaxAnalyses>
            <Senses/>
            </LexEntry>
            </Lexicon>";
            const string lee =
            @"<Lexicon>
            <LexEntry guid='ffdc58c9-5cc3-469f-9118-9f18c0138d02'>
            <MorphoSyntaxAnalyses>
            <MoStemMsa
                guid='33adabe9-a02e-42cb-b942-277a7be5c841'>
                <PartOfSpeech />
            </MoStemMsa>
            </MorphoSyntaxAnalyses>
            <Senses/>
            </LexEntry>
            </Lexicon>";

            var listener = new ListenerForUnitTests();
            var merger = new XmlMerger(new NullMergeSituation())
            {
                EventListener = listener
            };
            merger.MergeStrategies.SetStrategy("Lexicon", ElementStrategy.CreateSingletonElement());

            var strat = ElementStrategy.CreateForKeyedElement("guid", false);
            strat.AttributesToIgnoreForMerging.Add("guid");
            merger.MergeStrategies.SetStrategy("LexEntry", strat);

            strat = ElementStrategy.CreateSingletonElement();
            strat.NumberOfChildren = NumberOfChildrenAllowed.ZeroOrOne;
            merger.MergeStrategies.SetStrategy("MorphoSyntaxAnalyses", strat);

            strat = ElementStrategy.CreateForKeyedElement("guid", false);
            strat.AttributesToIgnoreForMerging.Add("guid");
            merger.MergeStrategies.SetStrategy("MoStemMsa", strat);

            strat = ElementStrategy.CreateSingletonElement();
            strat.NumberOfChildren = NumberOfChildrenAllowed.ZeroOrOne;
            merger.MergeStrategies.SetStrategy("PartOfSpeech", strat);

            strat = ElementStrategy.CreateSingletonElement();
            strat.IsAtomic = true;
            merger.MergeStrategies.SetStrategy("objsur", strat);

            XmlTestHelper.DoMerge(merger.MergeStrategies,
                merger.MergeSituation,
                commonAncestor, lee, matthew,
                new[] { "Lexicon/LexEntry/MorphoSyntaxAnalyses/MoStemMsa/PartOfSpeech/objsur[@guid='f92dbc59-e93f-4df2-b6bd-39a53e331201']" },
                null,
                1, new List<Type> { typeof(RemovedVsEditedElementConflict) },
                0, new List<Type>());
        }
 public void AncestorIsEmptyFileAndWeBothAddedDifferentContentWithTheyWinHasOneConflictReport()
 {
     const string data =
     @"<?xml version='1.0' encoding='utf-8'?>
     <data />";
     using (var ancestor = new TempFile())
     using (var ours = new TempFile())
     using (var theirs = new TempFile())
     {
         File.WriteAllText(ancestor.Path, "");
         File.WriteAllText(ours.Path, data.Replace("<data />", "<data>our addition</data>"));
         File.WriteAllText(theirs.Path, data.Replace("<data />", "<data>their addition</data>"));
         var listener = new ListenerForUnitTests();
         var merger = new XmlMerger(new NullMergeSituationTheyWin())
         {
             EventListener = listener
         };
         var result = merger.MergeFiles(ours.Path, theirs.Path, ancestor.Path);
         Assert.IsNotNull(result);
         listener.AssertExpectedChangesCount(0);
         listener.AssertExpectedConflictCount(1);
         listener.AssertFirstConflictType<BothAddedMainElementButWithDifferentContentConflict>();
         Assert.IsTrue(result.MergedNode.OuterXml.Contains("their addition"));
     }
 }
 private static XmlMerger GetMerger(out ListenerForUnitTests listener, bool isAtomic)
 {
     var elementStrategy = new ElementStrategy(false)
         {
             IsAtomic = isAtomic
         };
     var merger = new XmlMerger(new NullMergeSituation());
     merger.MergeStrategies.SetStrategy("topatomic", elementStrategy);
     listener = new ListenerForUnitTests();
     merger.EventListener = listener;
     return merger;
 }
 public void AncestorIsNotXmlThrows()
 {
     const string data =
     @"<?xml version='1.0' encoding='utf-8'?>
     <data />";
     using (var ancestor = new TempFile())
     using (var ours = new TempFile())
     using (var theirs = new TempFile())
     {
         File.WriteAllText(ancestor.Path, "Not xml stuff.");
         File.WriteAllText(ours.Path, data);
         File.WriteAllText(theirs.Path, data);
         var merger = new XmlMerger(new NullMergeSituation());
         Assert.Throws<XmlException>(() => merger.MergeFiles(ours.Path, theirs.Path, ancestor.Path));
     }
 }
Exemple #45
0
        /// <summary>
        /// Do a 3-file merge, placing the result over the "ours" file and returning an error status
        /// </summary>
        /// <remarks>Implementations can exit with an exception, which the caller will catch and deal with.
        /// The must not have any UI, no interaction with the user.</remarks>
        public void Do3WayMerge(MergeOrder mergeOrder)
        {
            if (mergeOrder == null)
                throw new ArgumentNullException("mergeOrder");

            bool addedCollationAttr;
            PreMergeFile(mergeOrder, out addedCollationAttr);

            var merger = new XmlMerger(mergeOrder.MergeSituation);
            SetupElementStrategies(merger);

            merger.EventListener = mergeOrder.EventListener;
            XmlMergeService.RemoveAmbiguousChildNodes = true;
            var result = merger.MergeFiles(mergeOrder.pathToOurs, mergeOrder.pathToTheirs, mergeOrder.pathToCommonAncestor);
            using (var writer = XmlWriter.Create(mergeOrder.pathToOurs, CanonicalXmlSettings.CreateXmlWriterSettings()))
            {
                var nameSpaceManager = new XmlNamespaceManager(new NameTable());
                nameSpaceManager.AddNamespace("palaso", "urn://palaso.org/ldmlExtensions/v1");
                nameSpaceManager.AddNamespace("palaso2", "urn://palaso.org/ldmlExtensions/v2");
                nameSpaceManager.AddNamespace("fw", "urn://fieldworks.sil.org/ldmlExtensions/v1");
                nameSpaceManager.AddNamespace("sil", "urn://www.sil.org/ldml/0.1");

                var readerSettings = CanonicalXmlSettings.CreateXmlReaderSettings(ConformanceLevel.Auto);
                readerSettings.NameTable = nameSpaceManager.NameTable;
                readerSettings.XmlResolver = null;
                readerSettings.ProhibitDtd = false;
                if (addedCollationAttr)
                {
                    // Remove the optional 'key' attr we added.
                    var adjustedCollation = result.MergedNode.SelectSingleNode("collations")
                        .SelectNodes("collation")
                        .Cast<XmlNode>().FirstOrDefault(collation => collation.Attributes["type"].Value == "standard");
                    if (adjustedCollation != null)
                    {
                        adjustedCollation.Attributes.Remove(adjustedCollation.Attributes["type"]);
                    }
                }
                using (var nodeReader = XmlReader.Create(new MemoryStream(Encoding.UTF8.GetBytes(result.MergedNode.OuterXml)), readerSettings))
                {
                    writer.WriteNode(nodeReader, false);
                }
            }
        }
Exemple #46
0
        /// <summary>
        /// "Merge" element if it is 'atomic' and return true. Otherwise, do nothing and return false.
        /// </summary>
        /// <remarks>
        /// <c>ours</c> may be changed to <c>theirs</c> if <c>ours</c> is null and <c>theirs</c> is not null.
        /// </remarks>
        internal static void Run(XmlMerger merger, XmlNode ourParent, ref XmlNode ours, XmlNode theirs, XmlNode commonAncestor)
        {
            // Route tested.
            Guard.AgainstNull(merger, nameof(merger));
            if (ours == null && theirs == null && commonAncestor == null)
            {
                throw new ArgumentNullException();                 // Route tested.
            }
            // One or two of the elements may be null.
            // If commonAncestor is null and one of the others is null, then the other one added a new element.
            // if ours and theirs are both null, they each deleted the element.
            var nodeForStrategy = ours ?? (theirs ?? commonAncestor);
            // Here is where we sort out the new 'IsAtomic' business of ElementStrategy.
            // 1. Fetch the relevant ElementStrategy
            var elementStrategy = merger.MergeStrategies.GetElementStrategy(nodeForStrategy);

            if (!elementStrategy.IsAtomic)
            {
                throw new InvalidOperationException("This method class only handles elements that are atomic (basically binary type data that can't really be merged.)");
            }

            if (commonAncestor == null)
            {
                if (ours == null)
                {
                    // They can't all be null, or there would have been an exception thrown, above.
                    //if (theirs == null)
                    //{
                    //    // Nobody did anything.
                    //    return true;
                    //}
                    // They seem to have added a new one.
                    // Route tested (x2).
                    merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(merger.MergeSituation.PathToFileInRepository, theirs));
                    XmlUtilities.ReplaceOursWithTheirs(ourParent, ref ours, theirs);                     // They added it.
                }
                else
                {
                    // Ours is not null.
                    if (theirs != null)
                    {
                        // Neither is theirs.
                        if (XmlUtilities.AreXmlElementsEqual(ours, theirs))
                        {
                            // Both added the same thing.
                            // Route tested (x2).
                            merger.EventListener.ChangeOccurred(new BothChangedAtomicElementReport(merger.MergeSituation.PathToFileInRepository, ours));
                        }
                        else
                        {
                            // Both added, but not the same thing.
                            if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin)
                            {
                                // Route tested.
                                merger.ConflictOccurred(new BothEditedTheSameAtomicElement(ours.Name,
                                                                                           ours, theirs, null, merger.MergeSituation, elementStrategy, merger.MergeSituation.AlphaUserId));
                            }
                            else
                            {
                                // Route tested.
                                merger.ConflictOccurred(new BothEditedTheSameAtomicElement(ours.Name,
                                                                                           ours, theirs, null, merger.MergeSituation, elementStrategy, merger.MergeSituation.BetaUserId));
                                XmlUtilities.ReplaceOursWithTheirs(ourParent, ref ours, theirs);
                            }
                        }
                    }
                    else
                    {
                        // We added. They are still null.
                        // Route tested (x2).
                        merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(merger.MergeSituation.PathToFileInRepository, ours));
                    }
                }
                return;                 // Routed used (x2).
            }

            // commonAncestor != null from here on out.
            if (ours == null && theirs == null)
            {
                // No problemo, since both deleted it.
                // Route tested (x2).
                merger.EventListener.ChangeOccurred(new XmlBothDeletionChangeReport(merger.MergeSituation.PathToFileInRepository, commonAncestor));
                return;
            }

            // 2A1. Compare 'ours' with 'theirs'.
            // If one is null, keep the other one, but only if it was edited.
            var theirsAndCommonAreEqual = theirs != null && XmlUtilities.AreXmlElementsEqual(theirs, commonAncestor);

            if (ours == null && !theirsAndCommonAreEqual)
            {
                // We deleted, they edited, so keep theirs under the least loss principle.
                // Route tested (x2 WeWin & !WeWin).
                merger.ConflictOccurred(new RemovedVsEditedElementConflict(theirs.Name, null, theirs,
                                                                           commonAncestor,
                                                                           merger.MergeSituation, elementStrategy,
                                                                           merger.MergeSituation.BetaUserId));
                XmlUtilities.ReplaceOursWithTheirs(ourParent, ref ours, theirs);
                return;
            }

            var oursAndCommonAreEqual = ours != null && XmlUtilities.AreXmlElementsEqual(ours, commonAncestor);

            if (theirs == null && !oursAndCommonAreEqual)
            {
                // We edited, they deleted, so keep ours under the least loss principle.
                Debug.Assert(ours != null, "We shoudn't come here if ours is also null...both deleted is handled elsewhere");
                // Route tested (x2 WeWin & !WeWin)
                merger.ConflictOccurred(new EditedVsRemovedElementConflict(ours.Name, ours, null, commonAncestor,
                                                                           merger.MergeSituation, elementStrategy,
                                                                           merger.MergeSituation.AlphaUserId));
                return;
            }

            var oursAndTheirsAreEqual = ours != null && theirs != null && XmlUtilities.AreXmlElementsEqual(ours, theirs);

            if (oursAndTheirsAreEqual && !oursAndCommonAreEqual)
            {
                // Both made same changes.
                // Route tested (x2).
                merger.EventListener.ChangeOccurred(new BothChangedAtomicElementReport(merger.MergeSituation.PathToFileInRepository, ours));
                return;
            }

            if (!oursAndTheirsAreEqual)
            {
                if (ours == null)
                {
                    // We deleted. They did nothing.
                    Debug.Assert(theirs != null, "both deleted should be handled before this");
                    Debug.Assert(theirsAndCommonAreEqual, "we deleted and they edited should be handled before this");
                    // leave ours null, preserving the deletion.
                    // We could plausibly call ChangeOccurred with an XmlDeletionChangeReport, but we are phasing those out.
                    return;                     // Route tested
                }
                if (theirs == null)
                {
                    // They deleted. We did nothing.
                    Debug.Assert(oursAndCommonAreEqual, "we edited and they deleted should be handled before this");
                    // Let the delete win.
                    ours = null;
                    // We could plausibly call ChangeOccurred with an XmlDeletionChangeReport, but we are phasing those out.
                    return;                      // Route tested
                }
                // Compare with common ancestor to see who made the change, if only one made it.\
                if (!oursAndCommonAreEqual && theirsAndCommonAreEqual)
                {
                    // We edited it. They did nothing.
                    // Route tested (x2).
                    merger.EventListener.ChangeOccurred(new XmlChangedRecordReport(null, null, ours.ParentNode, ours));
                }
                else if (!theirsAndCommonAreEqual && oursAndCommonAreEqual)
                {
                    // They edited it. We did nothing.
                    // Route tested (x2).
                    merger.EventListener.ChangeOccurred(new XmlChangedRecordReport(null, null, theirs.ParentNode, theirs));
                    XmlUtilities.ReplaceOursWithTheirs(ourParent, ref ours, theirs);
                }
                else
                {
                    // Both edited.
                    // 2A1b. If different, then report a conflict and then stop.
                    // Route tested (x2 WeWin & !WeWin).
                    merger.ConflictOccurred(merger.MergeSituation.ConflictHandlingMode ==
                                            MergeOrder.ConflictHandlingModeChoices.WeWin
                                                                                                        ? new BothEditedTheSameAtomicElement(ours.Name, ours, theirs, commonAncestor,
                                                                                                                                             merger.MergeSituation, elementStrategy,
                                                                                                                                             merger.MergeSituation.AlphaUserId)
                                                                                                        : new BothEditedTheSameAtomicElement(theirs.Name, ours, theirs, commonAncestor,
                                                                                                                                             merger.MergeSituation, elementStrategy,
                                                                                                                                             merger.MergeSituation.BetaUserId));
                    if (merger.MergeSituation.ConflictHandlingMode != MergeOrder.ConflictHandlingModeChoices.WeWin)
                    {
                        XmlUtilities.ReplaceOursWithTheirs(ourParent, ref ours, theirs);
                    }
                }
            }

            // No changes.
            // Route tested (x2).
        }