How to insert text into a content control with the Open XML SDK
Asked Answered
M

1

10

I'm trying to develop a solution which takes the input from a ASP.Net Web Page and Embed the input values into Corresponding Content Controls within a MS Word Document. The MS Word Document has also got Static Data with some Dynamic data to be Embed into the Header and Footer fields.

The Idea here is that the solution should be Web based. Can I use OpenXML for this purpose or any other approach that you can suggest.

Thank you very much in advance for all your valuable inputs. I really appreciate them.

Mathamathe answered 13/4, 2016 at 13:24 Comment(8)
Using the open xml sdk is your best option when you are working server side, since it doesn't require Word to be run in the background (or even be installed).Levant
Hi Alex, the reason for building it Server side is to enable the flexibility of accessing it as a website. That makes the solution platform independent and we don't need any Macro or any other logic running in Users machine. Once the users fills the data and generates the Word doc, they will be able to store the end document with embedded values into their local machineMathamathe
I know, that's why I said the Open XML SDK is the best option if you are going to run it on a server.Levant
Do you know if I can directly take the control values (for example, Text Box and Drop down lists) from the ASP.Net Web page and directly assign it to Content Controls inside the Word document?Mathamathe
Of course, create methods which process the input you get from the web page to insert it into the content controls. It can be hard to get started with though, I hope you don't have to do too much styling and formatting yourself.Levant
Well the document templates contain a header and footer where some of the Company information like Address need to be embed. Otherwise the Word doc looks simple. Do you have any example that I can follow to get it done? any link contain code approach would be a great help.Mathamathe
Included some code from my project in an answer. This should get you started with inserting simple text in a content control and removing the control afterwards (without removing the inserted text).Levant
Thanks a ton Alex. I'll get started with this and get in touch if I stumble on the way. I greatly appreciate your help and guidance on this :)Mathamathe
L
25

I have a little code sample from my project, to insert a few words in a content control you've created in a Word document:

public static WordprocessingDocument InsertText(this WordprocessingDocument doc, string contentControlTag, string text)
{
    SdtElement element = doc.MainDocumentPart.Document.Body.Descendants<SdtElement>()
      .FirstOrDefault(sdt => sdt.SdtProperties.GetFirstChild<Tag>()?.Val == contentControlTag);

    if (element == null)
      throw new ArgumentException($"ContentControlTag \"{contentControlTag}\" doesn't exist.");

    element.Descendants<Text>().First().Text = text;
    element.Descendants<Text>().Skip(1).ToList().ForEach(t => t.Remove());

    return doc;
}

It simply looks for the first contentcontrol in the document with a specific Tag (you can set that by enabling designer mode in word and right-clicking on the content control), and replaces the current text with the text passed into the method. After this the document will still contain the content controls of course which may not be desired. So when I'm done editing the document I run the following method to get rid of the content controls:

internal static WordprocessingDocument RemoveSdtBlocks(this WordprocessingDocument doc, IEnumerable<string> contentBlocks)
{
    List<SdtElement> SdtBlocks = doc.MainDocumentPart.Document.Descendants<SdtElement>().ToList();

    if (contentBlocks == null)
        return doc;

    foreach(var s in contentBlocks)
    {
        SdtElement currentElement = SdtBlocks.FirstOrDefault(sdt => sdt.SdtProperties.GetFirstChild<Tag>()?.Val == s);
        if (currentElement == null)
            continue;
        IEnumerable<OpenXmlElement> elements = null;

        if (currentElement is SdtBlock)
            elements = (currentElement as SdtBlock).SdtContentBlock.Elements();
        else if (currentElement is SdtCell)
            elements = (currentElement as SdtCell).SdtContentCell.Elements();
        else if (currentElement is SdtRun)
            elements = (currentElement as SdtRun).SdtContentRun.Elements();

        foreach (var el in elements)
            currentElement.InsertBeforeSelf(el.CloneNode(true));
        currentElement.Remove();
    }
    return doc;
}

To open the WordProcessingDocument from a template and edit it, there is plenty of information available online.

Edit:

Little sample code to open/save documents while working with them in a memorystream, of course you should take care of this with an extra repository class that takes care of managing the document in the real code:

byte[] byteArray = File.ReadAllBytes(@"C:\...\Template.dotx");

using (var stream = new MemoryStream())
{
    stream.Write(byteArray, 0, byteArray.Length);

    using (WordprocessingDocument doc = WordprocessingDocument.Open(stream, true))
    {
       //Needed because I'm working with template dotx file, 
       //remove this if the template is a normal docx. 
        doc.ChangeDocumentType(DocumentFormat.OpenXml.WordprocessingDocumentType.Document);
        doc.InsertText("contentControlName","testtesttesttest");
    }
    using (FileStream fs = new FileStream(@"C:\...\newFile.docx", FileMode.Create))
    {
       stream.WriteTo(fs);
    }
}
Levant answered 14/4, 2016 at 12:32 Comment(16)
Hi Alex, I have implemented the solution as you suggested above. I am able to change the text by picking up content controls in the code. But when I save the document after embedding the new values to the content controls, i don't see them being embed in the final word document that I saved. I'm wondering what is missing in my code. Do you have any idea what could that be?Mathamathe
@BryanJacob So you can update the text ("I am able to change the text"), but what do you mean by 'embedding' them into the final document? You don't want the content controls gone?Levant
var tagInfo = document.MainDocumentPart.Document.Body.Descendants<SdtElement>() .FirstOrDefault(sdt => sdt.SdtProperties.GetFirstChild<Tag>()?.Val == property.Name); if (tagInfo != null) tagInfo.Descendants<Text>().First().Text = "My Target Text"; Then i run the RemoveSdtBlock method you suggested. When i finally save this document as Word Document, i don't see the replaced values inside the controls. Do you see what I mean?Mathamathe
@BryanJacob Yes, but what is going wrong? You get an error? You don't see your text "My target text" inside the document?Levant
There is nothing wrong that I actually see. But at the same time, I don't see the values in the Document. I'm wondering what's going wrong.Mathamathe
@BryanJacob Just tested it again with a simple document with just a contentcontrol in it and it works for me. Probably there is something wrong with the way you save the document or something. I always write my memorystream to a filestream so I can create a new document (this approach is made to be used by templates, not change the template itself, but copy the template to a memorystream, change the memorystream with above method for example, and save it to a new file)Levant
Thanks Again alex. I have made a file copy and then tried to manipulate the document as you said above. But I haven't saved it as MemoryStream and then into a FileStream. I'll try to do it and will let you know how it goes. Thanks for all your help :)Mathamathe
Hi Alex, finally i could get what I really wanted. Now it is replacing the Content Control values with the user input values. Currently the final document is saved to the local disk on the server. Is it possible for me make the Document "Save As" so that the user can choose where they want to store the document? Thanks for all your inputs until now.Mathamathe
Let us continue this discussion in chat.Mathamathe
Hi Alex, one final thing to ask you. How can I replace content control values in the Header and Footer section in a Word Document?Mathamathe
@BryanJacob Those are located in the Header- and FooterPart of your document (so far all changes done in the code above are in the DocumentPart), so you'll have to use documentformat.openxml.packaging to open those parts and replace them there.Levant
Thanks a million for RemoveSdtBlocks, works awesome with slight modification to allow for SdtRow due to repeating sections.Averett
@AlexanderDerck really helpful post! But what if you have a (multiline) plain text content control, and you want to type multiple lines? I tried "\r\n" and environment.newline in element.Descendants<Text>().First().Text = text but with no luck, and even tried replacing those characters with new Break(), but still nothing!Brister
@Brister did you find a workaround for multiline content for rich text CC?Yentai
@KhawajaAsim did YOU find a workaround for inserting rich text?Birgit
@AlexanderDerck Hi. Im using your InsertText() Method. My Problem is that I have multiple Content-Control-Tags with the same name. Your method only finds the first Content-Control-Tag and sets it's text. Is it possible to set the same text for each Content-Control-Tag?Nicole

© 2022 - 2024 — McMap. All rights reserved.