Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / productivity / Office

Using .NET 3.0 to Automatically Generate Word 2007 Documents

4.18/5 (9 votes)
26 Aug 2007CPOL4 min read 1   1.9K  
This article describes how to automatically generate Word 2007 documents using .NET 3.0

Introduction

Suppose all information of my customers is stored on a database system or XML file and I am now requested to create hundreds of word letters, reports that will be sent to my customers. These letters have the same content but different customer name, customer address, etc.

There are some ways to make this task possible, but the new features of Office Word 2007 help me easily finish it. Creating a new document template with content controls is the new feature I'm interested in. NET Framework 3.0 also provides a new method to replicate, replacing the content of this template.

In this article, I am going to focus on how to create the document template and then automatically generate the others from this template.

Background

Open XML standardization becomes a new default format in Office 2007 based on ECMA standard. It definitely has some advantages because XML is the perfect format and it is already supported by many vendors.

You can learn more about OpenXML by clicking on the following links:

Using the Code

I use Visual Studio 2005, .NET Framework 3.0 to implement my application.

To help you get started with this article, the following steps are going to show you how to:

  1. Create the Word 2007 document template with Content Controls.
  2. Create custom XML file to map Content Controls to XML element in custom XML file.
  3. Create a relationship to link between the template document and Custom XML file.
  4. Define OrderInformation class.
  5. Define GenerateDocument class.
  6. Run application GenerateWord2007.exe.

Step 1: Create the Word 2007 Document Template with Content Controls

Imagine that here is the letter I have to design:

Screenshot - GenerateWord2007_1.jpg

In new document, I insert the appropriate plain text content controls by using the Developer tab. If the Developer tab doesn't appear on Microsoft Word 2007, "Show Developer Tab in the Ribbon" on Word Options should be checked. Name this document LabelForm.docm.

Step 2: Create Custom XML File to Map Content Controls of the Document Template to XML Element in Custom XML File

Based on the plain text content controls of LabelForm.docm, I create a new custom XML file item1.xml:

Screenshot - GenerateWord2007_2.jpg

The schema of this XML is entirely flexible to build, each element of Custom XML is set to each plain text content control of LabelForm.docm.

Step 3: Create a Relationship between the Template Document and Custom XML File

In order to make the content controls and custom XML file understand each other, I have to write some macros to link them. On Word 2007 screen, I create the new Macro CreateCustomXMLPart to attach item1.xml to LabelForm.docm.

VB.NET
///Initial CustomXMLPart
Sub CreateCustomXMLPart()
    ActiveDocument.CustomXMLParts.Add
    ActiveDocument.CustomXMLParts(12).Load ("\\customXml\item1.xml")
End Sub

And another macro LoadCustomXMLPart to map the content controls of LabelForm.docm:

VB.NET
 ///Load CustomXMLPart
Sub LoadCustomXMLPart() 
    Dim strXPath1 As String 
    strXPath1 = "/Customer/Address" 
    ActiveDocument.ContentControls(1).XMLMapping.SetMapping strXPath1 
    Dim strXPath2 As String 
    strXPath2 = "/Customer/City" 
    ActiveDocument.ContentControls(2).XMLMapping.SetMapping strXPath2 
    ........
End Sub    

I am ready to map one of its nodes to a content control. Run these macros, all plain text content controls of LabelForm.docm are automatically updated. Name it LetterFormTemplate.docm.

Screenshot - GenerateWord2007_3.jpg

Because my goal is to read data from SQL Server and each record will be generated to the new document, I write another macro to bind data to the appropriate content controls of LetterFormTemplate.docm when this template is open.

VB.NET
Private Sub Document_Open()
    Call BindData
End Sub

 ///Sub Bind each column of the record to the appropriate 
            content control
Sub Sub BindData() 
    Dim part As CustomXMLPart 
    Set part = ActiveDocument.CustomXMLParts.SelectByNamespace("")(1)
    Dim ctlAddress As ContentControl 
    Dim ctlCity As ContentControl
    ........
    Set ctlAddress = ActiveDocument.ContentControls(1)
    Set ctlCity = ActiveDocument.ContentControls(2) 
    ........
    ctlAddress.XMLMapping.SetMapping "/Customer/Address", "", part 
    ctlCity.XMLMapping.SetMapping "/Customer/City", "", part 
    ........
