Converting an InfoPath 2007 Form into a Word 2007 Document

A while back I wrote about generating a Word 2003 document from an InfoPath 2003 form. That post included code for using an existing XSLT stylesheet to transform InfoPath XML into WordML and then opening the results in Word 2003.

With the introduction of the Office Open XML Formats in the 2007 release, the process for programmatically using XSLT to generate Word 2007 documents has changed somewhat. S.Y.M. Wong-A-Ton illustrates this process superbly in the article titled Convert an InfoPath 2007 form into a Word 2007 document using XSLT and C#. The article describes how to build the XSLT stylesheet, use it to transform the InfoPath XML, and then replace the document.xml part of a Word 2007 document with the output of the transform.

Despite the effectiveness of S.Y.M.’s article, there are still a lot of people who are wary of using XSLT in this manner, mainly because the markup in the document.xml part (the basis for the XSLT stylesheet) is difficult to read. I have received a few requests in this space for an alternative to the XSLT approach.

Well, there is the option of using a custom XML part in your Word 2007 document template. From the InfoPath form, you can use the System.IO.Packaging namespace to programmatically manipulate the XML part, which would be easier to read than document.xml. You can then bind nodes in the part to content controls and thus surface the XML data in the presentation layer. Michael O’Donovan has a nice post showing how to add a custom XML part and content controls to a Word 2007 document template.

Once you have the Word 2007 document template in place, you can add code to the InfoPath form template that creates a new Word 2007 document instance and updates the custom XML part accordingly. In the example for this post, I will provide some C# functions that will help generate a new Word 2007 document. In order to use these functions, you will need references to the System.IO.Packaging namespace and to System.IO. If you recall from a previous post, you will need to reference WindowsBase.dll to get to the System.IO.Packaging namespace. Then, you can add the using statements to the top of your form code file.

using System.IO;
using System.IO.Packaging;

For the functions I am using, you will also need global variables for the Word 2007 package, the existing custom XML part, and the replacement XML for that part. These variables are added to the top of the class in the form code file.

private Package package;
private XmlDocument partDoc;
private PackagePart replacementPart;

The first function, GetDocumentFromPartForSchema, returns an XmlDocument object representing the data in the XML part. If the part does not conform to the schema that is passed, then nothing is returned.

private XmlDocument GetDocumentFromPartForSchema(PackagePart testPart, string schema)
{
    // Assume this is the one
    XmlDocument returnDoc = new XmlDocument();
    string itemNamespace;
    string propertyNamespace;

    // Retrieve the part information
    returnDoc.Load(testPart.GetStream());

    // Retrieve the item and property name spaces from the part
    itemNamespace = returnDoc.NameTable.Get(schema);
    propertyNamespace = returnDoc.NameTable.Get("{72B9FB7E-5162-45A9-B8D8-659A036A0BD8}");

    // If either namespace was not found, this isn't the part
    if (itemNamespace != null && itemNamespace.Length > 0)
    {
        // Set up search to get the root node to ensure this is the right one
        NamespaceManager.AddNamespace("ns0", schema);
        XmlNodeList propertiesList = returnDoc.SelectNodes("/ns0:UserInfo", NamespaceManager);

        // If properties is empty (or null) this isn't the part we're looking for
        if (propertiesList == null || propertiesList.Count == 0)
        {
            returnDoc = null;
        }
    }
    else
    {
        returnDoc = null;
    }

    return returnDoc;
}

The next function, DocumentCreate, makes a call to the GetDocumentFromPartForSchema function. It creates a copy of the Word 2007 document template and finds the custom XML part in the package.

public void DocumentCreate(string templateFileName, string newFileName, string schema)
{
    // Copy the template to the new file name
    File.Copy(templateFileName, newFileName, true);

    // Open the template
    package = Package.Open(newFileName);

    // Find the part we need
    PackagePartCollection packageParts = package.GetParts();

    if (packageParts != null)
    {
        // Uri to the current part
        string partUri;

        // Walk all of the parts until the target
        // part has been found
        foreach (PackagePart testPart in packageParts)
        {
            // Get the URI for the part
            partUri = testPart.Uri.ToString().ToLower();

            // See if the current part is one we want to look at
            // based on the xml having properties and conforming
            // to the schema passed in.
            partDoc = GetDocumentFromPartForSchema(testPart, schema);

            // If something was returned, we've got our part
            if (partDoc != null)
            {
                replacementPart = testPart;
                break;
            }
        }
    }
}

To retrieve values from the InfoPath data source and then set the values of nodes in the Word 2007 document’s custom XML part, I use the GetMainDataSourceValue and DocumentSetValue functions, respectively.

private string GetMainDataSourceValue(string xpath)
{
    string retValue = string.Empty;
    XPathNavigator retNode = MainDataSource.CreateNavigator().SelectSingleNode(xpath, NamespaceManager);
    if (retNode != null)
    {
        retValue = retNode.InnerXml;
    }
    return retValue;
}

