1
votes

I have been trying to get this to work for days now and am posting here as a last resort.

Background - I am creating a web application that allows a user to enter data into a web page and this data gets translated into a Microsoft Word document using placeholders. This Word document is a template that gets populated with data and saved to the users computer.

Features Solved - I have managed to populate all of the required text fields with user data. This is no issue at all. I have created a search and replace method that does all of this for me so I am not asking for assistance in relation to this.

Issues - Included in the data that the user enters in on the web page are several boolean values. These boolean values will be used to either check or un-check certain Checkboxes in the Word document. Each Checkbox in the Word document has a Bookmark that needs to be used to locate it. I have tried several ways to search through the entire xml of the Word document and alter the value assigned to that specific Checkbox element.

Sample xml extracted from Word template

<w:document>
<w:body>        
    <w:tbl>         
        <w:tr>          
            <w:tc>          
                <w:p w:rsidR="007D2E4C" w:rsidRPr="00233E81" w:rsidRDefault="007D2E4C" w:rsidP="007D2E4C">
                    <w:r>   
                        <w:fldChar w:fldCharType="begin">
                            <w:ffData>
                                <w:name w:val="cbxmeal0"/>
                                <w:enabled/>
                                <w:calcOnExit w:val="0"/>
                                <w:checkBox>
                                    <w:sizeAuto/>
                                    <w:default w:val="0"/>
                                </w:checkBox>
                            </w:ffData>
                        </w:fldChar>
                    </w:r>
                    <w:bookmarkStart w:id="10" w:name="cbxmeal0"/>
                    <w:r>
                        <w:rPr>
                            <w:sz w:val="20"/>
                        </w:rPr>
                        <w:instrText xml:space="preserve"> FORMCHECKBOX </w:instrText>
                    </w:r>
                    <w:r w:rsidR="00B128CE">
                        <w:rPr>
                            <w:sz w:val="20"/>
                        </w:rPr>
                    </w:r>
                    <w:r w:rsidR="00B128CE">
                        <w:rPr>
                            <w:sz w:val="20"/>
                        </w:rPr>
                        <w:fldChar w:fldCharType="separate"/>
                    </w:r>
                    <w:r>
                        <w:rPr>
                            <w:sz w:val="20"/>
                        </w:rPr>
                        <w:fldChar w:fldCharType="end"/>
                    </w:r>
                    <w:bookmarkEnd w:id="10"/>
                </w:p>
            </w:tc>
        </w:tr>
    </w:tbl>
</w:body>

The above xml text is a super simplified version but the structure is exactly the same. A simpler version of the structure is here Document > Body > Table > Table_Row > Table_Cell > Paragraph > Run > Field_Char > Form_Field_Data > Checkbox

Below is my extremely ugly way of iterating through the xml text in an attempt to locate the bookmark and checkbox. Note: I was only planning on getting it working and then I will optimise later.

            foreach (var table in document.MainDocumentPart.Document.Body.ChildElements.Where(t => t is DocumentFormat.OpenXml.Wordprocessing.Table))
        {
            foreach (var row in table.ChildElements.Where(r => r is DocumentFormat.OpenXml.Wordprocessing.TableRow))
            {
                foreach (var cell in row.ChildElements.Where(c => c is DocumentFormat.OpenXml.Wordprocessing.TableCell))
                {
                    foreach (var paragraph in cell.ChildElements.Where(p => p is DocumentFormat.OpenXml.Wordprocessing.Paragraph))
                    {
                        foreach (var run in paragraph.ChildElements.Where(r => r is DocumentFormat.OpenXml.Wordprocessing.Run))
                        {
                            foreach (var fieldChar in run.ChildElements.Where(fc => fc is DocumentFormat.OpenXml.Wordprocessing.FieldChar))
                            {
                                foreach (var formFieldData in fieldChar.ChildElements)
                                {
                                    foreach (var child in formFieldData.ChildElements)
                                    {
                                        //Check bookmark name
                                        //If bookmark name matches, 
                                        //Set checkbox value to true
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

If anyone can provide any assistance, whether it's a website address or actual code, that would be extremely helpful as I am losing faith in this quest! Also, forgive my LINQ usage as I have very little experience using it!

Software - I am using Visual Studio 2015, Microsoft Office 2016 and OpenXML 2.6 (I think).

1

1 Answers

2
votes

This should help you.

using (var wordDoc = WordprocessingDocument.Open(@"C:\test\cb\checkbox.docx", true))
{
    MainDocumentPart mainPart = wordDoc.MainDocumentPart;
    var document = mainPart.Document;
    var bookmarks = document.Body.Descendants<BookmarkStart>();

    var myBookmark = bookmarks.First(bms => bms.Name == "cbxmeal0");
    Paragraph paragraph = (Paragraph)myBookmark.Parent;

    Run myCheckbox = (Run)paragraph.Descendants<FieldCode>().First().Parent;
    Run newRun = new Run(@"
<w:r xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
    <w:fldChar w:fldCharType=""begin"">
        <w:ffData>
            <w:name w:val=""cbxmeal0"" />
            <w:enabled />
            <w:calcOnExit w:val=""0"" />
            <w:checkBox>
                <w:sizeAuto />
                <w:default w:val=""1"" />
            </w:checkBox>
        </w:ffData>
    </w:fldChar>
</w:r>
");
    paragraph.InsertBefore(newRun, myCheckbox);
}

I've created a Word document based on the XML you provided. The first few lines is just me zooming in on the Run which contains your checkbox.

The interesting part, is the "newRun" I create. You can create it in code, of from XML which is what I've done, but simply put, it adds a "FieldChar" to your Checkbox, setting it as checked. I'm not too sure whether it's important it is inserted before the Run containing the checkbox, but in my tests, it's what Word does on its own, and that's why I've done it the same way.

I hope it helps!