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
Anonymous
September 26, 2010
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.Anonymous
September 29, 2010
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.Anonymous
November 07, 2010
how do you create a new microsoft word if you remove it from your pcAnonymous
February 03, 2011
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 codeAnonymous
April 03, 2011
what namespace should be included to get that CustomXmlAnonymous
May 27, 2011
The comment has been removedAnonymous
February 24, 2012
Hi, Eichels - did you find a solution?Anonymous
February 06, 2013
Hi Eichels Hear is the solution for your application 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 rectifiedAnonymous
November 04, 2013
What is the parameter inputDocument in function ProcessDocumentXml(inputDocument, documentStream);?Anonymous
January 12, 2014
@alex Kahn System.Xml.Linq.XDocument Should be the parameterAnonymous
January 27, 2014
Where is this parameter 'inputDocument' extracted from. Does it come from the Template ?Anonymous
February 03, 2014
do we have the answer about 'inputDocument'? Where does it come from? Thank you very much!Anonymous
February 04, 2015
Carl, Where do you declare and initialize InputDocument parameter to ProcessDocumentXml?