1

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?

Cœur
  • 37,241
  • 25
  • 195
  • 267
  • You need to provide a [mcve], including what is meant by "text boxes" (Word has at least five things that can be called that). A screen shot or two showing that part of the document before and after the code (desired result). And the underlying Word Open XML of what you're trying to clone would help, as well. You might also try creating one or two new repeating sections as a user. Save the result to a new document. Use the Open XML SDK Productivity Tool to open the original document, then use the Compare feature on the new one. That generates the code to create the new from the original. – Cindy Meister Feb 13 '20 at 18:06
  • Thanks, got it! However, it doesn't matter what my repeating section actually contains. Let's say it only contains the "hello world" text. What I want to do is programatically clone that section. How can this be achieved using open xml? – Adrian Ganea Feb 13 '20 at 19:27
  • The second half of my comment... – Cindy Meister Feb 13 '20 at 19:32
  • Thanks, I'll try this and get back! – Adrian Ganea Feb 13 '20 at 19:44
  • I tried the tool and didn't help. As you suggested, I first created a file(original) that only contained a repeating section with some text in it. Then I added a new section item and saved the file with a different name(modified). When I try to compare the files setting original as the source and modified as the destination, I get "Fail to generate codes" If I try the other way around it's not helping me(it's simple to remove the sectionitem). Anyways, my requirement is to duplicate a section item regardless of its content, and I see that the generated code is based on the document structure. – Adrian Ganea Feb 14 '20 at 07:23
  • I'd try again, with two new (very simple) files and see if the Compare feature works then... – Cindy Meister Feb 15 '20 at 18:09
  • Thanks, but what could be more simple than a repeating section with some text in it... I think I would go with Interop, OpenXml doesn't seem to help in my scenario. – Adrian Ganea Feb 15 '20 at 21:12
  • We don't know enough about your scenario to be able to tell whether Open XML or Interop would be better. For example, it would be important to understand a little more about your template. While you are talking about "sections", you are cloning `SdtBlock` instances, which are not sections. So, when you say "unique ID", are you talking about the `w:id` child element of the `w:sdtPr` element or something else? Anyhow, nothing prevents you from cloning an element and then changing a unique Id to make sure it is indeed unique. – Thomas Barnekow Feb 16 '20 at 14:17

1 Answers1

4

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);
Thomas Barnekow
  • 2,059
  • 1
  • 12
  • 21