2
votes

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:".

1
In order to better help you, we will need to see secondDoc. Once we can view the structure, we can help troubleshoot your code.Taterhead
I have updated the initial post. I hope it's clear. Thank you!Cosmos24Magic
It is clear, but not clear enough. We need the actual document to inspect the exact xml structure. This will help determine why your method is not returning the correct values. If you don't want to or can't share the file, I understand. What you need to do is download the OpenXML Productivity Tool (microsoft.com/en-us/download/details.aspx?id=30425) and use it to open the document to inspect the xml structure. Then rewrite your GetBookmarkData method according to the structure. If you post the doc, we will do it for you ;)Taterhead
The document doesn't have anything else. It's like I posted in the description. I can't upload the file because it contains company data. I have OpenXML Productivity Tool but I don't really understand it. I'm looking in the /word/document.xml -> w:document -> w:body....there are a lot of paragraphs..so I click on one that contains BookmarkStart. In the run I can see the first line. Following another param is another run with the next line and so on until BookmarkEnd. Where are the bullets ? In /word/numbering.xml ? How do they relate ?Cosmos24Magic
Here is an exact example of what I have: drive.google.com/open?id=0BwlWg0JEosuNUTNpdGI2QkwxNms So I need to get the bulleted list from SecondTemplate.docx, into the merge field from MainTemplate.docx. I've bookmarked the list as Bookmark1. Maybe there is a much better way to do this instead of using bookmarks ?Cosmos24Magic

1 Answers

1
votes

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.