End Sub    

I completely finish the document template.

Step 4: Define OrderInformation Class

The purpose of OrderInformation class is merely to build the XML structure like item1.xml so that I can bind this data to LetterFormTemplate.docm.

C#
  public string Address
        {
            get { return _address; }
            set { _address = value; }
        }
  public string City
        {
            get { return _city; }
            set { _city = value; }
        }
        .....
  .........
// Build the XML structure
 public string ToXml()
        {
            XmlDocument xmlDoc = new XmlDocument();

            XmlElement root = xmlDoc.CreateElement("Customer");
            xmlDoc.AppendChild(root);
            AppendChild(xmlDoc, root, "Address", _address);
            AppendChild(xmlDoc, root, "City", _city);
            AppendChild(xmlDoc, root, "Region", _region);
            AppendChild(xmlDoc, root, "PostCode", _postcode);
            AppendChild(xmlDoc, root, "Country", _country);
            AppendChild(xmlDoc, root, "CustomerID", _customerID.ToString());
            AppendChild(xmlDoc, root, "OrderID", _orderID.ToString());
            AppendChild(xmlDoc, root, "ShippedDate", _shippedDate.ToString());
            AppendChild(xmlDoc, root, "HomePhone", _homePhone);
            AppendChild(xmlDoc, root, "LastName", _lastName);
            AppendChild(xmlDoc, root, "FirstName", _firstName);
            AppendChild(xmlDoc, root, "Title", _title);
                     
            return xmlDoc.InnerXml;
        }
private void AppendChild(XmlDocument xmlDoc, XmlElement root, 
			string nodeName, string nodeValue)
        {
            XmlElement newElement = xmlDoc.CreateElement(nodeName);
            newElement.InnerText = nodeValue;
            root.AppendChild(newElement);
        }     

Step 5: Define GenerateDocument Class

A main function GenerateWord() is implemented to generate the new document named the value of CustomerID from LetterFormTemplate.docm. In this class, System.IO.Package of .NET 3.0 is used to replicate the template, replacing custom XML file and finally generating the new document.

C#
 ///Generate Word document.            
public void GenerateWord( OrderInformation orderInfo)
        {
            string templateDoc = "LetterFormTemplate.docm";
            string filename = string.Format("{0}.docm", orderInfo.CustomerID);
           // Copy a new file name from template file
            File.Copy(templateDoc, filename, true);
           // Open the new Package
            Package pkg = Package.Open(filename, FileMode.Open, FileAccess.ReadWrite);
           // Specify the URI of the part to be read
            Uri uri = new Uri("/customXml/item1.xml", UriKind.Relative);
            PackagePart part = pkg.GetPart(uri);
           // Load XML 
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(orderInfo.ToXml());
           // Open the stream to write document
            StreamWriter partWrt = new StreamWriter
			(part.GetStream(FileMode.Open, FileAccess.Write));
            doc.Save(partWrt);

            partWrt.Flush();
            partWrt.Close();           
        }    

Everything is already done. Now I just implement my requirements.

* Read data from SQL Server to create the new OrderInformation instance. Assume that Northwind database is already set up. I execute a query to create the letter to Customer who owns order number 10205:

