diff --git a/OpenXMLTemplates/Documents/TemplateDocument.cs b/OpenXMLTemplates/Documents/TemplateDocument.cs index e81c37a..64d7602 100644 --- a/OpenXMLTemplates/Documents/TemplateDocument.cs +++ b/OpenXMLTemplates/Documents/TemplateDocument.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; using OpenXMLTemplates.Utils; @@ -113,25 +114,57 @@ internal void AddControl(ContentControl control, bool isFirstOrder) public void RemoveControlsAndKeepContent() { - foreach (var control in allContentControls) - { - var sdtElement = control.SdtElement; - - var contentElement = sdtElement.Descendants() - .FirstOrDefault(d => d is SdtContentBlock || d is SdtContentRun); - if (contentElement != null) - foreach (var contentElementChildElement in contentElement.ChildElements.ToList()) - { - contentElementChildElement.Remove(); - sdtElement.InsertBeforeSelf(contentElementChildElement); - } + var contentControls = WordprocessingDocument.MainDocumentPart.Document.Descendants().ToList(); - sdtElement.Remove(); + // Process each content control from innermost to outermost + foreach (var sdt in contentControls.OrderByDescending(s => s.Ancestors().Count())) + { + if (sdt is SdtBlock sdtBlock) + { + HandleSdtContent(sdtBlock, sdtBlock.SdtContentBlock); + } + else if (sdt is SdtRun sdtRun) + { + HandleSdtContent(sdtRun, sdtRun.SdtContentRun); + } + else if (sdt is SdtRow sdtRow) + { + HandleSdtRow(sdtRow); + } } allContentControls.Clear(); innerContentControls.Clear(); firstOrderContentControls.Clear(); } + + private static void HandleSdtContent(SdtElement sdt, OpenXmlElement content) + { + if (content != null) + { + var parent = sdt.Parent; + var elementsToMove = content.ChildElements.ToArray(); // Make a copy to avoid modifying the collection during iteration + foreach (var elem in elementsToMove) + { + parent.InsertBefore(elem.CloneNode(true), sdt); + } + // Remove the content control itself + sdt.Remove(); + } + } + private static void HandleSdtRow(SdtRow sdtRow) + { + if (sdtRow.SdtContentRow != null) + { + var tableRow = sdtRow.SdtContentRow.GetFirstChild(); + if (tableRow != null) + { + var parent = sdtRow.Parent; // This should be the Table + parent.InsertBefore(tableRow.CloneNode(true), sdtRow); + sdtRow.Remove(); + } + } + } + } } \ No newline at end of file diff --git a/OpenXMLTemplatesTest/ControlRemovalTest/Doc.docx b/OpenXMLTemplatesTest/ControlRemovalTest/Doc.docx new file mode 100644 index 0000000..f4af6ff Binary files /dev/null and b/OpenXMLTemplatesTest/ControlRemovalTest/Doc.docx differ diff --git a/OpenXMLTemplatesTest/ControlRemovalTest/Tests.cs b/OpenXMLTemplatesTest/ControlRemovalTest/Tests.cs new file mode 100644 index 0000000..2ae53d9 --- /dev/null +++ b/OpenXMLTemplatesTest/ControlRemovalTest/Tests.cs @@ -0,0 +1,57 @@ +using System.IO; +using System.Linq; +using NUnit.Framework; +using OpenXMLTemplates; +using OpenXMLTemplates.ControlReplacers; +using OpenXMLTemplates.Documents; +using OpenXMLTemplates.Engine; +using OpenXMLTemplates.Variables; +namespace OpenXMLTempaltesTest.ControlRemovalTest +{ + public class Tests + { + private TemplateDocument GetDoc => new TemplateDocument(this.CurrentFolder() + "Doc.docx"); + private string GetData => File.ReadAllText(this.CurrentFolder() + "data.json"); + + [Test] + public void TestControlRemoval() + { + using var doc = GetDoc; + var data = GetData; + + var src = new VariableSource(); + src.LoadDataFromJson(data); + + var engine = new DefaultOpenXmlTemplateEngine + { + KeepContentControlAfterReplacement = false + }; + engine.ReplaceAll(doc, src); + + doc.SaveAs(this.CurrentFolder() + "result.docx"); + + // confirm new content has been included in document + string? docText = null; + + using (StreamReader sr = new StreamReader(doc.WordprocessingDocument.MainDocumentPart.GetStream())) + { + docText = sr.ReadToEnd(); + } + + Assert.IsTrue(docText != null); + var replacedText = src.GetVariable("nested.[1].nestedList.[1]"); + Assert.IsTrue(docText.Contains(replacedText)); + + // confirm controls have been removed + Assert.AreEqual(0, + doc.WordprocessingDocument.ContentControls().Count(cc => + cc.GetContentControlTag() != null && cc.GetContentControlTag().StartsWith("repeatingitem"))); + + Assert.AreEqual(0, + doc.WordprocessingDocument.ContentControls().Count(cc => + cc.GetContentControlTag() != null && cc.GetContentControlTag() == "repeating_nestedList")); + + doc.WordprocessingDocument.AssertValid(); + } + } +} diff --git a/OpenXMLTemplatesTest/ControlRemovalTest/data.json b/OpenXMLTemplatesTest/ControlRemovalTest/data.json new file mode 100644 index 0000000..c7ade24 --- /dev/null +++ b/OpenXMLTemplatesTest/ControlRemovalTest/data.json @@ -0,0 +1,35 @@ +{ + "items": [ + "Item 1", + "Item 2", + "Item 3", + "Item 4" + ], + "complexItems": [ + { + "name": "First Name", + "address": "First Address" + }, + { + "name": "Second name", + "address": "Second address" + } + ], + "nested": [ + { + "name": "List 1", + "nestedList": [ + "item1-1", + "item1-2", + "item1-3" + ] + }, + { + "name": "List 2", + "nestedList": [ + "item2-1", + "item2-2" + ] + } + ] +} \ No newline at end of file diff --git a/OpenXMLTemplatesTest/ControlRemovalTest/~$Doc.docx b/OpenXMLTemplatesTest/ControlRemovalTest/~$Doc.docx new file mode 100644 index 0000000..37c76e1 Binary files /dev/null and b/OpenXMLTemplatesTest/ControlRemovalTest/~$Doc.docx differ diff --git a/OpenXMLTemplatesTest/OpenXMLTemplatesTest.csproj b/OpenXMLTemplatesTest/OpenXMLTemplatesTest.csproj index f0358e7..2a2c441 100644 --- a/OpenXMLTemplatesTest/OpenXMLTemplatesTest.csproj +++ b/OpenXMLTemplatesTest/OpenXMLTemplatesTest.csproj @@ -18,6 +18,12 @@ + + PreserveNewest + + + Always + PreserveNewest