Cosmos24Magic
Cosmos24Magic

Reputation: 229

Generate word doc through OpenXML

I have an ASP.NET 4.5 web forms application, running on an IIS 7.5.

I'm trying to generate a word document from one of it's pages where I have a custom form.

I uploaded a word document template which contains merge fields. In the code behind, I want to populate the merge fields based on sql database queries.

For some merge fields, I need to insert multiple lines of text. Some of them even have bullet lists. These text fragments I can't store in sql so I have added them in a separate word document with bookmarks.

So, just to recap:

Template.dotx -> contains the merge fields

Data.docx -> contains the text fragments that have been marked with bookmarks.

I have managed to replace the merge fields from Template.dotx with the use of OpenXML, but I can't find a way to get the data from the bookmarks into the merge fields.

This works great with Interop, but I had problems when I uploaded it on the server so I switched to OpenXML.

This is what I have tried so far:

private string GetBookmarkData(WordprocessingDocument secondWordDoc, string bookmarkKey)
    {
            string returnVal = "";
            foreach (BookmarkStart bookmarkStart in secondWordDoc.MainDocumentPart.RootElement.Descendants<BookmarkStart>())
            {
                if(bookmarkStart.Name == bookmarkKey)
                {
                    foreach(Run run in bookmarkStart.Parent.Descendants<Run>())
                    {
                        returnVal += run.Descendants<Text>().FirstOrDefault().Text + "<br/>";
                    }
                }
            }
            return returnVal;
        }
    
    
    protected void PrintBtn_Click(object sender, EventArgs e)
    {
                string mainTemplate = Server.MapPath("~/MyFolder/Template.dotx");
                string savePath = Server.MapPath("~/SaveFolder/Final.docx");
    
                File.Copy(mainTemplate, savePath);
                using(WordprocessingDocument firstDoc = WordprocessingDocument.Open(savePath, true))
                {
                    using (WordprocessingDocument secondDoc = WordprocessingDocument.Open(Server.MapPath("~/MyFolder/Data.docx"), true))
                    {
                        foreach (FieldCode field in firstDoc.MainDocumentPart.RootElement.Descendants<FieldCode>())
                        {
                            var fieldNameStart = field.Text.LastIndexOf(" MERGEFIELD", System.StringComparison.Ordinal);
                            String fieldText = field.InnerText;
                            if (fieldText.StartsWith(" MERGEFIELD"))
                            {
                                Int32 endMerge = fieldText.IndexOf("\\");
                                Int32 fieldNameLength = fieldText.Length - endMerge;
                                String fieldName = fieldText.Substring(11, endMerge - 11);
                                fieldName = fieldName.Trim();
                                string autoFill = "";
    
                                    switch (fieldName)
                                    {
                                        case "MergeField1":
                                            autoFill = mergefield_1;
                                            break;
                                        case "MergeField2":
                                            autoFill = mergefield_2;
                                            break;
                                        case "MergeField3":
                                            autoFill = GetBookmarkData(secondDoc, "Bookmark1");
                                            break;
                                        case "MergeField4":
                                            autoFill = GetBookmarkData(secondDoc, "Bookmark2");
                                            break;
                                        case "MergeField5":
                                            autoFill = GetBookmarkData(secondDoc, "Bookmark3");
                                            break;
                                  }
                            }
    
                            foreach (Run run in firstDoc.MainDocumentPart.Document.Descendants<Run>())
                            {
                               foreach (Text txtFromRun in run.Descendants<Text>().Where(a => a.Text == "«" + fieldName + "»"))
                               {
                                  txtFromRun.Text = autoFill;
                               }
                            }
                        }
                    } 
                }
                            
        firstDoc.ChangeDocumentType(WordprocessingDocumentType.Document);
        firstDoc.MainDocumentPart.Document.Save();
    }
}

So what does this do ?

