C#: Create and Manipulate Word Documents Programmatically Using DocX

Posted on September 28 2013 01:09 PM by jatten in C#, CodeProject, Tools   ||   Comments (6)

Typewriters640In a recent post, I extolled the virtues of a wonderful OSS library I had found for working with Excel data programmatically, LinqToExcel. In that post, I also mentioned a fantastic library for working with Word docs as well, and promised to discuss it in my next post. This is that post.

The big pain point in working with MS Word documents programmatically is . . . the Office Interop. To get almost anything done with Word (including simply pulling the text out of the document, you pretty much need to use Interop, which also means you have to have Word installed on the local machine which is consuming your application. Additionally, my understanding is that there are issues with doing Word automation on the server side.

Image by Mohylek - Some Rights Reserved

Interop is essentially a giant wrapper around the ugliness that is COM, and the abstraction layer is thin indeed. If you need to automate MS Office applications, Interop (or going all the way down to the COM level) is pretty much the only way to do it, and obviously, you need to have Office installed on the client machine for it to work at all.

Often times though, we don't so much need to automate the office application directly so much as get at the contents of Office file (such as Word or Excel files). Dealing with all that Interop nastiness makes this more painful than it needs to be.

Thankfully, the open source DocX library by Cathal Coffey solves both problems nicely, and unlike Interop, presents an easy-to-use, highly discoverable API for performing myriad manipulations/extractions against the Word document format (the .docx format, introduced as of Word 2007). Best of all, DocX does not require that Word or any other Office dependencies be installed on the client machine! The full source is available from Coffey's Codeplex repo, or you can add DocX to your project using Nuget.

In this post, we will look at a few of the basics for using this exceptionally useful library. Know that under the covers and with a little thought, there is a lot of functionality here beyond what we will look at in this article.

Add DocX to your project using Nuget

As with LinqToExcel, you can add the DocX library to your Visual Studio solution using the Nuget Package Manager Console by doing:

Install DocX using the Nuget Package Manager Console:
PM> Install-Package DocX

 

Alternatively, you can use the Solution Explorer. Right-click on the Solution, select "Manager Nuget Packages for Solution," and type "DocX in the search box (make sure you have selected "Online" in the left-hand menu). When you have located the DocX package, click Install:

Install DocX using the Nuget Package Manager GUI in VS Solution Explorer:

get-docx-from-nuget

 

Getting Started – Create a Word Document Using the DocX Library

Wanna Quickly create a Word-compatible, .docx format document on the fly from your code? Do this (I am assuming you have Word installed on your local machine):

(Note – change the file path to reflect your own machine)

A Really Simple Example:
using Novacode;
using System;
using System.Diagnostics;
 
namespace BlogSandbox
{
    public class DocX_Examples
    {      
        public void CreateSampleDocument()
        {
            // Modify to siut your machine:
            string fileName = @"D:\Users\John\Documents\DocXExample.docx";
  
            // Create a document in memory:
            var doc = DocX.Create(fileName);
  
            // Insert a paragrpah:
            doc.InsertParagraph("This is my first paragraph");
  
            // Save to the output directory:
            doc.Save();
  
            // Open in Word:
            Process.Start("WINWORD.EXE", fileName);
        }
    }
}

 

Note in the above we need to add using Novacode; to our namespace imports at the top of the file. The DocX library is contained within this namespace. If you run the code above, a word document should open like this:

Output of Really Simple Example Code:

docx-simple-code-sample-1

What we did in the above example was:

  • Create an in-memory instance of a DocX object with a file name passed in as part of the constructor.
  • Insert a DocX.Paragraph object containing some text.
  • Save the result to disc as a properly formatted .docx file.

Until we execute the Save() method, we are working with the XML representation of our new document in memory. Once we save the file to disc, we find a standard Word-compatible file in our Documents directory.

Use DocX to Add Formatted Paragraphs – A More Useful Example

A slightly more useful example might be to create a document with some more complex formatted text:

Create Multiple Paragraphs with Basic Formatting:
public void CreateSampleDocument()
{
    string fileName = @"D:\Users\John\Documents\DocXExample.docx";
    string headlineText = "Constitution of the United States";
    string paraOne = ""
        + "We the People of the United States, in Order to form a more perfect Union, "
        + "establish Justice, insure domestic Tranquility, provide for the common defence, "
        + "promote the general Welfare, and secure the Blessings of Liberty to ourselves "
        + "and our Posterity, do ordain and establish this Constitution for the United States of America.";
  
    // A formatting object for our headline:
    var headLineFormat = new Formatting();
    headLineFormat.FontFamily = new System.Drawing.FontFamily("Arial Black");
    headLineFormat.Size = 18D;
    headLineFormat.Position = 12;
  
    // A formatting object for our normal paragraph text:
    var paraFormat = new Formatting();
    paraFormat.FontFamily = new System.Drawing.FontFamily("Calibri");
    paraFormat.Size = 10D;
  
    // Create the document in memory:
    var doc = DocX.Create(fileName);
  
    // Insert the now text obejcts;
    doc.InsertParagraph(headlineText, false, headLineFormat);
    doc.InsertParagraph(paraOne, false, paraFormat);
  
    // Save to the output directory:
    doc.Save();
  
    // Open in Word:
    Process.Start("WINWORD.EXE", fileName);
}

 

Here, we have created some DocX.Formatting objects in advance, and then passed them as parameters to the InsertParagraph method for each of the two paragraphs we create in our code. When the code executes, Word opens and we see this:

Output from Creating Multiple Formatted Paragraphs

docx-simple-code-sample-2

In the above, the FontFamily and Size properties of the Formatting object are self-evident. The Position property determines the spacing between the current paragraph and the next.

We can also grab a reference to a paragraph object itself and adjust various properties. Instead of creating a Formatting object for our headline like we did in the previous example, we could grab a reference as the return from the InsertParagraph method and muck about:

Applying Formatting to a Paragraph Using the Property Accessors:
// Insert the Headline and do some formatting:
Paragraph headline = doc.InsertParagraph(headlineText);
headline.Color(System.Drawing.Color.Blue);
headline.Font(new System.Drawing.FontFamily("Comic Sans MS"));
headline.Bold();
headline.Position(12D);
headline.FontSize(18D);

 

This time, when the program executes, we see THIS:

docx-simple-code-sample-3-comic-sans

OH NO YOU DID NOT!

Yes, yes I DID print that headline in Comic Sans. Just, you know, so you could see the difference in formatting.

There is a lot that can be done with text formatting in a DocX document. Headers/Footers, paragraphs, and individual words and characters. Importantly, most of the things you might go looking for are easily discoverable – in other words, the author has done a great job building out his API.

Find and Replace Text Using DocX - Merge Templating, Anyone?

Of course, one of the most common things we might want to do is scan a pre-existing document, and replace certain text. Think templating here. For example, performing a standard Word Merge is not very doable on your web server, but using DocX, we can accomplish the same thing. The following example is simple due to space constraints, but you can see the possibilities:

First, just for kicks, we will create an initial document programmatically in one method, then write another method to find and replace certain text in the document:

Create a Sample Document:
private DocX GetRejectionLetterTemplate()
{
    // Adjust the path so suit your machine:
    string fileName = @"D:\Users\John\Documents\DocXExample.docx";
  
    // Set up our paragraph contents:
    string headerText = "Rejection Letter";
    string letterBodyText = DateTime.Now.ToShortDateString();
    string paraTwo = ""
        + "Dear %APPLICANT%" + Environment.NewLine + Environment.NewLine
        + "I am writing to thank you for your resume. Unfortunately, your skills and "
        + "experience do not match our needs at the present time. We will keep your "
        + "resume in our circular file for future reference. Don't call us, we'll call you. "
        + Environment.NewLine + Environment.NewLine
        + "Sincerely, "
        + Environment.NewLine + Environment.NewLine
        + "Jim Smith, Corporate Hiring Manager";
  
    // Title Formatting:
    var titleFormat = new Formatting();
    titleFormat.FontFamily = new System.Drawing.FontFamily("Arial Black");
    titleFormat.Size = 18D;
    titleFormat.Position = 12;
  
    // Body Formatting
    var paraFormat = new Formatting();
    paraFormat.FontFamily = new System.Drawing.FontFamily("Calibri");
    paraFormat.Size = 10D;
    titleFormat.Position = 12;
  
    // Create the document in memory:
    var doc = DocX.Create(fileName);
  
    // Insert each prargraph, with appropriate spacing and alignment:
    Paragraph title = doc.InsertParagraph(headerText, false, titleFormat);
    title.Alignment = Alignment.center;
  
    doc.InsertParagraph(Environment.NewLine);
    Paragraph letterBody = doc.InsertParagraph(letterBodyText, false, paraFormat);
    letterBody.Alignment = Alignment.both;
  
    doc.InsertParagraph(Environment.NewLine);
    doc.InsertParagraph(paraTwo, false, paraFormat);
  
    return doc;
}

 

See the %APPLICANT% placeholder? That is my replacement target (a poor-man's merge field, if you will). Now that we have a private method to generate a document template of sorts, let's add a public method to perform a replacement action:

Find and Replace Text in a Word Document Using DocX:
public void CreateRejectionLetter(string applicantField, string applicantName)
{
    // We will need a file name for our output file (change to suit your machine):
    string fileNameTemplate = @"D:\Users\John\Documents\Rejection-Letter-{0}-{1}.docx";
  
    // Let's save the file with a meaningful name, including the applicant name and the letter date:
    string outputFileName = string.Format(fileNameTemplate, applicantName, DateTime.Now.ToString("MM-dd-yy"));
  
    // Grab a reference to our document template:
    DocX letter = this.GetRejectionLetterTemplate();
  
    // Perform the replace:
    letter.ReplaceText(applicantField, applicantName);
  
    // Save as New filename:
    letter.SaveAs(outputFileName);
  
    // Open in word:
    Process.Start("WINWORD.EXE", "\"" + outputFileName + "\"");
}

 

Now, when we run the code above, our output is thus:

docx-rejection-letter-sample

Obviously, the preceding example was a little contrived and overly simple. But you can see the potential . . . If our letter contained additional "merge fields, we could just as easily pass in a Dictionary<string, string>, where the Dictionary contains one or more Key Value Pairs containing a replacement target and a replacement value. Then we could iterate,  using the Dictionary Keys as the search string, and replace with the Dictionary values.

DocX Exposes Many of the Most Useful Parts of the Word Object Model

In this quick article, we have only scratched the surface. DocX exposes most of the stuff we commonly wish we could get to within a Word document (Tables, Pictures, Headers, Footers, Shapes, etc.) without forcing us to navigate the crusty Interop model. This also saves us from some of the COM de-referencing issues which often arise when automating Word within an application. Ever had a bunch of "orphaned" instances of Word (or Excel, etc.) running in the background, visible only in the Windows Task Manager? Yeah, I thought so . . .

If you need to generate or work with Word documents on a server, this is a great tool as well. No dependencies on MS Office, no need to have Word running. You can generate Word documents on the fly, and/or from templates, ready to be downloaded.

I strongly recommend you check out the project on Codeplex. Also, the project author, Cathal Coffey, discusses much of the DocX functionality on his own blog. If you dig DocX, drop by and let him know. He's really done a fantastic job.

Additional Resources/Items of Interest

 

Posted on September 28 2013 01:09 PM by jatten     

Comments (6)

Comments (6) -

N. Scott
N. Scott
9/28/2013 9:12:07 PM #

Neatly written!

DocX library is indeed very powerful and flexible.


As an extremely simple alternative, where only Search-Replace(Templating) is required, here's another one that does the trick without using any third party libraries.

www.codeproject.com/.../Simple-DocX-Editor-in-Native-Csharp

jatten
jatten
9/29/2013 2:44:17 PM #

Thanks for reading, and for taking the time to comment!

Yes, I had also found another templating library I've yet to have the time to check out. Now I'm adding your suggestion to my list as well.

Cheers!

darkfreq
darkfreq
9/30/2013 2:14:10 PM #

Thanks so much for writing this. This will save us a ton of time. We were able to work our way around the text and formatting but the image manipulation confounded us. Can't wait to try this out; from the code samples is looks so simple. A million thanks!!!

Sakapuce
Sakapuce
9/30/2013 2:51:12 PM #

A serious alternative to Aspose.

Sakapuce
Sakapuce
9/30/2013 2:52:00 PM #

A serious alternative to Aspose.

XIV-Admin
XIV-Admin
9/30/2013 5:40:58 PM #

darkFreq - Thanks for reading, and hope it works out for you! Please let me know how it goes, and if you run into anything cool, post back here in the comments . . .

Sakapuce - Yeah, right? Thanks for reading, and taking the time to comment!

Pingbacks and trackbacks (2)+

Comments are closed

About the author

My name is John Atten, and my username on many of my online accounts is xivSolutions. I am Fascinated by all things technology and software development. I work mostly with C#, Java, SQL Server 2012, learning ASP.NET MVC, html 5/CSS/Javascript. I am always looking for new information, and value your feedback (especially where I got something wrong!). You can email me at:

jatten@typecastexception.com

Web Hosting by