chorus

changeset 853:fa661656d390 MergeImprovements

INtermediate commit. (Too many tests fail, but I wanted to get the changes in.)
author Randy
date Tue Jan 24 05:28:06 2012 -0600 (3 months ago)
parents be6f806dfc49
children 87b32e6e66a7
files src/LibChorus/FileTypeHanders/xml/XmlAdditionChangeReport.cs src/LibChorus/FileTypeHanders/xml/XmlChangedRecordReport.cs src/LibChorus/FileTypeHanders/xml/XmlDeletionChangeReport.cs src/LibChorus/FileTypeHanders/xml/XmlTextChange.cs src/LibChorus/LibChorus.csproj src/LibChorus/Properties/AnnotationImages.Designer.cs src/LibChorus/Properties/AnnotationImages.resx src/LibChorus/merge/xml/generic/MergeAtomicElementService.cs src/LibChorus/merge/xml/generic/MergeChildrenMethod.cs src/LibChorus/merge/xml/generic/MergeTextNodesMethod.cs src/LibChorus/merge/xml/generic/XmlMerger.cs src/LibChorus/merge/xml/generic/XmlUtilities.cs src/LibChorusTests/LibChorus.Tests.csproj src/LibChorusTests/merge/xml/generic/TextElementMergeTests.cs src/LibChorusTests/merge/xml/generic/XmlMergerTests.cs src/LibChorusTests/merge/xml/generic/XmlTestHelper.cs
line diff
     1.1 --- a/src/LibChorus/FileTypeHanders/xml/XmlAdditionChangeReport.cs	Sat Jan 21 15:48:29 2012 -0600
     1.2 +++ b/src/LibChorus/FileTypeHanders/xml/XmlAdditionChangeReport.cs	Tue Jan 24 05:28:06 2012 -0600
     1.3 @@ -24,7 +24,6 @@
     1.4              _url = url;
     1.5          }
     1.6  
     1.7 -
     1.8          //when merging, the eventual revision is unknown
     1.9          public XmlAdditionChangeReport(string fullPath, XmlNode addedElement)
    1.10              : base(null, new FileInUnknownRevision(fullPath, FileInRevision.Action.Modified))
    1.11 @@ -76,11 +75,7 @@
    1.12              if(r==null)
    1.13                  return false;
    1.14              var otherGuid = r._addedElement.GetOptionalStringAttribute("guid",string.Empty);
    1.15 -			// REVIEW JohnH(RandyR): Why is it checking 'guid' again, since it has done it already? Is it to supposed to check 'otherGuid' instead?
    1.16 -            if (guid == string.Empty)
    1.17 -                return base.Equals(obj);
    1.18 -
    1.19 -			return String.Equals(guid, otherGuid, StringComparison.InvariantCultureIgnoreCase); // Make sure case is ignored.
    1.20 +			return otherGuid == string.Empty ? base.Equals(obj) : String.Equals(guid, otherGuid, StringComparison.OrdinalIgnoreCase);
    1.21          }
    1.22      }
    1.23  }
    1.24 \ No newline at end of file
     2.1 --- a/src/LibChorus/FileTypeHanders/xml/XmlChangedRecordReport.cs	Sat Jan 21 15:48:29 2012 -0600
     2.2 +++ b/src/LibChorus/FileTypeHanders/xml/XmlChangedRecordReport.cs	Tue Jan 24 05:28:06 2012 -0600
     2.3 @@ -8,7 +8,7 @@
     2.4      /// This may only be useful for quick, high-level identification that an entry changed,
     2.5      /// leaving *what* changed to a second pass, if needed by the user.
     2.6      /// </summary>
     2.7 -    public class XmlChangedRecordReport : ChangeReport, IChangeReport, IXmlChangeReport
     2.8 +    public class XmlChangedRecordReport : ChangeReport, IXmlChangeReport
     2.9      {
    2.10          private readonly XmlNode _parent;
    2.11          private readonly XmlNode _child;
     3.1 --- a/src/LibChorus/FileTypeHanders/xml/XmlDeletionChangeReport.cs	Sat Jan 21 15:48:29 2012 -0600
     3.2 +++ b/src/LibChorus/FileTypeHanders/xml/XmlDeletionChangeReport.cs	Tue Jan 24 05:28:06 2012 -0600
     3.3 @@ -19,7 +19,7 @@
     3.4  
     3.5          //when merging, the eventual revision is unknown
     3.6          public XmlDeletionChangeReport(string fullPath, XmlNode parentNode, XmlNode childNode)
     3.7 -            : this(new FileInUnknownRevision(fullPath, FileInRevision.Action.Modified), parentNode,  childNode)
     3.8 +            : this(new FileInUnknownRevision(fullPath, FileInRevision.Action.Deleted), parentNode,  childNode)
     3.9          {
    3.10          }
    3.11  
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/src/LibChorus/FileTypeHanders/xml/XmlTextChange.cs	Tue Jan 24 05:28:06 2012 -0600
     4.3 @@ -0,0 +1,236 @@
     4.4 +using System;
     4.5 +using System.Collections.Generic;
     4.6 +using System.Linq;
     4.7 +using System.Text;
     4.8 +using System.Xml;
     4.9 +using Chorus.Properties;
    4.10 +using Chorus.VcsDrivers.Mercurial;
    4.11 +using Chorus.merge;
    4.12 +using Palaso.Xml;
    4.13 +
    4.14 +namespace Chorus.FileTypeHanders.xml
    4.15 +{
    4.16 +	/// <summary>
    4.17 +	/// Abstract class that supports the three concrete type of XML elements that contain an XmlNodeType.Text element.
    4.18 +	/// </summary>
    4.19 +	public abstract class XmlTextChange : ChangeReport, IXmlChangeReport
    4.20 +	{
    4.21 +		private readonly XmlNode _affectedNode;
    4.22 +		private readonly string _url;
    4.23 +
    4.24 +		protected XmlTextChange(FileInRevision parent, FileInRevision child, XmlNode affectedNode, string url)
    4.25 +			: base(parent, child)
    4.26 +		{
    4.27 +			if (affectedNode.HasChildNodes && affectedNode.FirstChild.NodeType != XmlNodeType.Text)
    4.28 +				throw new ArgumentException(AnnotationImages.kElementNotTextElement, "affectedNode");
    4.29 +
    4.30 +			_affectedNode = affectedNode;
    4.31 +			_url = url;
    4.32 +		}
    4.33 +
    4.34 +		protected abstract string FormattedMessageForFullHumanReadableDescription { get; }
    4.35 +
    4.36 +		#region Implementation of IXmlChangeReport
    4.37 +
    4.38 +		public XmlNode ParentNode
    4.39 +		{
    4.40 +			get { return ChildNode.ParentNode; }
    4.41 +		}
    4.42 +
    4.43 +		public XmlNode ChildNode
    4.44 +		{
    4.45 +			get { return _affectedNode; }
    4.46 +		}
    4.47 +
    4.48 +		#endregion
    4.49 +
    4.50 +		#region Implementation of IChangeReport
    4.51 +
    4.52 +		public override string UrlOfItem
    4.53 +		{
    4.54 +			get { return _url; }
    4.55 +		}
    4.56 +
    4.57 +		#endregion
    4.58 +	}
    4.59 +
    4.60 +	/// <summary>
    4.61 +	/// Change report for Xml Text element (parent of XmlNodeType.Text element) that changed.
    4.62 +	/// </summary>
    4.63 +	public sealed class XmlTextChangedReport : XmlTextChange
    4.64 +	{
    4.65 +		public XmlTextChangedReport(FileInRevision parentFileInRevision, FileInRevision childFileInRevision, XmlNode editedElement, string url)
    4.66 +			: base(parentFileInRevision, childFileInRevision, editedElement, url)
    4.67 +        {
    4.68 +        }
    4.69 +
    4.70 +		public XmlTextChangedReport(FileInRevision parentFileInRevision, FileInRevision childFileInRevision, XmlNode editedElement)
    4.71 +			: this(parentFileInRevision, childFileInRevision, editedElement, string.Empty)
    4.72 +        {
    4.73 +        }
    4.74 +
    4.75 +		public XmlTextChangedReport(string fullPath, XmlNode editedElement)
    4.76 +			: this(null, new FileInUnknownRevision(fullPath, FileInRevision.Action.Modified), editedElement, string.Empty)
    4.77 +		{
    4.78 +		}
    4.79 +
    4.80 +		#region Overrides of ChangeReport
    4.81 +
    4.82 +		public override string ActionLabel
    4.83 +		{
    4.84 +			get { return "Changed"; }
    4.85 +		}
    4.86 +
    4.87 +		#endregion
    4.88 +
    4.89 +		#region Overrides of XmlTextChange
    4.90 +
    4.91 +		protected override string FormattedMessageForFullHumanReadableDescription
    4.92 +		{
    4.93 +			get { return "Modified <{0}> of <{1}>"; }
    4.94 +		}
    4.95 +
    4.96 +		#endregion
    4.97 +	}
    4.98 +
    4.99 +	/// <summary>
   4.100 +	/// Change report for Xml Text element (parent of XmlNodeType.Text element) that was added.
   4.101 +	/// </summary>
   4.102 +	public sealed class XmlTextAddedReport : XmlTextChange
   4.103 +	{
   4.104 +        // When merging, the eventual revision is unknown.
   4.105 +        public XmlTextAddedReport(string fullPath, XmlNode addedElement)
   4.106 +			: base(null, new FileInUnknownRevision(fullPath, FileInRevision.Action.Added), addedElement, string.Empty)
   4.107 +        {
   4.108 +        }
   4.109 +
   4.110 +		#region Overrides of XmlTextChange
   4.111 +
   4.112 +		public override string ActionLabel
   4.113 +		{
   4.114 +			get { return "Added"; }
   4.115 +		}
   4.116 +
   4.117 +		protected override string FormattedMessageForFullHumanReadableDescription
   4.118 +		{
   4.119 +			get { return "Added <{0}> to <{1}>"; }
   4.120 +		}
   4.121 +
   4.122 +		#endregion
   4.123 +
   4.124 +		public override int GetHashCode()
   4.125 +		{
   4.126 +			var guid = ChildNode.GetOptionalStringAttribute("guid", string.Empty);
   4.127 +			return (guid == string.Empty)
   4.128 +				? base.GetHashCode()
   4.129 +				: guid.ToLowerInvariant().GetHashCode();
   4.130 +		}
   4.131 +
   4.132 +		public override bool Equals(object obj)
   4.133 +		{
   4.134 +			var otherReport = obj as XmlTextAddedReport;
   4.135 +			if (otherReport == null)
   4.136 +				return false;
   4.137 +
   4.138 +			var guid = ChildNode.GetOptionalStringAttribute("guid", string.Empty);
   4.139 +			var otherGuid = otherReport.ChildNode.GetOptionalStringAttribute("guid", string.Empty);
   4.140 +			if (guid == string.Empty || otherGuid == string.Empty)
   4.141 +				return base.Equals(obj);
   4.142 +
   4.143 +			return String.Equals(guid, otherGuid, StringComparison.OrdinalIgnoreCase);
   4.144 +		}
   4.145 +	}
   4.146 +
   4.147 +	/// <summary>
   4.148 +	/// Change report for Xml Text element (parent of XmlNodeType.Text node) that was deleted.
   4.149 +	/// </summary>
   4.150 +	public sealed class XmlTextDeletedReport : XmlTextChange
   4.151 +	{
   4.152 +        public XmlTextDeletedReport(FileInRevision fileInRevision, XmlNode deletedChildNode, string url)
   4.153 +            : base(null, fileInRevision, deletedChildNode, url)
   4.154 +        {
   4.155 +        }
   4.156 +
   4.157 +        // When merging, the eventual revision is unknown
   4.158 +		public XmlTextDeletedReport(string fullPath, XmlNode deletedChildNode)
   4.159 +            : base(null, new FileInUnknownRevision(fullPath, FileInRevision.Action.Deleted),  deletedChildNode, string.Empty)
   4.160 +        {
   4.161 +		}
   4.162 +
   4.163 +		public override string ActionLabel
   4.164 +		{
   4.165 +			get { return "Deleted"; }
   4.166 +		}
   4.167 +
   4.168 +		#region Overrides of XmlTextChange
   4.169 +
   4.170 +		protected override string FormattedMessageForFullHumanReadableDescription
   4.171 +		{
   4.172 +			get { return "Deleted <{0}> from <{1}>"; }
   4.173 +		}
   4.174 +
   4.175 +		#endregion
   4.176 +	}
   4.177 +
   4.178 +	/// <summary>
   4.179 +	/// Change report for Xml Text element (parent of XmlNodeType.Text node) that was deleted by both people.
   4.180 +	/// </summary>
   4.181 +	public sealed class XmlTextBothDeletedReport : XmlTextChange
   4.182 +	{
   4.183 +		public XmlTextBothDeletedReport(FileInRevision fileInRevision, XmlNode deletedChildNode, string url)
   4.184 +			: base(null, fileInRevision, deletedChildNode, url)
   4.185 +		{
   4.186 +		}
   4.187 +
   4.188 +		// When merging, the eventual revision is unknown
   4.189 +		public XmlTextBothDeletedReport(string fullPath, XmlNode deletedChildNode)
   4.190 +			: base(null, new FileInUnknownRevision(fullPath, FileInRevision.Action.Deleted), deletedChildNode, string.Empty)
   4.191 +		{
   4.192 +		}
   4.193 +
   4.194 +		public override string ActionLabel
   4.195 +		{
   4.196 +			get { return "Both Deleted"; }
   4.197 +		}
   4.198 +
   4.199 +		#region Overrides of XmlTextChange
   4.200 +
   4.201 +		protected override string FormattedMessageForFullHumanReadableDescription
   4.202 +		{
   4.203 +			get { return "Both people deleted <{0}> from <{1}>"; }
   4.204 +		}
   4.205 +
   4.206 +		#endregion
   4.207 +	}
   4.208 +
   4.209 +	/// <summary>
   4.210 +	/// Change report for Xml Text element (parent of XmlNodeType.Text node) that was added by both people.
   4.211 +	/// </summary>
   4.212 +	public sealed class XmlTextBothAddedReport : XmlTextChange
   4.213 +	{
   4.214 +		public XmlTextBothAddedReport(FileInRevision fileInRevision, XmlNode addedChildNode, string url)
   4.215 +			: base(null, fileInRevision, addedChildNode, url)
   4.216 +		{
   4.217 +		}
   4.218 +
   4.219 +		// When merging, the eventual revision is unknown
   4.220 +		public XmlTextBothAddedReport(string fullPath, XmlNode addedChildNode)
   4.221 +			: base(null, new FileInUnknownRevision(fullPath, FileInRevision.Action.Deleted), addedChildNode, string.Empty)
   4.222 +		{
   4.223 +		}
   4.224 +
   4.225 +		public override string ActionLabel
   4.226 +		{
   4.227 +			get { return "Both Added"; }
   4.228 +		}
   4.229 +
   4.230 +		#region Overrides of XmlTextChange
   4.231 +
   4.232 +		protected override string FormattedMessageForFullHumanReadableDescription
   4.233 +		{
   4.234 +			get { return "Both people added <{0}> to <{1}>"; }
   4.235 +		}
   4.236 +
   4.237 +		#endregion
   4.238 +	}
   4.239 +}
     5.1 --- a/src/LibChorus/LibChorus.csproj	Sat Jan 21 15:48:29 2012 -0600
     5.2 +++ b/src/LibChorus/LibChorus.csproj	Tue Jan 24 05:28:06 2012 -0600
     5.3 @@ -138,7 +138,9 @@
     5.4      <Compile Include="FileTypeHanders\lift\LiftFolder.cs" />
     5.5      <Compile Include="FileTypeHanders\Xml2WayDiffer.cs" />
     5.6      <Compile Include="FileTypeHanders\Xml2WayDiffService.cs" />
     5.7 +    <Compile Include="FileTypeHanders\xml\XmlTextChange.cs" />
     5.8      <Compile Include="merge\xml\generic\MergeAtomicElementService.cs" />
     5.9 +    <Compile Include="merge\xml\generic\MergeTextNodesMethod.cs" />
    5.10      <Compile Include="merge\xml\generic\XmlMergeService.cs" />
    5.11      <Compile Include="FileTypeHanders\oneStory\OneStoryChangePresenter.cs" />
    5.12      <Compile Include="FileTypeHanders\oneStory\OneStoryDiffer.cs" />
     6.1 --- a/src/LibChorus/Properties/AnnotationImages.Designer.cs	Sat Jan 21 15:48:29 2012 -0600
     6.2 +++ b/src/LibChorus/Properties/AnnotationImages.Designer.cs	Tue Jan 24 05:28:06 2012 -0600
     6.3 @@ -89,6 +89,15 @@
     6.4          }
     6.5          
     6.6          /// <summary>
     6.7 +        ///   Looks up a localized string similar to Element is not a text element.
     6.8 +        /// </summary>
     6.9 +        public static string kElementNotTextElement {
    6.10 +            get {
    6.11 +                return ResourceManager.GetString("kElementNotTextElement", resourceCulture);
    6.12 +            }
    6.13 +        }
    6.14 +        
    6.15 +        /// <summary>
    6.16          ///   Looks up a localized string similar to File does not exist..
    6.17          /// </summary>
    6.18          public static string kFileDoesNotExist {
     7.1 --- a/src/LibChorus/Properties/AnnotationImages.resx	Sat Jan 21 15:48:29 2012 -0600
     7.2 +++ b/src/LibChorus/Properties/AnnotationImages.resx	Tue Jan 24 05:28:06 2012 -0600
     7.3 @@ -169,4 +169,7 @@
     7.4    <data name="kNotATextElement">
     7.5      <value xml:space="preserve">Not a Text element.</value>
     7.6    </data>
     7.7 +  <data name="kElementNotTextElement">
     7.8 +    <value xml:space="preserve">Element is not a text element</value>
     7.9 +  </data>
    7.10  </root>
    7.11 \ No newline at end of file
     8.1 --- a/src/LibChorus/merge/xml/generic/MergeAtomicElementService.cs	Sat Jan 21 15:48:29 2012 -0600
     8.2 +++ b/src/LibChorus/merge/xml/generic/MergeAtomicElementService.cs	Tue Jan 24 05:28:06 2012 -0600
     8.3 @@ -56,26 +56,17 @@
     8.4  						// If one is null, keep the other one, but only if it was edited.
     8.5  					if (ours == null && !XmlUtilities.AreXmlElementsEqual(theirs, commonAncestor))
     8.6  					{
     8.7 +						// We deleted, they edited, so keep theirs under the least loss principle.
     8.8 +						merger.EventListener.ConflictOccurred(new RemovedVsEditedElementConflict(theirs.Name, theirs, null,
     8.9 +																								 commonAncestor,
    8.10 +																								 merger.MergeSituation, null,
    8.11 +																								 MergeSituation.kBetaUserId));
    8.12  						ours = theirs;
    8.13 -						// We deleted, they edited, so keep theirs under the least loss principle.
    8.14 -						if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin)
    8.15 -						{
    8.16 -							merger.EventListener.ConflictOccurred(new RemovedVsEditedElementConflict(ours.Name, ours, theirs, commonAncestor,
    8.17 -							                                                                         merger.MergeSituation, null,
    8.18 -							                                                                         MergeSituation.kAlphaUserId));
    8.19 -						}
    8.20 -						else
    8.21 -						{
    8.22 -							merger.EventListener.ConflictOccurred(new RemovedVsEditedElementConflict(theirs.Name, theirs, ours,
    8.23 -							                                                                         commonAncestor,
    8.24 -							                                                                         merger.MergeSituation, null,
    8.25 -							                                                                         MergeSituation.kBetaUserId));
    8.26 -						}
    8.27  					}
    8.28  					else if (theirs == null && !XmlUtilities.AreXmlElementsEqual(ours, commonAncestor))
    8.29  					{
    8.30  						// We edited, they deleted, so keep ours under the least loss principle.
    8.31 -						merger.EventListener.ConflictOccurred(new RemovedVsEditedElementConflict(ours.Name, ours, theirs, commonAncestor,
    8.32 +						merger.EventListener.ConflictOccurred(new EditedVsRemovedElementConflict(ours.Name, ours, null, commonAncestor,
    8.33  																					   merger.MergeSituation, null,
    8.34  																					   MergeSituation.kAlphaUserId));
    8.35  					}
     9.1 --- a/src/LibChorus/merge/xml/generic/MergeChildrenMethod.cs	Sat Jan 21 15:48:29 2012 -0600
     9.2 +++ b/src/LibChorus/merge/xml/generic/MergeChildrenMethod.cs	Tue Jan 24 05:28:06 2012 -0600
     9.3 @@ -17,7 +17,6 @@
     9.4  		private List<XmlNode> _theirKeepers = new List<XmlNode>();
     9.5  		private List<XmlNode> _ancestorKeepers = new List<XmlNode>();
     9.6  
     9.7 -
     9.8          /// <summary>
     9.9          /// Use this one for a diff of one xml node against another
    9.10          /// </summary>
    9.11 @@ -99,25 +98,22 @@
    9.12  			{
    9.13  				XmlNode ourChild = newChildren[i];
    9.14  				XmlNode theirChild;
    9.15 -                XmlNode ancestorChild = FindMatchingNode(ourChild, _ancestor);
    9.16 +                var ancestorChild = FindMatchingNode(ourChild, _ancestor);
    9.17  				if (resultOrderer.Correspondences.TryGetValue(ourChild, out theirChild)
    9.18  					&& !ChildrenAreSame(ourChild, theirChild))
    9.19  				{
    9.20 -                        // There's a corresponding node and it isn't the same as ours...
    9.21 -                        if (theirChild.NodeType == XmlNodeType.Text)
    9.22 -                            _merger.MergeTextNodes(ref ourChild, theirChild, ancestorChild);
    9.23 -                        else
    9.24 -                            _merger.MergeInner(ref ourChild, theirChild, ancestorChild);
    9.25 -                    
    9.26 +					// There's a corresponding node and it isn't the same as ours...
    9.27 +                    _merger.MergeInner(ref ourChild, theirChild, ancestorChild);
    9.28  				    newChildren[i] = ourChild;
    9.29  				}
    9.30  				else
    9.31  				{
    9.32                      //Review JohnT (jh): Is this the correct interpretation?
    9.33 -                    if (ancestorChild==null)
    9.34 -                    {
    9.35 -                        _merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(_merger.MergeSituation.PathToFileInRepository, ourChild));
    9.36 -                    }
    9.37 +					if (ancestorChild == null)
    9.38 +					{
    9.39 +						if (!XmlUtilities.IsTextLevel(ourChild)) // MergeTextNodesMethod has already added the addition report.
    9.40 +							_merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(_merger.MergeSituation.PathToFileInRepository, ourChild));
    9.41 +					}
    9.42  				}
    9.43  			}
    9.44  
    9.45 @@ -274,7 +270,18 @@
    9.46  				XmlNode ourChild = finder.GetNodeToMerge(ancestorChild, _ours);
    9.47  				XmlNode theirChild = finder.GetNodeToMerge(ancestorChild, _theirs);
    9.48  
    9.49 -                if (ourChild == null)
    9.50 +				var extantNode = ancestorChild ?? ourChild ?? theirChild;
    9.51 +				if (extantNode is XmlCharacterData)
    9.52 +					return; // Already done.
    9.53 +
    9.54 +				if (XmlUtilities.IsTextLevel(ourChild, theirChild, ancestorChild))
    9.55 +				{
    9.56 +					new MergeTextNodesMethod(_merger,
    9.57 +						ref ourChild, _ourKeepers,
    9.58 +						theirChild, _theirKeepers,
    9.59 +						ancestorChild, _ancestorKeepers).Run();
    9.60 +				}
    9.61 +                else if (ourChild == null)
    9.62                  {
    9.63                      // We deleted it.
    9.64                      if (theirChild == null)
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/src/LibChorus/merge/xml/generic/MergeTextNodesMethod.cs	Tue Jan 24 05:28:06 2012 -0600
    10.3 @@ -0,0 +1,293 @@
    10.4 +using System;
    10.5 +using System.Collections.Generic;
    10.6 +using System.Xml;
    10.7 +using Chorus.FileTypeHanders.xml;
    10.8 +using Chorus.Properties;
    10.9 +
   10.10 +namespace Chorus.merge.xml.generic
   10.11 +{
   10.12 +	/// <summary>
   10.13 +	/// Method class that processes XmlNodes that have XmlNodeType.Text for a child.
   10.14 +	/// </summary>
   10.15 +	public class MergeTextNodesMethod
   10.16 +	{
   10.17 +		private XmlNode _ours;
   10.18 +		private readonly List<XmlNode> _ourKeepers;
   10.19 +		private readonly XmlNode _theirs;
   10.20 +		private readonly List<XmlNode> _theirKeepers;
   10.21 +		private readonly XmlNode _ancestor;
   10.22 +		private readonly List<XmlNode> _ancestorKeepers;
   10.23 +		private readonly XmlMerger _merger;
   10.24 +
   10.25 +		/// <summary>
   10.26 +		/// This is for regular three-way merges.
   10.27 +		/// </summary>
   10.28 +		public MergeTextNodesMethod(XmlMerger merger,
   10.29 +			ref XmlNode ours, List<XmlNode> ourKeepers,
   10.30 +			XmlNode theirs, List<XmlNode> theirKeepers,
   10.31 +			XmlNode ancestor, List<XmlNode> ancestorKeepers)
   10.32 +		{
   10.33 +			_ours = ours;
   10.34 +			_ourKeepers = ourKeepers;
   10.35 +			_theirs = theirs;
   10.36 +			_theirKeepers = theirKeepers;
   10.37 +			_ancestor = ancestor;
   10.38 +			_ancestorKeepers = ancestorKeepers;
   10.39 +			_merger = merger;
   10.40 +
   10.41 +		}
   10.42 +
   10.43 +		/// <summary>
   10.44 +		/// Merges the children into the "ours" xmlnode, and uses the merger's listener to publish what happened.
   10.45 +		/// </summary>
   10.46 +		/// <remarks>
   10.47 +		/// NB: from MergeChildrenMethod DoDeletions method, which can call here.
   10.48 +		/// 
   10.49 +		/// Remove from ancestorKeepers any node that does not correspond to anything (both deleted)
   10.50 +		/// Remove from ancestorKeepers and theirKeepers any pair that correspond to each other but not to anything in ours. Report conflict (delete/edit) if pair not identical.
   10.51 +		/// Remove from ancestorKeepers and ourKeepers any pair that correspond to each other and are identical, but don't correspond to anything in theirs (they deleted)
   10.52 +		/// Report conflict (edit/delete) on any pair that correspond in ours and ancestor, but nothing in theirs, and that are NOT identical. (but keep them...we win)
   10.53 +		/// </remarks>
   10.54 +		public void Run()
   10.55 +		{
   10.56 +			var extantNode = _ours ?? _theirs ?? _ancestor;
   10.57 +			if (extantNode.NodeType != XmlNodeType.Element)
   10.58 +				return;
   10.59 +
   10.60 +			var ourText = _ours == null ? null : _ours.InnerText.Trim();
   10.61 +			var theirText = _theirs == null ? null : _theirs.InnerText.Trim();
   10.62 +			var ancestorText = _ancestor == null ? null : _ancestor.InnerText.Trim();
   10.63 +
   10.64 +			if (ourText == theirText && ourText == ancestorText)
   10.65 +				return; // No changes by anyone.
   10.66 +
   10.67 +			if ((ancestorText != string.Empty && ourText == string.Empty && theirText == string.Empty)
   10.68 +				|| (ancestorText != null && ourText == null && theirText == null))
   10.69 +			{
   10.70 +				// It appears we and they both deleted ancestor.
   10.71 +				_merger.EventListener.ChangeOccurred(new XmlTextBothDeletedReport(_merger.MergeSituation.PathToFileInRepository, _ancestor));
   10.72 +				_ancestorKeepers.Remove(_ancestor);
   10.73 +				//_ourKeepers.Remove(_ours);
   10.74 +				//_theirKeepers.Remove(_theirs);
   10.75 +				return;
   10.76 +			}
   10.77 +
   10.78 +			if (ancestorText == null)
   10.79 +			{
   10.80 +				// We both added something to ancestor.
   10.81 +				if (ourText == theirText)
   10.82 +				{
   10.83 +					// We both added the same thing. It could be content or empty strings.
   10.84 +					_merger.EventListener.ChangeOccurred(new XmlTextBothAddedReport(_merger.MergeSituation.PathToFileInRepository, _ours));
   10.85 +					return;
   10.86 +				}
   10.87 +				// At this point, ancestor is null, and ourText is *not* the same as theirText, and ourText and theirText are not null.
   10.88 +				// TODO: Add conflict and set winner to merge sit. Then, be sure our.inner is reset, if they won.
   10.89 +				return;
   10.90 +			}
   10.91 +
   10.92 +			if (ancestorText == string.Empty)
   10.93 +			{
   10.94 +				if (ourText == string.Empty)
   10.95 +				{
   10.96 +					if (theirText == string.Empty)
   10.97 +						return;  // No changes by anyone.
   10.98 +					if (theirText == null)
   10.99 +					{
  10.100 +						// They deleted whole element. We did nothing
  10.101 +						_merger.EventListener.ChangeOccurred(new XmlTextDeletedReport(_merger.MergeSituation.PathToFileInRepository, _theirs));
  10.102 +						_ancestorKeepers.Remove(_ancestor);
  10.103 +						_ourKeepers.Remove(_ours);
  10.104 +						_ours = null;
  10.105 +						return;
  10.106 +					}
  10.107 +					if (theirText.Length > 0)
  10.108 +					{
  10.109 +						// They added text to otherwise empty node. We did nothing.
  10.110 +						_merger.EventListener.ChangeOccurred(new XmlTextAddedReport(_merger.MergeSituation.PathToFileInRepository, _theirs));
  10.111 +						_ours.InnerText = _theirs.InnerText;
  10.112 +						return;
  10.113 +					}
  10.114 +					// They added text content into node. We did nothing.
  10.115 +					_ours.InnerText = _theirs.InnerText;
  10.116 +					_merger.EventListener.ChangeOccurred(new XmlTextAddedReport(_merger.MergeSituation.PathToFileInRepository, _theirs));
  10.117 +					return;
  10.118 +				}
  10.119 +				if (ourText == null)
  10.120 +				{
  10.121 +					if (theirText.Length > 0)
  10.122 +					{
  10.123 +						// We deleted. They edited. Keep theirs under least loss principle.
  10.124 +						// TODO: Add XmlTextRemovedVsEditConflict.
  10.125 +						_ours = _theirs;
  10.126 +						return;
  10.127 +					}
  10.128 +				}
  10.129 +				if (theirText == string.Empty)
  10.130 +				{
  10.131 +					if (ourText == string.Empty)
  10.132 +						return;  // No changes by anyone.
  10.133 +					if (ourText == null)
  10.134 +					{
  10.135 +						// We deleted. They did nothing.
  10.136 +						_ancestorKeepers.Remove(_ancestor);
  10.137 +						_theirKeepers.Remove(_theirs);
  10.138 +						_merger.EventListener.ChangeOccurred(new XmlTextDeletedReport(_merger.MergeSituation.PathToFileInRepository,  _ours));
  10.139 +						return;
  10.140 +					}
  10.141 +					if (ourText.Length > 0)
  10.142 +					{
  10.143 +						// We edited. They did nothing. Keep ours.
  10.144 +						_merger.EventListener.ChangeOccurred(new XmlTextChangedReport(_merger.MergeSituation.PathToFileInRepository, _ours));
  10.145 +						return;
  10.146 +					}
  10.147 +					return;
  10.148 +				}
  10.149 +				if (theirText == null)
  10.150 +				{
  10.151 +					if (ourText.Length > 0)
  10.152 +					{
  10.153 +						// They deleted. We edited. Keep ours under least loss principle.
  10.154 +						// TODO: Add XmlTextEditVsRemovedConflict.
  10.155 +						return;
  10.156 +					}
  10.157 +					return;
  10.158 +				}
  10.159 +			}
  10.160 +
  10.161 +			if (ancestorText.Length > 0)
  10.162 +			{
  10.163 +				if (ourText == string.Empty)
  10.164 +				{
  10.165 +					// We deleted the text and left the node.
  10.166 +					if (theirText == null)
  10.167 +					{
  10.168 +						// We deleted the text and left the node. But, they deleted the text and the node.
  10.169 +						// TODO: Add new type of conflict report for this. Left the node fom ours.
  10.170 +						return;
  10.171 +					}
  10.172 +					if (theirText == string.Empty)
  10.173 +					{
  10.174 +						// Both deleted the text and left the node.
  10.175 +						_merger.EventListener.ChangeOccurred(new XmlTextBothDeletedReport(_merger.MergeSituation.PathToFileInRepository, _ancestor));
  10.176 +						return;
  10.177 +					}
  10.178 +					if (theirText == ancestorText)
  10.179 +					{
  10.180 +						// We deleted the text and left the node.
  10.181 +						_merger.EventListener.ChangeOccurred(new XmlTextDeletedReport(_merger.MergeSituation.PathToFileInRepository, _ancestor));
  10.182 +						return;
  10.183 +					}
  10.184 +				}
  10.185 +				if (ourText == null)
  10.186 +				{
  10.187 +					return;
  10.188 +				}
  10.189 +				// ourText is not null nor an empty string.
  10.190 +				if (ourText == ancestorText && ourText != theirText)
  10.191 +				{
  10.192 +					_ours.InnerText = _theirs.InnerText;
  10.193 +					if (theirText == string.Empty)
  10.194 +					{
  10.195 +						// They deleted the text, but left the node.
  10.196 +						_merger.EventListener.ChangeOccurred(new XmlTextDeletedReport(_merger.MergeSituation.PathToFileInRepository, _ours));
  10.197 +					}
  10.198 +					else
  10.199 +					{
  10.200 +						// They changed it, nobody else did anything.
  10.201 +						_merger.EventListener.ChangeOccurred(new XmlTextChangedReport(_merger.MergeSituation.PathToFileInRepository, _theirs));
  10.202 +					}
  10.203 +					return;
  10.204 +				}
  10.205 +				if (theirText == ancestorText && ourText != ancestorText)
  10.206 +				{
  10.207 +					// We changed. They did nothing.
  10.208 +					_merger.EventListener.ChangeOccurred(new XmlTextChangedReport(_merger.MergeSituation.PathToFileInRepository, _ours));
  10.209 +					return;
  10.210 +				}
  10.211 +				return;
  10.212 +			}
  10.213 +			/******** Not sure how one can get to this point. *********/
  10.214 +
  10.215 +			//if (string.IsNullOrEmpty(ourText))
  10.216 +			//{
  10.217 +			//    if (_ancestor == null || string.IsNullOrEmpty(ancestorText))
  10.218 +			//    {
  10.219 +			//        _merger.EventListener.ChangeOccurred(new XmlTextAddedReport(_merger.MergeSituation.PathToFileInRepository, _theirs));
  10.220 +			//        _ours.InnerText = _theirs.InnerText; // We had it empty, or null.
  10.221 +			//        return;
  10.222 +			//    }
  10.223 +			//    if (ancestorText == theirText)
  10.224 +			//    {
  10.225 +			//        // They didn't touch it. So leave our deletion standing.
  10.226 +			//        _merger.EventListener.ChangeOccurred(new XmlTextDeletedReport(_merger.MergeSituation.PathToFileInRepository, _theirs));
  10.227 +			//        return;
  10.228 +			//    }
  10.229 +			//    //they edited it. Keep theirs under the principle of least data loss.
  10.230 +			//    _ours.InnerText = _theirs.InnerText;
  10.231 +			//    _merger.EventListener.ConflictOccurred(new RemovedVsEditedTextConflict(_ours, _theirs, _ancestor, _merger.MergeSituation,
  10.232 +			//        _merger.MergeSituation.BetaUserId));
  10.233 +			//    return;
  10.234 +			//}
  10.235 +
  10.236 +			//if ((_ancestor == null) || (ourText != ancestorText))
  10.237 +			//{
  10.238 +			//    //we're not empty, we edited it, and we don't equal theirs.
  10.239 +			//    //  Moved  EventListener.ChangeOccurred(new TextEditChangeReport(this.MergeSituation.PathToFileInRepository, SafelyGetStringTextNode(ancestor), SafelyGetStringTextNode(ours)));
  10.240 +			//    if (string.IsNullOrEmpty(theirText))
  10.241 +			//    {
  10.242 +			//        if (_ancestor == null || ancestorText == string.Empty)
  10.243 +			//        {
  10.244 +			//            // We both added at least the containing element for the text.
  10.245 +			//            if (ourText == theirText)
  10.246 +			//            {
  10.247 +			//                // Both added the same thing, even if it is only an empty element.
  10.248 +			//                // TODO: Add a "both added" change report.
  10.249 +			//                return;
  10.250 +			//            }
  10.251 +			//            if (theirText.Length > 0 && ourText == string.Empty)
  10.252 +			//            {
  10.253 +			//                // They added some content, even.
  10.254 +			//                //EventListener.ChangeOccurred(new XmlTextAddedReport(MergeSituation.PathToFileInRepository, theirs));
  10.255 +			//                return;
  10.256 +			//            }
  10.257 +			//            if (ourText.Length > 0 && theirText == string.Empty)
  10.258 +			//            {
  10.259 +			//                // We added some content, even.
  10.260 +			//                _merger.EventListener.ChangeOccurred(new XmlTextAddedReport(_merger.MergeSituation.PathToFileInRepository, _ours));
  10.261 +			//                return;
  10.262 +			//            }
  10.263 +			//        }
  10.264 +			//        //we edited, they deleted it. Keep ours.
  10.265 +			//        //EventListener.ConflictOccurred(new EditedVsRemovedTextConflict(ours, theirs, ancestor, MergeSituation,
  10.266 +			//        //	MergeSituation.AlphaUserId));
  10.267 +			//        return;
  10.268 +			//    }
  10.269 +			//    // We know: ours is different from theirs; ours is not empty; ours is different from ancestor;
  10.270 +			//    // theirs is not empty.
  10.271 +			//    if (_ancestor != null && _theirs.InnerText == _ancestor.InnerText)
  10.272 +			//    {
  10.273 +			//        // Moved from above.
  10.274 +			//        _merger.EventListener.ChangeOccurred(new XmlTextChangedReport(_merger.MergeSituation.PathToFileInRepository, _ours));
  10.275 +			//        return; // we edited it, they did not, keep ours.
  10.276 +			//    }
  10.277 +			//    //both edited it. Keep ours, but report conflict.
  10.278 +			//    //EventListener.ConflictOccurred(new BothEditedTextConflict(ours, theirs, ancestor, MergeSituation, MergeSituation.AlphaUserId));
  10.279 +			//    return;
  10.280 +			//}
  10.281 +
  10.282 +			//// we didn't edit it, they did
  10.283 +			//if (theirText != ancestorText)
  10.284 +			//{
  10.285 +			//    if (theirText == string.Empty)
  10.286 +			//    {
  10.287 +			//        _merger.EventListener.ChangeOccurred(new XmlTextDeletedReport(_merger.MergeSituation.PathToFileInRepository, _ours));
  10.288 +			//        _ours.InnerText = theirText;
  10.289 +			//        return;
  10.290 +			//    }
  10.291 +			//}
  10.292 +			////EventListener.ChangeOccurred(new TextEditChangeReport(MergeSituation.PathToFileInRepository, SafelyGetStringTextNode(ancestor), SafelyGetStringTextNode(theirs)));
  10.293 +			//_ours.InnerText = _theirs.InnerText;
  10.294 +		}
  10.295 +	}
  10.296 +}
  10.297 \ No newline at end of file
    11.1 --- a/src/LibChorus/merge/xml/generic/XmlMerger.cs	Sat Jan 21 15:48:29 2012 -0600
    11.2 +++ b/src/LibChorus/merge/xml/generic/XmlMerger.cs	Tue Jan 24 05:28:06 2012 -0600
    11.3 @@ -4,6 +4,7 @@
    11.4  using System.Linq;
    11.5  using System.Xml;
    11.6  using Chorus.FileTypeHanders.text;
    11.7 +using Chorus.FileTypeHanders.xml;
    11.8  using Chorus.Properties;
    11.9  
   11.10  namespace Chorus.merge.xml.generic
   11.11 @@ -71,21 +72,10 @@
   11.12  			if (ours != null && !ours.HasChildNodes && theirs != null && !theirs.HasChildNodes && ancestor != null && !ancestor.HasChildNodes)
   11.13  				return;
   11.14  
   11.15 -			// This loop is either a normal element, or it has only text content.
   11.16 -			var isTextLevel = ours != null && (ours.ChildNodes.Count == 1 && ours.FirstChild.NodeType == XmlNodeType.Text);
   11.17 -			isTextLevel = isTextLevel || theirs != null && (theirs.ChildNodes.Count == 1 && theirs.FirstChild.NodeType == XmlNodeType.Text);
   11.18 -			isTextLevel = isTextLevel || ancestor != null && (ancestor.ChildNodes.Count == 1 && ancestor.FirstChild.NodeType == XmlNodeType.Text);
   11.19 -			if (isTextLevel)
   11.20 -			{
   11.21 -				MergeTextNodes(ref ours, theirs, ancestor);
   11.22 -			}
   11.23 -			else
   11.24 -			{
   11.25 -				MergeChildren(ref ours, theirs, ancestor);
   11.26 -			}
   11.27 +			MergeChildren(ref ours, theirs, ancestor);
   11.28          }
   11.29  
   11.30 -        public NodeMergeResult Merge(string ourXml, string theirXml, string ancestorXml)
   11.31 +    	public NodeMergeResult Merge(string ourXml, string theirXml, string ancestorXml)
   11.32          {
   11.33              XmlDocument doc = new XmlDocument();
   11.34              XmlNode ourNode = XmlUtilities.GetDocumentNodeFromRawXml(ourXml, doc);
   11.35 @@ -131,6 +121,9 @@
   11.36  
   11.37          private static IEnumerable<XmlAttribute> GetAttrs(XmlNode node)
   11.38          {
   11.39 +			if (node is XmlCharacterData)
   11.40 +				return new List<XmlAttribute>();
   11.41 +
   11.42              //need to copy so we can iterate while changing
   11.43  			return new List<XmlAttribute>(node.Attributes.Cast<XmlAttribute>());
   11.44          }
   11.45 @@ -214,7 +207,7 @@
   11.46                  }
   11.47                  else
   11.48                  {
   11.49 -                    var strat = this.MergeStrategies.GetElementStrategy(ours);
   11.50 +                    var strat = MergeStrategies.GetElementStrategy(ours);
   11.51  
   11.52                      //for unit test see Merge_RealConflictPlusModDateConflict_ModDateNotReportedAsConflict()
   11.53                      if (strat == null || !strat.AttributesToIgnoreForMerging.Contains(ourAttr.Name))
   11.54 @@ -249,111 +242,13 @@
   11.55              }
   11.56          }
   11.57  
   11.58 -    	internal void MergeTextNodes(ref XmlNode ours, XmlNode theirs, XmlNode ancestor)
   11.59 -    	{
   11.60 -			if (ours != null && ours.ChildNodes.Count > 0 && ours.FirstChild.NodeType != XmlNodeType.Text)
   11.61 -				throw new ArgumentException(AnnotationImages.kNotATextElement, "ours");
   11.62 -			if (theirs != null && theirs.ChildNodes.Count > 0 && theirs.FirstChild.NodeType != XmlNodeType.Text)
   11.63 -				throw new ArgumentException(AnnotationImages.kNotATextElement, "ours");
   11.64 -			if (ancestor != null && ancestor.ChildNodes.Count > 0 && ancestor.FirstChild.NodeType != XmlNodeType.Text)
   11.65 -				throw new ArgumentException(AnnotationImages.kNotATextElement, "ours");
   11.66 -
   11.67 -			var ourText = ours == null ? null : ours.InnerText.Trim();
   11.68 -			var theirText = theirs == null ? null : theirs.InnerText.Trim();
   11.69 -			var ancestorText = ancestor == null ? null : ancestor.InnerText.Trim();
   11.70 -
   11.71 -			if (ourText == theirText)
   11.72 -			{
   11.73 -				// Null, empty strings or same.
   11.74 -				// [RBR: TODO: Right, but is it the same as ancestor, or did both make the same change?] It could be addition, deletion, or plain vanilla change.
   11.75 -				return; // we agree
   11.76 -			}
   11.77 -
   11.78 -			if (string.IsNullOrEmpty(ourText))
   11.79 -			{
   11.80 -				if (ancestor == null || string.IsNullOrEmpty(ancestorText))
   11.81 -				{
   11.82 -					// TODO: Add new text added report.
   11.83 -					ours.InnerText = theirs.InnerText; // We had it empty, or null.
   11.84 -					return;
   11.85 -				}
   11.86 -				if (ancestorText == theirText)
   11.87 -				{
   11.88 -					// TODO: Add new text deleted report.
   11.89 -					// They didn't touch it. So leave it deleted
   11.90 -					return;
   11.91 -				}
   11.92 -				//they edited it. Keep theirs under the principle of least data loss.
   11.93 -				ours.InnerText = theirs.InnerText;
   11.94 -				EventListener.ConflictOccurred(new RemovedVsEditedTextConflict(ours, theirs, ancestor, MergeSituation,
   11.95 -					MergeSituation.BetaUserId));
   11.96 -				return;
   11.97 -			}
   11.98 -
   11.99 -			if ((ancestor == null) || (ours.InnerText != ancestor.InnerText))
  11.100 -			{
  11.101 -				//we're not empty, we edited it, and we don't equal theirs.
  11.102 -				//  Moved  EventListener.ChangeOccurred(new TextEditChangeReport(this.MergeSituation.PathToFileInRepository, SafelyGetStringTextNode(ancestor), SafelyGetStringTextNode(ours)));
  11.103 -				if (string.IsNullOrEmpty(theirText))
  11.104 -				{
  11.105 -					if (ancestor == null)
  11.106 -					{
  11.107 -						// We both added at least the containing element for the text.
  11.108 -						if (ourText == theirText)
  11.109 -						{
  11.110 -							// Both added the same thing, even if it is only an empty element.
  11.111 -							// TODO: Add a "both added" change report.
  11.112 -							return;
  11.113 -						}
  11.114 -						if (theirText.Length > 0 && ourText == string.Empty)
  11.115 -						{
  11.116 -							// They added some content, even.
  11.117 -							// TODO: Add a "text addition" change report for the right person.
  11.118 -							return;
  11.119 -						}
  11.120 -						if (ourText.Length > 0 && theirText == string.Empty)
  11.121 -						{
  11.122 -							// We added some content, even.
  11.123 -							// TODO: Add a "text addition" change report for the right person.
  11.124 -							return;
  11.125 -						}
  11.126 -					}
  11.127 -					//we edited, they deleted it. Keep ours.
  11.128 -					EventListener.ConflictOccurred(new EditedVsRemovedTextConflict(ours, theirs, ancestor, MergeSituation,
  11.129 -						MergeSituation.AlphaUserId));
  11.130 -					return;
  11.131 -				}
  11.132 -				// We know: ours is different from theirs; ours is not empty; ours is different from ancestor;
  11.133 -				// theirs is not empty.
  11.134 -				if (ancestor != null && theirs.InnerText == ancestor.InnerText)
  11.135 -				{
  11.136 -					// Moved from aboe.
  11.137 -					EventListener.ChangeOccurred(new TextEditChangeReport(this.MergeSituation.PathToFileInRepository, SafelyGetStringTextNode(ancestor), SafelyGetStringTextNode(ours)));
  11.138 -					return; // we edited it, they did not, keep ours.
  11.139 -				}
  11.140 -				//both edited it. Keep ours, but report conflict.
  11.141 -				EventListener.ConflictOccurred(new BothEditedTextConflict(ours, theirs, ancestor, MergeSituation, MergeSituation.AlphaUserId));
  11.142 -				return;
  11.143 -			}
  11.144 -			// we didn't edit it, they did
  11.145 -			EventListener.ChangeOccurred(new TextEditChangeReport(MergeSituation.PathToFileInRepository, SafelyGetStringTextNode(ancestor), SafelyGetStringTextNode(theirs)));
  11.146 -			ours.InnerText = theirs.InnerText;
  11.147 -        }
  11.148 -
  11.149 -        private static string SafelyGetStringTextNode(XmlNode node)
  11.150 -        {
  11.151 -            if(node==null || node.InnerText==null)
  11.152 -                return String.Empty;
  11.153 -            return node.InnerText.Trim();
  11.154 -        }
  11.155 -
  11.156 -        private void MergeChildren(ref XmlNode ours, XmlNode theirs, XmlNode ancestor)
  11.157 +    	private void MergeChildren(ref XmlNode ours, XmlNode theirs, XmlNode ancestor)
  11.158          {
  11.159              //is this a level of the xml file that would consitute the minimal unit conflict-understanding
  11.160              //from a user perspecitve?
  11.161              //e.g., in a dictionary, this is the lexical entry.  In a text, it might be  a paragraph.
  11.162              var generator = MergeStrategies.GetElementStrategy(ours).ContextDescriptorGenerator;
  11.163 -            if(generator!=null)
  11.164 +            if(generator != null)
  11.165              {
  11.166                  //review: question: does this not get called at levels below the entry?
  11.167                  //this would seem to fail at, say, a sense. I'm confused. (JH 30june09)
  11.168 @@ -363,8 +258,5 @@
  11.169  
  11.170              new MergeChildrenMethod(ours, theirs, ancestor, this).Run();
  11.171          }
  11.172 -
  11.173 -
  11.174 -
  11.175      }
  11.176  }
  11.177 \ No newline at end of file
    12.1 --- a/src/LibChorus/merge/xml/generic/XmlUtilities.cs	Sat Jan 21 15:48:29 2012 -0600
    12.2 +++ b/src/LibChorus/merge/xml/generic/XmlUtilities.cs	Tue Jan 24 05:28:06 2012 -0600
    12.3 @@ -2,7 +2,9 @@
    12.4  using System.CodeDom.Compiler;
    12.5  using System.Collections;
    12.6  using System.Collections.Generic;
    12.7 +using System.Diagnostics;
    12.8  using System.IO;
    12.9 +using System.Linq;
   12.10  using System.Reflection;
   12.11  using System.Text;
   12.12  using System.Xml;
   12.13 @@ -72,10 +74,8 @@
   12.14          }
   12.15      }
   12.16  
   12.17 -    public class XmlUtilities
   12.18 +    public static class XmlUtilities
   12.19      {
   12.20 -
   12.21 -    
   12.22          public static bool AreXmlElementsEqual(string ours, string theirs)
   12.23          {
   12.24  			if (ours == theirs)
   12.25 @@ -85,7 +85,7 @@
   12.26              XmlReader or = XmlReader.Create(osr);
   12.27              XmlDocument od = new XmlDocument();
   12.28              XmlNode on = od.ReadNode(or);
   12.29 -            on.Normalize();
   12.30 +            @on.Normalize();
   12.31  
   12.32              StringReader tsr = new StringReader(theirs);
   12.33              XmlReader tr = XmlReader.Create(tsr);
   12.34 @@ -93,7 +93,7 @@
   12.35              XmlNode tn = td.ReadNode(tr);
   12.36              tn.Normalize();//doesn't do much
   12.37  
   12.38 -            return AreXmlElementsEqual(on, tn);
   12.39 +            return AreXmlElementsEqual(@on, tn);
   12.40          }
   12.41  
   12.42          /// <summary>
   12.43 @@ -114,7 +114,7 @@
   12.44              XmlReader or = XmlReader.Create(osr);
   12.45              XmlDocument od = new XmlDocument();
   12.46              XmlNode on = od.ReadNode(or);
   12.47 -            on.Normalize();
   12.48 +            @on.Normalize();
   12.49  
   12.50              StringReader tsr = new StringReader(theirs);
   12.51              XmlReader tr = XmlReader.Create(tsr);
   12.52 @@ -122,14 +122,14 @@
   12.53              XmlNode tn = td.ReadNode(tr);
   12.54              tn.Normalize();//doesn't do much
   12.55  
   12.56 -            System.Diagnostics.Debug.Assert(astrElementXPath.Length == astrAttributeToIgnore.Length);
   12.57 +            Debug.Assert(astrElementXPath.Length == astrAttributeToIgnore.Length);
   12.58              for (int i = 0; i < astrElementXPath.Length; i++)
   12.59              {
   12.60 -                RemoveItem(on, astrElementXPath[i], astrAttributeToIgnore[i]);
   12.61 +                RemoveItem(@on, astrElementXPath[i], astrAttributeToIgnore[i]);
   12.62                  RemoveItem(tn, astrElementXPath[i], astrAttributeToIgnore[i]);
   12.63              }
   12.64  
   12.65 -            return AreXmlElementsEqual(on, tn);
   12.66 +            return AreXmlElementsEqual(@on, tn);
   12.67          }
   12.68  
   12.69          private static void RemoveItem(XmlNode node, string strXPath, string strAttribute)
   12.70 @@ -230,6 +230,47 @@
   12.71  		{
   12.72  			return XElement.Parse(xml).ToString();
   12.73  		}
   12.74 +
   12.75 +    	public static string SafelyGetStringTextNode(XmlNode node)
   12.76 +    	{
   12.77 +    		return (node == null || node.InnerText == String.Empty) ? String.Empty : node.InnerText.Trim();
   12.78 +    	}
   12.79 +
   12.80 +		public static bool IsTextLevel(XmlNode ours, XmlNode theirs, XmlNode ancestor)
   12.81 +		{
   12.82 +			return (ours != null || theirs != null || ancestor != null)
   12.83 +				&& ((IsTextLevel(ours) || ours == null)
   12.84 +				&& (IsTextLevel(theirs) || theirs == null)
   12.85 +				&& (IsTextLevel(ancestor) || ancestor == null));
   12.86 +		}
   12.87 +
   12.88 +    	public static bool IsTextLevel(XmlNode node)
   12.89 +    	{
   12.90 +    		var badNodeTypes = new HashSet<XmlNodeType>
   12.91 +    		                   	{
   12.92 +									XmlNodeType.None,
   12.93 +									XmlNodeType.Element,
   12.94 +    		                   		XmlNodeType.Attribute,
   12.95 +									XmlNodeType.CDATA,
   12.96 +									XmlNodeType.EntityReference,
   12.97 +									XmlNodeType.Entity,
   12.98 +									XmlNodeType.ProcessingInstruction,
   12.99 +									XmlNodeType.Comment,
  12.100 +									XmlNodeType.Document,
  12.101 +									XmlNodeType.DocumentType,
  12.102 +									XmlNodeType.DocumentFragment,
  12.103 +									XmlNodeType.Notation,
  12.104 +									XmlNodeType.EndElement,
  12.105 +									XmlNodeType.EndEntity,
  12.106 +									XmlNodeType.XmlDeclaration
  12.107 +    		                   	};
  12.108 +			// I (RBR) think the only ones we can cope with are:
  12.109 +			// Text, Whitespace, and SignificantWhitespace.
  12.110 +			// Alternatively, one might be able to use the XmlText, XmlWhitespace,
  12.111 +			// and XmlSignificantWhitespace subclasses of XmlCharacterData (leaving out XmlCDataSection and XmlComment), which match well with those legal enums
  12.112 +
  12.113 +			return node == null || (node.NodeType == XmlNodeType.Element && (!node.HasChildNodes || node.ChildNodes.Cast<XmlNode>().All(childNode => !badNodeTypes.Contains(childNode.NodeType))));
  12.114 +    	}
  12.115      }
  12.116  
  12.117      public class XmlFormatException : ApplicationException
    13.1 --- a/src/LibChorusTests/LibChorus.Tests.csproj	Sat Jan 21 15:48:29 2012 -0600
    13.2 +++ b/src/LibChorusTests/LibChorus.Tests.csproj	Tue Jan 24 05:28:06 2012 -0600
    13.3 @@ -129,6 +129,7 @@
    13.4      <Compile Include="merge\xml\generic\ConflictIOTest.cs" />
    13.5      <Compile Include="merge\xml\generic\MergeChildrenMethodTests.cs" />
    13.6      <Compile Include="merge\xml\generic\MergeChildrenMethod_DiffOnlyTests.cs" />
    13.7 +    <Compile Include="merge\xml\generic\TextElementMergeTests.cs" />
    13.8      <Compile Include="merge\xml\generic\xmldiff\XmlDiffTests.cs" />
    13.9      <Compile Include="merge\xml\generic\XmlMergerTests.cs" />
   13.10      <Compile Include="merge\xml\generic\XmlTestHelper.cs" />
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/src/LibChorusTests/merge/xml/generic/TextElementMergeTests.cs	Tue Jan 24 05:28:06 2012 -0600
    14.3 @@ -0,0 +1,299 @@
    14.4 +using System;
    14.5 +using System.Collections.Generic;
    14.6 +using Chorus.FileTypeHanders.xml;
    14.7 +using Chorus.merge.xml.generic;
    14.8 +using NUnit.Framework;
    14.9 +
   14.10 +namespace LibChorus.Tests.merge.xml.generic
   14.11 +{
   14.12 +	[TestFixture]
   14.13 +	public class TextElementMergeTests
   14.14 +	{
   14.15 +		private MergeStrategies _mergeStrategies;
   14.16 +
   14.17 +		[SetUp]
   14.18 +		public void TestSetup()
   14.19 +		{
   14.20 +			_mergeStrategies = new MergeStrategies();
   14.21 +			_mergeStrategies.ElementStrategies.Add("a", ElementStrategy.CreateSingletonElement());
   14.22 +			_mergeStrategies.ElementStrategies.Add("b", ElementStrategy.CreateSingletonElement());
   14.23 +			_mergeStrategies.ElementStrategies.Add("c", ElementStrategy.CreateSingletonElement());
   14.24 +			_mergeStrategies.ElementStrategies.Add("d", ElementStrategy.CreateSingletonElement());
   14.25 +		}
   14.26 +
   14.27 +		[TearDown]
   14.28 +		public void TestTearDown()
   14.29 +		{
   14.30 +			_mergeStrategies = null;
   14.31 +		}
   14.32 +
   14.33 +		[Test]
   14.34 +		public void WeAddedNewTextElementToNonExistingElementTheyDidNothingHasOneChangeReport()
   14.35 +		{
   14.36 +			const string ancestor = @"<a/>";
   14.37 +			const string ours =
   14.38 +@"<a>
   14.39 +	<b>ourNewText</b>
   14.40 +</a>";
   14.41 +			const string theirs = ancestor;
   14.42 +
   14.43 +			XmlTestHelper.DoMerge(
   14.44 +				_mergeStrategies,
   14.45 +				ancestor, ours, theirs,
   14.46 +				new List<string> { "a/b[text()='ourNewText']" },
   14.47 +				null,
   14.48 +				0, null,
   14.49 +				1, new List<Type> { typeof(XmlTextAddedReport) });
   14.50 +		}
   14.51 +
   14.52 +		[Test]
   14.53 +		public void WeAddedNewTextToExtantTextElementTheyDidNothingHasOneChangeReport()
   14.54 +		{
   14.55 +			const string ancestor =
   14.56 +@"<a>
   14.57 +	<b/>
   14.58 +</a>";
   14.59 +			const string ours =
   14.60 +@"<a>
   14.61 +	<b>ourNewText</b>
   14.62 +</a>";
   14.63 +			const string theirs = ancestor;
   14.64 +
   14.65 +			XmlTestHelper.DoMerge(
   14.66 +				_mergeStrategies,
   14.67 +				ancestor, ours, theirs,
   14.68 +				new List<string> { "a/b[text()='ourNewText']" },
   14.69 +				null,
   14.70 +				0, null,
   14.71 +				1, new List<Type> { typeof(XmlTextAddedReport) });
   14.72 +		}
   14.73 +
   14.74 +		[Test]
   14.75 +		public void TheyAddedNewTextElementToNonExistingElementWeDidNothingHasOneChangeReport()
   14.76 +		{
   14.77 +			const string ancestor = @"<a/>";
   14.78 +			const string ours = ancestor;
   14.79 +			const string theirs =
   14.80 +@"<a>
   14.81 +	<b>theirNewText</b>
   14.82 +</a>";
   14.83 +			XmlTestHelper.DoMerge(
   14.84 +				_mergeStrategies,
   14.85 +				ancestor, ours, theirs,
   14.86 +				new List<string> { "a/b[text()='theirNewText']" },
   14.87 +				null,
   14.88 +				0, null,
   14.89 +				1, new List<Type> { typeof(XmlTextAddedReport) });
   14.90 +		}
   14.91 +
   14.92 +		[Test]
   14.93 +		public void TheyAddedNewTextToExtantTextElementWeDidNothingHasOneChangeReport()
   14.94 +		{
   14.95 +			const string ancestor =
   14.96 +@"<a>
   14.97 +	<b/>
   14.98 +</a>";
   14.99 +			const string ours = ancestor;
  14.100 +			const string theirs =
  14.101 +@"<a>
  14.102 +	<b>theirNewText</b>
  14.103 +</a>";
  14.104 +
  14.105 +			XmlTestHelper.DoMerge(
  14.106 +				_mergeStrategies,
  14.107 +				ancestor, ours, theirs,
  14.108 +				new List<string> { "a/b[text()='theirNewText']" },
  14.109 +				null,
  14.110 +				0, null,
  14.111 +				1, new List<Type> { typeof(XmlTextAddedReport) });
  14.112 +		}
  14.113 +
  14.114 +		[Test]
  14.115 +		public void TheyDeletedTextNodeButNotTextParent1WeDidNothingHasOneChangeReport()
  14.116 +		{
  14.117 +			const string ancestor =
  14.118 +@"<a>
  14.119 +	<b>originalText</b>
  14.120 +</a>";
  14.121 +			const string ours = ancestor;
  14.122 +			const string theirs =
  14.123 +@"<a>
  14.124 +	<b></b>
  14.125 +</a>";
  14.126 +
  14.127 +			XmlTestHelper.DoMerge(
  14.128 +				_mergeStrategies,
  14.129 +				ancestor, ours, theirs,
  14.130 +				new List<string> { "a/b" },
  14.131 +				new List<string> { "a/b[text()='originalText']" },
  14.132 +				0, null,
  14.133 +				1, new List<Type> { typeof(XmlTextDeletedReport) });
  14.134 +		}
  14.135 +
  14.136 +		[Test]
  14.137 +		public void WeDeletedTextNodeButNotTextParent2TheyDidNothingHasOneChangeReport()
  14.138 +		{
  14.139 +			const string ancestor =
  14.140 +@"<a>
  14.141 +	<b>originalText</b>
  14.142 +</a>";
  14.143 +			const string ours =
  14.144 +@"<a>
  14.145 +	<b/>
  14.146 +</a>";
  14.147 +			const string theirs = ancestor;
  14.148 +
  14.149 +			XmlTestHelper.DoMerge(
  14.150 +				_mergeStrategies,
  14.151 +				ancestor, ours, theirs,
  14.152 +				new List<string> { "a/b" },
  14.153 +				new List<string> { "a/b[text()='originalText']" },
  14.154 +				0, null,
  14.155 +				1, new List<Type> { typeof(XmlTextDeletedReport) });
  14.156 +		}
  14.157 +
  14.158 +		[Test]
  14.159 +		public void WeDeletedTextNodeButNotTextParent1TheyDidNothingHasOneChangeReport()
  14.160 +		{
  14.161 +			const string ancestor =
  14.162 +@"<a>
  14.163 +	<b>originalText</b>
  14.164 +</a>";
  14.165 +			const string ours =
  14.166 +@"<a>
  14.167 +	<b></b>
  14.168 +</a>";
  14.169 +			const string theirs = ancestor;
  14.170 +
  14.171 +			XmlTestHelper.DoMerge(
  14.172 +				_mergeStrategies,
  14.173 +				ancestor, ours, theirs,
  14.174 +				new List<string> { "a/b" },
  14.175 +				new List<string> { "a/b[text()='originalText']" },
  14.176 +				0, null,
  14.177 +				1, new List<Type> { typeof(XmlTextDeletedReport) });
  14.178 +		}
  14.179 +
  14.180 +		[Test]
  14.181 +		public void TheyDeletedTextNodeButNotTextParent2WeDidNothingHasOneChangeReport()
  14.182 +		{
  14.183 +			const string ancestor =
  14.184 +@"<a>
  14.185 +	<b>originalText</b>
  14.186 +</a>";
  14.187 +			const string ours = ancestor;
  14.188 +			const string theirs =
  14.189 +@"<a>
  14.190 +	<b/>
  14.191 +</a>";
  14.192 +
  14.193 +			XmlTestHelper.DoMerge(
  14.194 +				_mergeStrategies,
  14.195 +				ancestor, ours, theirs,
  14.196 +				new List<string> { "a/b" },
  14.197 +				new List<string> { "a/b[text()='originalText']" },
  14.198 +				0, null,
  14.199 +				1, new List<Type> { typeof(XmlTextDeletedReport) });
  14.200 +		}
  14.201 +
  14.202 +		[Test]
  14.203 +		public void BothDeletedWithOneChangeReport1()
  14.204 +		{
  14.205 +			const string ancestor =
  14.206 +@"<a>
  14.207 +	<b>originalText</b>
  14.208 +</a>";
  14.209 +			const string ours =
  14.210 +@"<a>
  14.211 +	<b/>
  14.212 +</a>";
  14.213 +			const string theirs = ours;
  14.214 +
  14.215 +			XmlTestHelper.DoMerge(
  14.216 +				_mergeStrategies,
  14.217 +				ancestor, ours, theirs,
  14.218 +				new List<string> { "a/b" },
  14.219 +				new List<string> { "a/b[text()='originalText']" },
  14.220 +				0, null,
  14.221 +				1, new List<Type> { typeof(XmlTextBothDeletedReport) });
  14.222 +		}
  14.223 +
  14.224 +		[Test]
  14.225 +		public void BothDeletedWithOneChangeReport2()
  14.226 +		{
  14.227 +			const string ancestor =
  14.228 +@"<a>
  14.229 +	<b>originalText</b>
  14.230 +</a>";
  14.231 +			const string ours =
  14.232 +@"<a>
  14.233 +</a>";
  14.234 +			const string theirs = ours;
  14.235 +
  14.236 +			XmlTestHelper.DoMerge(
  14.237 +				_mergeStrategies,
  14.238 +				ancestor, ours, theirs,
  14.239 +				null,
  14.240 +				new List<string> { "a/b" },
  14.241 +				0, null,
  14.242 +				1, new List<Type> { typeof(XmlTextBothDeletedReport) });
  14.243 +		}
  14.244 +
  14.245 +		[Test]
  14.246 +		public void BothDeletedWithOneChangeReport3()
  14.247 +		{
  14.248 +			const string ancestor =
  14.249 +@"<a>
  14.250 +	<b>originalText</b>
  14.251 +</a>";
  14.252 +			const string ours = "<a/>";
  14.253 +			const string theirs = ours;
  14.254 +
  14.255 +			XmlTestHelper.DoMerge(
  14.256 +				_mergeStrategies,
  14.257 +				ancestor, ours, theirs,
  14.258 +				null,
  14.259 +				new List<string> { "a/b" },
  14.260 +				0, null,
  14.261 +				1, new List<Type> { typeof(XmlTextBothDeletedReport) });
  14.262 +		}
  14.263 +
  14.264 +		[Test]
  14.265 +		public void WeEditedTheyDidNothingOneChange()
  14.266 +		{
  14.267 +			const string ancestor = "<a><b>before</b></a>";
  14.268 +			const string ours = "<a><b>after</b></a>";
  14.269 +			const string theirs = ancestor;
  14.270 +
  14.271 +			XmlTestHelper.DoMerge(
  14.272 +				_mergeStrategies,
  14.273 +				ancestor, ours, theirs,
  14.274 +				new List<string> { "a/b[text()='after']" },
  14.275 +				null,
  14.276 +				0, null,
  14.277 +				1, new List<Type> { typeof(XmlTextChangedReport) });
  14.278 +		}
  14.279 +
  14.280 +		[Test]
  14.281 +		public void TheyEditedWeDidNothingOneChange()
  14.282 +		{
  14.283 +			const string ancestor = "<a><b>before</b></a>";
  14.284 +			const string ours = ancestor;
  14.285 +			const string theirs = "<a><b>after</b></a>";
  14.286 +
  14.287 +			XmlTestHelper.DoMerge(
  14.288 +				_mergeStrategies,
  14.289 +				ancestor, ours, theirs,
  14.290 +				new List<string> { "a/b[text()='after']" },
  14.291 +				null,
  14.292 +				0, null,
  14.293 +				1, new List<Type> { typeof(XmlTextChangedReport) });
  14.294 +		}
  14.295 +
  14.296 +		// TODO: Both made same edits change report (both?).
  14.297 +
  14.298 +		// TODO: RemovedVsEdit conflict report (both).
  14.299 +		// TODO: EditVsRemove conflict report (both).
  14.300 +		// TODO: Conflicting edits conflict report (both).
  14.301 +	}
  14.302 +}
  14.303 \ No newline at end of file
    15.1 --- a/src/LibChorusTests/merge/xml/generic/XmlMergerTests.cs	Sat Jan 21 15:48:29 2012 -0600
    15.2 +++ b/src/LibChorusTests/merge/xml/generic/XmlMergerTests.cs	Tue Jan 24 05:28:06 2012 -0600
    15.3 @@ -1,12 +1,11 @@
    15.4  using System;
    15.5  using Chorus.merge;
    15.6  using Chorus.merge.xml.generic;
    15.7 -using LibChorus.Tests.merge.xml;
    15.8  using NUnit.Framework;
    15.9  
   15.10  namespace LibChorus.Tests.merge.xml.generic
   15.11  {
   15.12 -    [TestFixture]
   15.13 +	[TestFixture]
   15.14      public class XmlMergerTests
   15.15      {
   15.16          [Test]
   15.17 @@ -81,14 +80,15 @@
   15.18              CheckBothWaysNoConflicts("<r><t/></r>", "<r><t></t></r>", "<r><t>hello</t></r>",
   15.19                                       "r/t[not(text())]",
   15.20                                       "r[count(t)=1]");
   15.21 -        }
   15.22 +		}
   15.23  
   15.24 -        [Test]
   15.25 -        public void TextElement_OneEdited_NoConflicts()
   15.26 -        {
   15.27 -            CheckBothWaysNoConflicts("<r><t>after</t></r>", "<r><t>before</t></r>", "<r><t>before</t></r>",
   15.28 -                                     "r/t[contains(text(),'after')]");
   15.29 -        }
   15.30 +		// Moved to TextElementMergeTests
   15.31 +		//[Test]
   15.32 +		//public void TextElement_OneEdited_NoConflicts()
   15.33 +		//{
   15.34 +		//    CheckBothWaysNoConflicts("<r><t>after</t></r>", "<r><t>before</t></r>", "<r><t>before</t></r>",
   15.35 +		//                             "r/t[contains(text(),'after')]");
   15.36 +		//}
   15.37  
   15.38  
   15.39  
    16.1 --- a/src/LibChorusTests/merge/xml/generic/XmlTestHelper.cs	Sat Jan 21 15:48:29 2012 -0600
    16.2 +++ b/src/LibChorusTests/merge/xml/generic/XmlTestHelper.cs	Tue Jan 24 05:28:06 2012 -0600
    16.3 @@ -3,6 +3,9 @@
    16.4  using System.IO;
    16.5  using System.Text;
    16.6  using System.Xml;
    16.7 +using Chorus.merge;
    16.8 +using Chorus.merge.xml.generic;
    16.9 +using LibChorus.Tests.merge.xml.generic;
   16.10  using NUnit.Framework;
   16.11  
   16.12  namespace LibChorus.Tests.merge.xml
   16.13 @@ -128,5 +131,53 @@
   16.14  			}
   16.15  			Assert.IsNull(node);
   16.16  		}
   16.17 +
   16.18 +    	public static string DoMerge(
   16.19 +			MergeStrategies mergeStrategies,
   16.20 +			string ancestorXml, string ourXml, string theirXml,
   16.21 +			IEnumerable<string> xpathQueriesThatMatchExactlyOneNode, IEnumerable<string> xpathQueriesThatReturnNull,
   16.22 +			int expectedConflictCount, List<Type> expectedConflictTypes,
   16.23 +			int expectedChangesCount, List<Type> expectedChangeTypes)
   16.24 +		{
   16.25 +			var doc = new XmlDocument();
   16.26 +			var ourNode = XmlUtilities.GetDocumentNodeFromRawXml(ourXml, doc);
   16.27 +			var theirNode = XmlUtilities.GetDocumentNodeFromRawXml(theirXml, doc);
   16.28 +			var ancestorNode = XmlUtilities.GetDocumentNodeFromRawXml(ancestorXml, doc);
   16.29 +
   16.30 +			var mergeSituation = new NullMergeSituation();
   16.31 +			var eventListener = new ListenerForUnitTests();
   16.32 +    		var merger = new XmlMerger(mergeSituation)
   16.33 +    		             	{
   16.34 +								MergeStrategies = mergeStrategies
   16.35 +    		             	};
   16.36 +			var retval = merger.Merge(eventListener, ourNode, theirNode, ancestorNode).OuterXml;
   16.37 +			Assert.AreSame(eventListener, merger.EventListener); // Make sure it never changes it, while we aren't looking, since at least one Merge method does that very thing.
   16.38 +
   16.39 +			if (xpathQueriesThatMatchExactlyOneNode != null)
   16.40 +			{
   16.41 +				foreach (var query in xpathQueriesThatMatchExactlyOneNode)
   16.42 +					AssertXPathMatchesExactlyOne(retval, query);
   16.43 +			}
   16.44 +
   16.45 +			if (xpathQueriesThatReturnNull != null)
   16.46 +			{
   16.47 +				foreach (var query in xpathQueriesThatReturnNull)
   16.48 +					AssertXPathIsNull(retval, query);
   16.49 +			}
   16.50 +
   16.51 +			eventListener.AssertExpectedConflictCount(expectedConflictCount);
   16.52 +    		expectedConflictTypes = expectedConflictTypes ?? new List<Type>();
   16.53 +			Assert.AreEqual(expectedConflictTypes.Count, eventListener.Conflicts.Count, "Expected conflict count and actual number found differ.");
   16.54 +			for (var idx = 0; idx < expectedConflictTypes.Count; ++idx)
   16.55 +				Assert.AreSame(expectedConflictTypes[idx], eventListener.Conflicts[idx].GetType());
   16.56 +
   16.57 +			eventListener.AssertExpectedChangesCount(expectedChangesCount);
   16.58 +    		expectedChangeTypes = expectedChangeTypes ?? new List<Type>();
   16.59 +			Assert.AreEqual(expectedChangeTypes.Count, eventListener.Changes.Count, "Expected change count and actual number found differ.");
   16.60 +			for (var idx = 0; idx < expectedChangeTypes.Count; ++idx)
   16.61 +				Assert.AreSame(expectedChangeTypes[idx], eventListener.Changes[idx].GetType());
   16.62 +
   16.63 +			return retval;
   16.64 +		}
   16.65      }
   16.66  }
   16.67 \ No newline at end of file