diff --git a/FatAntelope.Tests/XdtDiffWriterTests.cs b/FatAntelope.Tests/XdtDiffWriterTests.cs index a8b147f..69bcad4 100644 --- a/FatAntelope.Tests/XdtDiffWriterTests.cs +++ b/FatAntelope.Tests/XdtDiffWriterTests.cs @@ -88,6 +88,64 @@ public void InsertBefore() AssertCanTransform(source, target); } + [TestMethod] + public void InsertFollowedByParentChange() + { + // Insert a child element into a parent element that has a change affecting its unique trait. + + var source = @" + + + + + + + "; + + var target = @" + + + + + + + + + "; + + var patch = GetPatch(source, target); + + AssertCanTransform(source, target); + } + + [TestMethod] + public void InsertAndRemove() + { + // Insert a child element into a parent element that has a change affecting its unique trait. + + var source = @" + + + + + + "; + + var target = @" + + + + + + + + "; + + var patch = GetPatch(source, target); + + AssertCanTransform(source, target); + } + [TestMethod] public void InsertChildWithParentChange() { @@ -181,14 +239,14 @@ public void InsertBeforeSingle() var patch = GetPatch(source, target); // Values - AssertValue(patch.SelectSingleNode("/root/child[1]/@name"), "child1"); - AssertValue(patch.SelectSingleNode("/root/child[2]/@name"), "child2"); - - // Transform = InsertBefore - AssertTransform(patch.SelectSingleNode("/root/child[1]"), "InsertBefore(/root/*[1])"); + AssertValue(patch.SelectSingleNode("/root/child[1]/@name"), "child2"); + AssertValue(patch.SelectSingleNode("/root/child[2]/@name"), "child1"); // Transform = SetAttributes - AssertTransform(patch.SelectSingleNode("/root/child[2]"), "SetAttributes(value)"); + AssertTransform(patch.SelectSingleNode("/root/child[1]"), "SetAttributes(value)"); + + // Transform = InsertBefore + AssertTransform(patch.SelectSingleNode("/root/child[2]"), "InsertBefore(/root/*[1])"); AssertCanTransform(source, target); } diff --git a/FatAntelope/Writers/XdtDiffWriter.cs b/FatAntelope/Writers/XdtDiffWriter.cs index 602dd6a..ec1bc22 100644 --- a/FatAntelope/Writers/XdtDiffWriter.cs +++ b/FatAntelope/Writers/XdtDiffWriter.cs @@ -226,23 +226,41 @@ private XmlNode WriteElement(XNode oldElement, XNode newElement, XmlNode target, : null; } - // Process child elements + // Calculate xpath to current element for use in child node changes path = GetPath(path, oldElement, oldTrait); + + // Process 'changed' child elements first for (var i = 0; i < newElement.Elements.Length; i++ ) { var child = newElement.Elements[i]; - if (child.Match == MatchType.Change || child.Match == MatchType.NoMatch) + if (child.Match == MatchType.Change) WriteElement(child.Matching, child, element, path, i); } - - // Remove elements that dont exist in the target config - // HACK: Reverse to remove from the end first, this prevents the condition when using - // positional predicates to remove child[1] and then try also to remove child[2], - // however, it has now moved up into position [1]. - foreach(var child in oldElement.Elements.Reverse()) + + // Process 'inserted' and 'removed' child elements together in reverse + var newElements = newElement.Elements; + var oldElements = oldElement.Elements; + var max = newElements.Length > oldElements.Length + ? newElements.Length + : oldElements.Length; + + for (var i = max - 1; i >= 0; i--) { - if (child.Match == MatchType.NoMatch) - WriteElement(child, null, element, path, 0); + // Remove child element + if(oldElements.Length > i) + { + var remove = oldElements[i]; + if (remove.Match == MatchType.NoMatch) + WriteElement(remove, null, element, path, 0); + } + + // Insert child element + if (newElements.Length > i) + { + var insert = newElements[i]; + if (insert.Match == MatchType.NoMatch) + WriteElement(insert.Matching, insert, element, path, i); + } } return element;