When I click on a button, I call the method PrintBtn_Click. After doing some SQL magic (that I haven't included in this), I initialize some variables which will fill each merge field. This example is a short and edited version. The original is much bigger. Using this code I managed to populate the merge fields. It works great. However the method: `

string GetBookmarkData(WordprocessingDocument secondWordDoc, string bookmarkKey)`

Doesn't really do what it's supposed to. It should go into the Data.docx, retrieve all the text from the bookmark I specified. It only returns the rows that have no bullets or weird formatting.

I have used the same process using Interop and I had no problem. How can I do this with OpenXML ? Are the rows with bullets stored in a different xml ?

I tried to retrieve all the Runs between BookmarkStart and BookmarkEnd and grab the Text from it.

Update

The secondDoc is actually the Data.docx and looks something like this:

Bookmark1

•   Text-Information 1 (This is just an example)
•   Text-Information 2 (This is just an example)
•   Text-Information 3 (This is just an example)
•   Text-Information 4 (This is just an example)

Bookmark2

This is a list of multiple items:
Item 1                              x.000,00 
Item 2                              x.000,00 
Item 3                              x.000,00 
Item 4                              x.000,00 
Item 5                              000,00 
This is the conclusion for this list.

Following is a list of other multiple items:
Item 1                              x.000,00 
Item 2                              x.000,00 
Item 3                              x.000,00 
Item 4                              x.000,00 
Item 5                              000,00 
This is the conclusions for this list


Bookmark3

a)  Another example of text that needs to go in the mergefield:
•   Article 1 xxxx  Quantity/Producer etc
•   Article 2 xxxx  Quantity/Producer etc
Some details about this block of text that is not relevant but I need to insert it in the merge field as well

So the entire text after "Bookmark1"/"Bookmark2"/"Bookmark3", needs to go in their specific merge fields, if a certain radiobutton is pressed. I have bookmarked these blocks of text. As I told you above, it only inserts some rows which have no bullets. For instance, the merge field which corresponds to Bookmark2, receives only "This is a list of multiple items:".

Upvotes: 2

Views: 1782

Answers (1)

Taterhead
Taterhead

Reputation: 5951

Looking at your document and your code, I see two places that could be the source of your problem:

First: the xml layout for your SecondTemplate.docx containing Bookmark1 is like so:

<Paragraph>
    <Bookmarkstart name=bookmark1/>
    <Run>
        <Text "Item 1">
    </Run>
</Paragraph>
<Paragraph>
    <Run>
        <Text "Item 2">
    </Run>
</Paragraph>    
<Paragraph>
    <Run>
        <Text "Item 3">
    </Run>
</Paragraph>    
<Paragraph>
    <Run>
        <Text "Item 4">
    </Run>
    <Bookmarkend/>
</Paragraph>    

and your code here:

            if(bookmarkStart.Name == bookmarkKey)
            {
                foreach(Run run in bookmarkStart.Parent.Descendants<Run>())
                {
                    returnVal += run.Descendants<Text>().FirstOrDefault().Text + "<br/>";
                }
            }

when the bookmarkstart.Parent call runs, it matches on the Paragraph that is directly above the bookmark :

<Paragraph>
    <Bookmarkstart name=bookmark1/>
    <Run>
        <Text "Item 1">
    </Run>
</Paragraph>

so when the rest of the loop executes, you only get the "Item 1" pulled into your merge process. You need to re-work your logic to correctly match the Text in the Run for all four paragraphs between the BookmarkStart and BookmarkEnd.

Second: Another issue that often trips people up in OpenXml is when you are trying to match the Run in the Descendants call here:

 bookmarkStart.Parent.Descendants<Run>

If you are referring to the DocumentFormat.OpenXml.Drawing.Run , not the correct 'DocumentFormat.OpenXml.Wordprocessing.Run', this can prevent a match - so mouse over that Run in Visual Studio and ensure you are matching the correct Run. Adjust your using statements to get the correct one. A Using statement like

using Run = DocumentFormat.OpenXml.Wordprocessing.Run;

is often used depending on the rest of your code in that file. Hope these clues help you.

Upvotes: 1

Related Questions