/// <summary> /// Merges the children into the "ours" xmlnode, and uses the merger's listener to publish what happened /// </summary> public void Run() { // Pre-process three nodes to handle duplicates in each node, But only if finder is one of these: // FindFirstElementWithSameName, FindByKeyAttribute, or FindByMultipleKeyAttributes. // Initialise lists of keepers to current ancestorChildren, ourChildren, theirChildren CopyChildrenToList(_ancestor, _childrenOfAncestorKeepers); CopyChildrenToList(_ours, _childrenOfOurKeepers); CopyChildrenToList(_theirs, _childrenOfTheirKeepers); // Deal with deletions. DoDeletions(); // Allow the most promising parent's strategy to determine whether order is significant for children. // The default configuration of ElementStrategy uses an AskChildren strategy, which means individual // children determine whether their order is significant. var parentNode = _ours ?? _theirs ?? _ancestor; var parentStrategy = _merger.MergeStrategies.GetElementStrategy(parentNode); var parentOrder = parentStrategy == null ? ChildOrder.AskChildren : parentStrategy.ChildOrderPolicy.OrderSignificance(parentNode); // Determine if child order is important, we shouldn't create reordering conflicts if the order of children is unimportant var childOrderMatters = parentOrder == ChildOrder.Significant; if(parentOrder == ChildOrder.AskChildren && parentNode.HasChildNodes) { var childStrategy = _merger.MergeStrategies.GetElementStrategy(parentNode.FirstChild); if(childStrategy != null && childStrategy.OrderIsRelevant) { childOrderMatters = true; } } ChildOrderer oursOrderer = new ChildOrderer(_childrenOfOurKeepers, _childrenOfTheirKeepers, MakeCorrespondences(_childrenOfOurKeepers, _childrenOfTheirKeepers, _theirs), _merger, parentOrder); ChildOrderer resultOrderer = oursOrderer; // default if (oursOrderer.OrderIsDifferent) { // The order of the two lists is not consistent. Compare with ancestor to see who changed. ChildOrderer ancestorOursOrderer = new ChildOrderer(_childrenOfAncestorKeepers, _childrenOfOurKeepers, MakeCorrespondences(_childrenOfAncestorKeepers, _childrenOfOurKeepers, _ours), _merger, parentOrder); ChildOrderer ancestorTheirsOrderer = new ChildOrderer(_childrenOfAncestorKeepers, _childrenOfTheirKeepers, MakeCorrespondences(_childrenOfAncestorKeepers, _childrenOfTheirKeepers, _theirs), _merger, parentOrder); if (ancestorTheirsOrderer.OrderIsDifferent) { if (ancestorOursOrderer.OrderIsDifferent && childOrderMatters) { // stick with our orderer (we win), but report conflict. // Route tested (XmlMergerTests). _merger.ConflictOccurred(new BothReorderedElementConflict(_ours.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(_ours), _merger.MergeSituation.AlphaUserId)); } else { // only they re-ordered; take their order as primary. resultOrderer = new ChildOrderer(_childrenOfTheirKeepers, _childrenOfOurKeepers, MakeCorrespondences(_childrenOfTheirKeepers, _childrenOfOurKeepers, _ours), _merger, parentOrder); } } else if(!ancestorOursOrderer.OrderIsDifferent) { // our order is different from theirs, but neither is different from the ancestor. // the only way this can be true is if both inserted the same thing, but in // different places. Stick with our orderer (we win), but report conflict. // Route tested (XmlMergerTests). _merger.ConflictOccurred(new BothInsertedAtDifferentPlaceConflict(_ours.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(_ours), _merger.MergeSituation.AlphaUserId)); } // otherwise we re-ordered, but they didn't. That's not a problem, unless it resulted // in inconsistency or ambiguity in other stuff that someone added. } if (!resultOrderer.OrderIsConsistent || (resultOrderer.OrderIsDifferent && resultOrderer.OrderIsAmbiguous)) { // Route tested (XmlMergerTests[x2]). _merger.ConflictOccurred(new AmbiguousInsertReorderConflict(_ours.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(_ours), _merger.MergeSituation.AlphaUserId)); } else if (resultOrderer.OrderIsAmbiguous) { // Route tested (MergeChildrenMethodTests, XmlMergerTests). _merger.ConflictOccurred(new AmbiguousInsertConflict(_ours.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(_ours), _merger.MergeSituation.AlphaUserId)); } // Merge corresponding nodes. // 'resultsChildren' may contain nodes from either 'ours', 'theirs', or both, // as the 'resultsChildren' collection has been combined in the ordereing operation. List<XmlNode> resultsChildren = resultOrderer.GetResultList(); for (int i = 0; i < resultsChildren.Count; i++) { XmlNode ourChild = resultsChildren[i]; // The 'DoDeletions' method call, above, possibly called MergeTextNodesMethod.DoDeletions, // which may have added 'ourChild' to _skipInnerMergeFor, // as it did some fairly exotic work with full and partial deletions. // So, don't do those again here. if (_skipInnerMergeFor.Contains(ourChild)) continue; XmlNode theirChild; var ancestorChild = FindMatchingNode(ourChild, _ancestor, new HashSet<XmlNode>(_childrenOfAncestorKeepers)); if (resultOrderer.Correspondences.TryGetValue(ourChild, out theirChild) && !ChildrenAreSame(ourChild, theirChild)) { // Both 'ourChild' and 'theirChild exist. 'ancestorChild' may, or, may not, exist. // There's a corresponding node and it isn't the same as ours... // Route tested: MergeChildrenMethod_DiffOnlyTests. _merger.MergeInner(_ours, ref ourChild, theirChild, ancestorChild); resultsChildren[i] = ourChild; } else { // 'theirChild' may, or may not, exist. But if it does exist, it is the same as 'ourChild'. //Review JohnT (jh): Is this the correct interpretation? if (ancestorChild == null) { if (XmlUtilities.IsTextNodeContainer(ourChild) == TextNodeStatus.IsTextNodeContainer) // No, it hasn't. MergeTextNodesMethod has already added the addition report. { if (theirChild == null) { // Route tested: MergeChildrenMethod_DiffOnlyTests & XmlMergerTests. _merger.EventListener.ChangeOccurred(new XmlTextAddedReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); // Route tested (x2). } else { _merger.EventListener.ChangeOccurred(new XmlTextBothAddedReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); // Route tested } } else if (!(ourChild is XmlCharacterData)) { if (theirChild == null) // Route tested (MergeChildrenMethodTests, XmlMergerTests). _merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); else _merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); } } else { // ancestorChild is not null. if (XmlUtilities.IsTextNodeContainer(ourChild) == TextNodeStatus.IsTextNodeContainer) { if (theirChild != null && !XmlUtilities.AreXmlElementsEqual(ourChild, ancestorChild)) _merger.EventListener.ChangeOccurred(new XmlTextBothMadeSameChangeReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); // Route tested } else { _merger.MergeInner(_ours, ref ourChild, theirChild, ancestorChild); resultsChildren[i] = ourChild; } } } } // Plug results back into 'ours' for (int i = 0; i < resultsChildren.Count; i++) { XmlNode ourChild = resultsChildren[i]; while (_ours.ChildNodes.Count > i && ourChild != _ours.ChildNodes[i]) _ours.RemoveChild(_ours.ChildNodes[i]); if (_ours.ChildNodes.Count > i) continue; // we found the exact node already present, leave it alone. if (ourChild.ParentNode == _ours) _ours.AppendChild(ourChild); else { if (ourChild is XmlElement) _ours.AppendChild(_ours.OwnerDocument.ImportNode(ourChild, true)); else if (ourChild is XmlText) _ours.AppendChild(_ours.OwnerDocument.CreateTextNode(ourChild.OuterXml)); else Debug.Fail("so far we only know how to copy elements and text nodes at this point"); } } // Remove any leftovers. while (_ours.ChildNodes.Count > resultsChildren.Count) _ours.RemoveChild(_ours.ChildNodes[resultsChildren.Count]); }
/// <summary> /// Merges the children into the "ours" xmlnode, and uses the merger's listener to publish what happened /// </summary> public void Run() { // Pre-process three nodes to handle duplicates in each node, But only if finder is one of these: // FindFirstElementWithSameName, FindByKeyAttribute, or FindByMultipleKeyAttributes. // Initialise lists of keepers to current ancestorChildren, ourChildren, theirChildren CopyChildrenToList(_ancestor, _childrenOfAncestorKeepers); CopyChildrenToList(_ours, _childrenOfOurKeepers); CopyChildrenToList(_theirs, _childrenOfTheirKeepers); // Deal with deletions. DoDeletions(); // Allow the most promising parent's strategy to determine whether order is significant for children. // The default configuration of ElementStrategy uses an AskChildren strategy, which means individual // children determine whether their order is significant. var parentNode = _ours ?? _theirs ?? _ancestor; var parentStrategy = _merger.MergeStrategies.GetElementStrategy(parentNode); var parentOrder = parentStrategy == null ? ChildOrder.AskChildren : parentStrategy.ChildOrderPolicy.OrderSignificance(parentNode); // Determine if child order is important, we shouldn't create reordering conflicts if the order of children is unimportant var childOrderMatters = parentOrder == ChildOrder.Significant; if (parentOrder == ChildOrder.AskChildren && parentNode.HasChildNodes) { var childStrategy = _merger.MergeStrategies.GetElementStrategy(parentNode.FirstChild); if (childStrategy != null && childStrategy.OrderIsRelevant) { childOrderMatters = true; } } ChildOrderer oursOrderer = new ChildOrderer(_childrenOfOurKeepers, _childrenOfTheirKeepers, MakeCorrespondences(_childrenOfOurKeepers, _childrenOfTheirKeepers, _theirs), _merger, parentOrder); ChildOrderer resultOrderer = oursOrderer; // default if (oursOrderer.OrderIsDifferent) { // The order of the two lists is not consistent. Compare with ancestor to see who changed. ChildOrderer ancestorOursOrderer = new ChildOrderer(_childrenOfAncestorKeepers, _childrenOfOurKeepers, MakeCorrespondences(_childrenOfAncestorKeepers, _childrenOfOurKeepers, _ours), _merger, parentOrder); ChildOrderer ancestorTheirsOrderer = new ChildOrderer(_childrenOfAncestorKeepers, _childrenOfTheirKeepers, MakeCorrespondences(_childrenOfAncestorKeepers, _childrenOfTheirKeepers, _theirs), _merger, parentOrder); if (ancestorTheirsOrderer.OrderIsDifferent) { if (ancestorOursOrderer.OrderIsDifferent && childOrderMatters) { // stick with our orderer (we win), but report conflict. // Route tested (XmlMergerTests). _merger.ConflictOccurred(new BothReorderedElementConflict(_ours.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(_ours), _merger.MergeSituation.AlphaUserId)); } else { // only they re-ordered; take their order as primary. resultOrderer = new ChildOrderer(_childrenOfTheirKeepers, _childrenOfOurKeepers, MakeCorrespondences(_childrenOfTheirKeepers, _childrenOfOurKeepers, _ours), _merger, parentOrder); } } else if (!ancestorOursOrderer.OrderIsDifferent) { // our order is different from theirs, but neither is different from the ancestor. // the only way this can be true is if both inserted the same thing, but in // different places. Stick with our orderer (we win), but report conflict. // Route tested (XmlMergerTests). _merger.ConflictOccurred(new BothInsertedAtDifferentPlaceConflict(_ours.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(_ours), _merger.MergeSituation.AlphaUserId)); } // otherwise we re-ordered, but they didn't. That's not a problem, unless it resulted // in inconsistency or ambiguity in other stuff that someone added. } if (!resultOrderer.OrderIsConsistent || (resultOrderer.OrderIsDifferent && resultOrderer.OrderIsAmbiguous)) { // Route tested (XmlMergerTests[x2]). _merger.ConflictOccurred(new AmbiguousInsertReorderConflict(_ours.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(_ours), _merger.MergeSituation.AlphaUserId)); } else if (resultOrderer.OrderIsAmbiguous) { // Route tested (MergeChildrenMethodTests, XmlMergerTests). _merger.ConflictOccurred(new AmbiguousInsertConflict(_ours.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(_ours), _merger.MergeSituation.AlphaUserId)); } // Merge corresponding nodes. // 'resultsChildren' may contain nodes from either 'ours', 'theirs', or both, // as the 'resultsChildren' collection has been combined in the ordereing operation. List <XmlNode> resultsChildren = resultOrderer.GetResultList(); for (int i = 0; i < resultsChildren.Count; i++) { XmlNode ourChild = resultsChildren[i]; // The 'DoDeletions' method call, above, possibly called MergeTextNodesMethod.DoDeletions, // which may have added 'ourChild' to _skipInnerMergeFor, // as it did some fairly exotic work with full and partial deletions. // So, don't do those again here. if (_skipInnerMergeFor.Contains(ourChild)) { continue; } XmlNode theirChild; var ancestorChild = FindMatchingNode(ourChild, _ancestor, new HashSet <XmlNode>(_childrenOfAncestorKeepers)); if (resultOrderer.Correspondences.TryGetValue(ourChild, out theirChild) && !ChildrenAreSame(ourChild, theirChild)) { // Both 'ourChild' and 'theirChild exist. 'ancestorChild' may, or, may not, exist. // There's a corresponding node and it isn't the same as ours... // Route tested: MergeChildrenMethod_DiffOnlyTests. _merger.MergeInner(ref ourChild, theirChild, ancestorChild); resultsChildren[i] = ourChild; } else { // 'theirChild' may, or may not, exist. But if it does exist, it is the same as 'ourChild'. //Review JohnT (jh): Is this the correct interpretation? if (ancestorChild == null) { if (XmlUtilities.IsTextNodeContainer(ourChild) == TextNodeStatus.IsTextNodeContainer) // No, it hasn't. MergeTextNodesMethod has already added the addition report. { if (theirChild == null) { // Route tested: MergeChildrenMethod_DiffOnlyTests & XmlMergerTests. _merger.EventListener.ChangeOccurred(new XmlTextAddedReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); // Route tested (x2). } else { _merger.EventListener.ChangeOccurred(new XmlTextBothAddedReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); // Route tested } } else if (!(ourChild is XmlCharacterData)) { if (theirChild == null) { // Route tested (MergeChildrenMethodTests, XmlMergerTests). _merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); } else { _merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); } } } else { // ancestorChild is not null. if (XmlUtilities.IsTextNodeContainer(ourChild) == TextNodeStatus.IsTextNodeContainer) { if (theirChild != null && !XmlUtilities.AreXmlElementsEqual(ourChild, ancestorChild)) { _merger.EventListener.ChangeOccurred(new XmlTextBothMadeSameChangeReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); // Route tested } } else { _merger.MergeInner(ref ourChild, theirChild, ancestorChild); resultsChildren[i] = ourChild; } } } } // Plug results back into 'ours' for (int i = 0; i < resultsChildren.Count; i++) { XmlNode ourChild = resultsChildren[i]; while (_ours.ChildNodes.Count > i && ourChild != _ours.ChildNodes[i]) { _ours.RemoveChild(_ours.ChildNodes[i]); } if (_ours.ChildNodes.Count > i) { continue; // we found the exact node already present, leave it alone. } if (ourChild.ParentNode == _ours) { _ours.AppendChild(ourChild); } else { if (ourChild is XmlElement) { _ours.AppendChild(_ours.OwnerDocument.ImportNode(ourChild, true)); } else if (ourChild is XmlText) { _ours.AppendChild(_ours.OwnerDocument.CreateTextNode(ourChild.OuterXml)); } else { Debug.Fail("so far we only know how to copy elements and text nodes at this point"); } } } // Remove any leftovers. while (_ours.ChildNodes.Count > resultsChildren.Count) { _ours.RemoveChild(_ours.ChildNodes[resultsChildren.Count]); } }