C#
///Get Data From SQL Server
 public OrderInformation GetOrderInformation()
        {
            string sqlQuery;
            sqlQuery = "SELECT o.OrderID, o.CustomerID, o.ShippedDate, ";
            sqlQuery = sqlQuery + "e.LastName, e.FirstName, e.Title,e.Address, _
			e.City, e.Region, e.PostalCode,e.HomePhone,e.Country ";
            sqlQuery = sqlQuery + "FROM Orders o ";
            sqlQuery = sqlQuery + "INNER JOIN Employees e ON _
				e.EmployeeID = o.EmployeeID ";
            sqlQuery = sqlQuery + "WHERE o.OrderID = 10250";
            using (SqlConnection conn = new SqlConnection_
		(@"Integrated Security=SSPI;Persist Security Info=False;_
		Initial Catalog=Northwind;Data Source=localhost"))
            {
                SqlCommand cmd = new SqlCommand(sqlQuery, conn);
                cmd.Connection.Open();

                SqlDataReader sdr = cmd.ExecuteReader();
                sdr.Read();
                OrderInformation orderInfo = new OrderInformation();
                orderInfo.OrderID = sdr.GetInt32(0);
                orderInfo.CustomerID = sdr.GetString(1);
                orderInfo.ShippedDate = sdr.GetDateTime(2);
                orderInfo.LastName = sdr.GetString(3);
                orderInfo.FirstName = sdr.GetString(4);
                orderInfo.Title = sdr.GetString(5);
                orderInfo.Address = sdr.GetString(6);
                orderInfo.City = sdr.GetString(7);
                orderInfo.Region = sdr.GetString(8);
                orderInfo.PostCode = sdr.GetString(9);
                orderInfo.HomePhone = sdr.GetString(10);
                orderInfo.Country = sdr.GetString(11);

                return orderInfo;
            }
        }    

* Read data from XML file that contains many Customers:

Screenshot - GenerateWord2007_4.jpg

Parse XML data, storing them on List of OrderInformation:

C#
public List ListOrders()
        {
            XmlDocument customers = new XmlDocument();
            Hashtable nodeData = new Hashtable();

            customers.Load("Data.xml");
            List<orderinformation /> orderList = new List<orderinformation />();
            foreach (XmlNode customer in customers.DocumentElement.ChildNodes)
            {
                foreach (XmlElement element in customer.ChildNodes)
                {

                    {
                        nodeData.Add(element.Name, element.InnerText);
                    }
                }
                if (nodeData.Count > 0)
                {
                    OrderInformation orderInfo = new OrderInformation();
                    orderInfo.OrderID = Convert.ToInt32(nodeData["OrderID"]);
                    orderInfo.CustomerID = nodeData["CustomerID"].ToString();
                    orderInfo.ShippedDate = Convert.ToDateTime(nodeData["ShippedDate"]);
                    orderInfo.LastName = nodeData["LastName"].ToString();
                    orderInfo.FirstName = nodeData["FirstName"].ToString();
                    orderInfo.Title = nodeData["Title"].ToString();
                    orderInfo.Address = nodeData["Address"].ToString();
                    orderInfo.City = nodeData["City"].ToString();
                    orderInfo.Region = nodeData["Region"].ToString();
                    orderInfo.PostCode = nodeData["PostCode"].ToString();
                    orderInfo.HomePhone = nodeData["HomePhone"].ToString();
                    orderInfo.Country = nodeData["Country"].ToString();
                    // Add To List 
                    orderList.Add(orderInfo);
                    nodeData.Clear();
                }       
            }
            return orderList;
        }     

Step 6: Run Application GenerateWord2007.exe

This application is implemented by two methods to generate the new documents that data is read from SQL Server or XML file.

Screenshot - GenerateWord2007_5.jpg

From SQL Server

C#
private void button1_Click(object sender, EventArgs e)
     {
         OrderInformation orderInfo;
         GenerateDocument doc = new GenerateDocument();
         orderInfo =  doc.GetOrderInformation();
         doc.GenerateWord(orderInfo);
     }  

From XML File

C#
private void button2_Click(object sender, EventArgs e)
     {
         GenerateDocument doc = new GenerateDocument();
         List orderList = new List();
         orderList = doc.ListOrders();
         foreach (OrderInformation orderInfo in orderList)
         {
             doc.GenerateWord(orderInfo);
         }
     }

Points of Interest

Open XML is definitively worth learning. Some new extensions such as *.docx for Microsoft Word, *.xlsx for Microsoft Excel or *.pptx for PowerPoint gives us the ability to read or modify them without using API. I just want to share my ideas so that you can have an overview about the concept or you may create something more efficient.

History

  • 26th August, 2007: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)