public void DocumentSetValue(string nodeName, string nodeValue)
{
    // Make sure we have a document to search
    if (partDoc != null)
    {
        // Get the value from the part
        XmlNodeList nodeList = partDoc.SelectNodes("/ns0:UserInfo//ns0:" + nodeName, NamespaceManager);

        // Make sure we have a node to set
        if (nodeList != null && nodeList.Count > 0)
        {
            XmlNode node = nodeList[0];
            node.InnerText = nodeValue;
        }
    }
    else
    {
        throw new Exception("Part has not been set");
    }
}

The DocumentSave function replaces the stream of the replacement part with the contents of the partDoc variable.

public void DocumentSave()
{
    if (package != null)
    {
        partDoc.Save(replacementPart.GetStream());
        package.Flush();
        package.Close();
    }
    else
    {
        throw new Exception("Package has not been loaded");
    }
}

Finally, within an event handler in your form code (perhaps, a button control event handler), you can add code that sets nodes in your custom XML part accordingly and then saves the new Word 2007 document. For this example, I have a simple XML part identifying user information.

public void BtnGenerateDocument_Clicked(object sender, ClickedEventArgs e)
{
    try
        {
        // First, create a copy of the template
        string lastName = GetMainDataSourceValue("//my:LastName");
        string documentTemplate = @"c:DemosUserInfoTemplate.docx";
        string newDocument = @"c:Demos" + lastName + ".docx";
        DocumentCreate(documentTemplate, newDocument, "http://www.litwareinc.com/2008/schemas/UserInfo");

        // Then, set values for the custom XML part in the new file
        DocumentSetValue("FirstName", GetMainDataSourceValue("//my:FirstName"));
        DocumentSetValue("LastName", lastName);
        DocumentSetValue("Address", GetMainDataSourceValue("//my:Address"));
        DocumentSetValue("City", GetMainDataSourceValue("//my:City"));
        DocumentSetValue("State", GetMainDataSourceValue("//my:State"));
        DocumentSetValue("Zip", GetMainDataSourceValue("//my:Zip"));
        DocumentSetValue("Phone", GetMainDataSourceValue("//my:Phone"));
        DocumentSetValue("Email", GetMainDataSourceValue("//my:Email"));
        DocumentSave();
        MessageBox.Show("A document has been generated and saved to " + newDocument + ".", "Document Generated");
    }
    catch (Exception ex)
    {
        MessageBox.Show("There was a failure in generating the document:nn" + ex.Message);
    }
}

14 thoughts on “Converting an InfoPath 2007 Form into a Word 2007 Document

  1. Interesting approach hereCould include the source xml data as well as the UserInfoTemplate.docx file?All the source, in a zip? 🙂

  2. What is NamespaceManager and MainDataSource???Compilation error…The name ‘MainDataSource’ does not exist in the current contextThe name ‘NamespaceManager’ does not exist in the current context

  3. NamespaceManager and MainDataSource (both in the Microsoft.Office.InfoPath namespace) are defined automatically for managed-code InfoPath projects.Regards,David

  4. Hi, Xrome.The example shown in this post does not address repeating structures in the form’s data source. However, you could add a “for” or “foreach” construct to your code to iterate through rows of a table.I hope this helps…Regards,David

  5. Can someone help me understand why it’s not easier just to use the InfoPath form to update a database with the appropriate information and simply use MS Reporting Services to produce the actual “form” as a Word doc?

  6. Hi, Randy.Your approach might work fine if a database is a part of your InfoPath solution. However, not all form solutions use a database.Also, the approaches described in this post and in S.Y.M. Wong-A-Ton’s post use a specific Word template, where the formatting for the document is pre-determined.Regards,David

  7. Hi,I need to export InfoPath form into Excel 2007 document. I have excel template. I tried to use above mentioned example for Excel 2007 but did not get succeed. Did Anyone try to convert InfoPath form into Excel 2007 Document ? ThanksChuck.

  8. Hi, Chuck.The method described in this post requires the use of content controls, which is not supported in Excel 2007. Have you looked into the “Export to Microsoft Office Excel” feature of InfoPath?Regards,David

  9. David,Thanks for your reply. Yes, I looked into the “Export to Microsoft Office Excel” feature of InfoPath. I need output excel file in specific format (eg: company logo on top left corner, text, and column data). Export to excel is not possible for my requirement. I am planning to import “Microsoft.Office.Interop.Excel” DLL and create excel file on fly using C# code. Do you think any other option?ThanksChuck.

  10. David,I have a repeating table in my infopath form which is populated with data from an SQL database/web service dynamically. I needed to have button, which on click could transfer the data from the repeating table to an excel sheet. Is this possible? Please help..

  11. Hi, Victoria.Content controls are not present in Excel as they are in Word, so the method described in this post is not applicable. However, you can still programmatically manipulate worksheet parts in Excel from an InfoPath event handler using the System.IO.Packaging namespace. The worksheet parts in an Excel (2007) document can be found in the .xlworksheets directory of the archive.Regards,David

Comments are closed.