Search code examples
ms-wordopenxmlopenxml-sdk

Programmatically add repeating section item to a word document using OpenXml.WordProcessing .NET


I have a document template which I want to dynamically populate using C#. The template contains a repeating section that has a few text boxes and some static text. I want to be able to populate the text boxes and to add new section items when needed.

The code that almost works is as follows:

WordprocessingDocument doc = WordprocessingDocument.Open(@"C:\in\test.docx", true);
var mainDoc = doc.MainDocumentPart.Document.Body
    .GetFirstChild<DocumentFormat.OpenXml.Wordprocessing.SdtBlock>()                    
    .GetFirstChild<DocumentFormat.OpenXml.Wordprocessing.SdtContentBlock>();

var person = mainDoc.ChildElements[mainDoc.ChildElements.Count-1];
person.InsertAfterSelf<DocumentFormat.OpenXml.Wordprocessing.SdtBlock>(
    (DocumentFormat.OpenXml.Wordprocessing.SdtBlock) person.Clone());

This however, produces a corrupted file because the unique IDs are also duplicated by the Clone method.

Any idea on how to achieve my goal?


Solution

  • Here is some code that shows how you could do this. Note that this removes the existing unique Id (the w:id element) to ensure this is not repeated.

    using WordprocessingDocument doc = WordprocessingDocument.Open(@"C:\in\test.docx", true);
    
    // Get the w:sdtContent element of the first block-level w:sdt element,
    // noting that "sdtContent" is called "mainDoc" in the question.
    SdtContentBlock sdtContent = doc.MainDocumentPart.Document.Body
        .Elements<SdtBlock>()
        .Select(sdt => sdt.SdtContentBlock)
        .First();
    
    // Get last element within SdtContentBlock. This seems to represent a "person".
    SdtBlock person = sdtContent.Elements<SdtBlock>().Last();
    
    // Create a clone and remove an existing w:id element from the clone's w:sdtPr
    // element, to ensure we don't repeat it. Note that the w:id element is optional
    // and Word will add one when it saves the document.
    var clone = (SdtBlock) person.CloneNode(true);
    SdtId id = clone.SdtProperties?.Elements<SdtId>().FirstOrDefault();
    id?.Remove();
    
    // Add the clone as the new last element.
    person.InsertAfterSelf(clone);