chorus
changeset 853:fa661656d390 MergeImprovements
INtermediate commit. (Too many tests fail, but I wanted to get the changes in.)
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
