diff --git a/Rebracer.Tests/UtilitiesTets/XmlMergerTests.cs b/Rebracer.Tests/UtilitiesTets/XmlMergerTests.cs index 01dda7c..f6391d9 100644 --- a/Rebracer.Tests/UtilitiesTets/XmlMergerTests.cs +++ b/Rebracer.Tests/UtilitiesTets/XmlMergerTests.cs @@ -245,6 +245,36 @@ public void ReorderingNewElementsGetNewLines() { "); } + [TestMethod] + public void ReplacedChildElementsPreserveIndentation() { + // Note two tabs before each element + var source = @" + + Hi! + + Bye! + + + +"; + var container = XElement.Parse(source, LoadOptions.PreserveWhitespace); + var newSource = MergeElements(container, + new XElement("a", new XAttribute("b", "c"), + new XElement("x1", "Hi!"), + new XElement("x2", new XElement("NewContent", "Bye!")) + ) + ); + container.ToString().Should().Be(@" + + Hi! + + Bye! + + + +"); + } + [TestMethod] public void ReorderingElementsPreservesComments() { // Note two tabs before each element diff --git a/Rebracer/Utilities/XmlMerger.cs b/Rebracer/Utilities/XmlMerger.cs index 09e4aaa..ab395d5 100644 --- a/Rebracer/Utilities/XmlMerger.cs +++ b/Rebracer/Utilities/XmlMerger.cs @@ -52,6 +52,7 @@ public static bool MergeElements(this XElement container, IEnumerable if (!changed && !XNode.DeepEquals(o, newItems[newIndex].Value)) changed = true; o.ReplaceWith(newItems[newIndex].Value); + IndentChildren(newItems[newIndex].Value); newIndex++; } @@ -92,15 +93,16 @@ public static bool MergeElements(this XElement container, IEnumerable ///Gets all whitespace and comment nodes before the specified element, until its preceding element. static IEnumerable GetPrecedingTrivia(this XElement element) { var lastElem = element.ElementsBeforeSelf().LastOrDefault(); - if (lastElem == null) // If it's the first element, take all preceding nodes + if (lastElem == null) // If it's the first element, take all preceding nodes return element.NodesBeforeSelf(); - else // Otherwise, take all nodes after the prior element. + else // Otherwise, take all nodes after the prior element. return lastElem.NodesAfterSelf().TakeWhile(n => n != element); } ///A method to insert nodes into a LINQ to XML document. delegate void NodeInserter(params object[] content); - private static XNode GetPrecedingWhitespace(this XElement element) { + ///Gets the XText node containing the whitespace used to indent this element. If there is no preceding whitespace, the following whitespace will be returned, if any. + private static XText GetPrecedingWhitespace(this XElement element) { // If we started from an empty parent, there is no known whitespace if (element == null) return null; @@ -111,5 +113,48 @@ private static XNode GetPrecedingWhitespace(this XElement element) { return null; return new XText(sample); } + + ///Indents the children of an inserted element, based on the indentation of the parent element. + public static void IndentChildren(this XElement parent) { + var parentPrefix = parent.GetPrecedingWhitespace(); + if (parentPrefix == null) // No known indent to start from + return; + + // Copy the child list before we start mutating. + // Looking at XContainer source code, this isn't + // strictly necessary. + var childElements = parent.Elements().ToList(); + if (childElements.Count == 0) + return; + parent.Add(parentPrefix); // Indent the closing tag. + + var childPrefix = GetChildPrefix(parent.Depth(), parentPrefix.Value); + if (string.IsNullOrEmpty(childPrefix)) + return; + + foreach (var child in childElements) { + child.AddBeforeSelf(childPrefix); + IndentChildren(child); + } + } + + public static int Depth(this XElement element) { + if (element.Parent == null) + return 0; + return element.Parent.Depth() + 1; + } + + private static string GetChildPrefix(int parentDepth, string parentPrefix) { + string newLine = parentPrefix.Substring(0, parentPrefix.TakeWhile(c => c == '\r' || c == '\n').Count()); + parentPrefix = parentPrefix.Substring(newLine.Length); + + string levelIndent; + if (parentDepth == 0) + return newLine; // Don't know how to indent + else + levelIndent = parentPrefix.Substring(0, parentPrefix.Length / parentDepth); + + return newLine + string.Concat(Enumerable.Repeat(levelIndent, parentDepth + 1)); + } } }