Creating a new Microsoft Word document from a template using OpenXml


When working with Microsoft Word automation there is often the requirement to create documents from define templates. With the addition of the OpenXml specification it is now possible to do this safely within server side code.

In a recent project I came across this requirement where a new Word document needed to be created based upon a template, with the addition of adding a custom XML part into the document. The custom XML part was used to perform bindings to a content controls within the document. Using OpenXml this became an easy task, as I will show.

The main processing I performed to achieve the Word Document creation was opening the required template document as a stream, and then creating a new document, as a MemoryStream, based on this based on this template:

using (MemoryStream documentStream = new MemoryStream((int)this.templateStream.Length))
{
templateStream.Position = 0L;
byte[] buffer = new byte[2048];
int length = buffer.Length;
int size;
while ((size = templateStream.Read(buffer, 0, length)) != 0)
{
documentStream.Write(buffer, 0, size);
}
documentStream.Position = 0L;

// Modify the document to ensure it is correctly marked as a Document and not Template
using (WordprocessingDocument document = WordprocessingDocument.Open(documentStream, true))
{
document.ChangeDocumentType(DocumentFormat.OpenXml.WordprocessingDocumentType.Document);
}

// Add the XML into the document and save to the correct location.
ProcessDocumentXml(inputDocument, documentStream);
File.WriteAllBytes(documentPath, documentStream.ToArray());
}

The ChangeDocumentType operation is critical to this process. This operation, provided by the OpenXml SDK, ensures the document is no longer marked as a Template but rather as a Document.

In this example I save stream as a file to a defined document path. One could just as easily write the response to say a web response stream.

To define Word documents from templates this is all the code one needs to write. However as you can see there is an additional step defined in this process called ProcessDocumentXml. It is this operation that performs the addition requirement to insert a custom Xml document into the Word Document.

private static void ProcessDocumentXml(XDocument inputDocument, Stream documentStream)
{
// Open the document in the stream and replace the custom XML part
using (Package packageFile = Package.Open(documentStream, FileMode.Open, FileAccess.ReadWrite))
{
PackagePart packagePart = null;

// Find part containing the correct namespace
foreach (PackagePart part in packageFile.GetParts())
{
if (part.ContentType.Equals("application/xml", StringComparison.OrdinalIgnoreCase))
{
using (XmlReader reader = XmlReader.Create(part.GetStream()))
{
if (reader != null)
{
reader.MoveToContent();
if (reader.NamespaceURI == "MyCustomXmlNamespace")
{
packagePart = part;
break;
}
}
}
}
}

if (packagePart != null)
{
// Delete the existing XML part
Uri uriData = packagePart.Uri;
if (packageFile.PartExists(uriData))
{
packageFile.DeletePart(uriData);
}

// Load the custom XML data
PackagePart pkgprtData = packageFile.CreatePart(uriData, CdsaHelper.DataContentType);
using (XmlWriter writer = XmlWriter.Create(pkgprtData.GetStream()))
{
inputDocument.Save(writer);
}
}
}
}

To replace a custom XML part within a Word Document one merely has to locate the necessary document part, usually by document namespace, and then perform the necessary Delete and Create operations; effectively an Update.

Hopefully this demonstrates the ease with which one can create Word Document from templates, using server side code. The ability to also insert custom Xml parts into the document provides he additional ability to customise the content of a document, once again within server side code.

Written by Carl Nolan

Comments (13)

  1. Tim says:

    Hi,

    Thanks for the post, it has been exceptionally helpful to me.

    I want to ask, however, about this problem.  Trying your code, I get an error here:

    using (WordprocessingDocument document = WordprocessingDocument.Open(documentStream, true))

    Error being "File contains corrupted data".  The file is fine, it seems – opens fine, no errors.

    I believe the problem is I don't know how it is you're getting the original template document to stream to the object "templateStream".  I've been experimenting with various options, none seem to work, and I don't know what I'm doing.  Could you post code for how to do this?

    Cheers,

    Tim.

  2. Carl Nolan says:

    The template should just be opened using

    using (Stream templateStream = File.OpenRead(templateDoc))

    where the templateDoc is the file path.

    This error may occur if the document is not a correct template or an older version of word.

  3. ashley says:

    how do you create a new microsoft word if you remove it from your pc

  4. Rajnish says:

    string fileName = @"C:MyDoc1.dotx";

                   string savePath = @"C:MyDoc2.dotx";

                   WordprocessingDocument templateDoc = WordprocessingDocument.Open(fileName, true);

                   MainDocumentPart templateMainPart = templateDoc.MainDocumentPart;

                   Stream templateStream = templateMainPart.GetStream();

                   using (MemoryStream documentStream = new MemoryStream((int)templateStream.Length))

                   {

                       templateStream.Position = 0L;

                       byte[] buffer = new byte[2048];

                       int length = buffer.Length;

                       int size;

                       while ((size = templateStream.Read(buffer, 0, length)) != 0)

                       {

                           documentStream.Write(buffer, 0, size);

                       }

                       documentStream.Position = 0L;

                       using (WordprocessingDocument document = WordprocessingDocument.Open(documentStream, true))

                       {

                           document.ChangeDocumentType(DocumentFormat.OpenXml.WordprocessingDocumentType.Document);

                       }

                       System.IO.File.WriteAllBytes(savePath, documentStream.ToArray());

                   }

    i am getting following error on line 16 when opening (documentstring,true).

    An exception of type 'System.IO.FileFormatException' occurred in WindowsBase.dll but was not handled in user code

  5. Mamatha says:

    what namespace should be included to get that CustomXml

  6. Eichels says:

    I keep getting a exception: "The specified package is invalid. The main part is missing."

    I'm using a simple "Hello World!" docx create in Word 2007 and validates just fine using the Open XML SDK 2.0 Productivity Tool.  Any ideas?  Thanks in advance….

  7. Claus Madsen says:

    Hi, Eichels – did you find a solution?

  8. varada says:

    Hi Eichels

          Hear is the solution for your application

    http://www.microsoft.com/…/details.aspx

    clk on the above link and downlad the OpenXml SDK tool  in you r system

    Add the reference to you application [Document.Format and WindowBase ]

    And add the below mentioned  name space to your application

    using System.IO;

    using DocumentFormat.OpenXml;

    using DocumentFormat.OpenXml.Packaging;

    using DocumentFormat.OpenXml.Wordprocessing;

    Your error will be rectified

  9. Alex Kahn says:

    What is the parameter inputDocument in function ProcessDocumentXml(inputDocument, documentStream);?

  10. Michael H says:

    @alex Kahn

    System.Xml.Linq.XDocument Should be the parameter

  11. R_M says:

    Where is this parameter 'inputDocument' extracted from.

    Does it come from the Template ?

  12. kritochin says:

    do we have the answer about 'inputDocument'? Where does it come from?

    Thank you very much!

  13. AJ says:

    Carl,

    Where do you declare and initialize InputDocument parameter to ProcessDocumentXml?