Creating a Word Document with the Open XML SDK 2.0

February 25, 2009

Introduction

I'm always eager to find ways of making my life easier, so recently I've been searching for a method of creating a Microsoft Word document using purely managed code (none of that Object Model awfulness). I'd been playing around with version 1.0 of the Open XML SDK and while this works fine, it's not strongly typed, so requires you to manipulate the XML directly.

Brian Jones has an excellent post about the SDK in his blog: – http://blogs.msdn.com/brian_jones/archive/2008/10/06/open-xml-format-sdk-2-0.aspx, which also includes an example of how to create a basic Word document in C#. This post hopes to demonstrate how to create a document that includes a header and an image.

If you're following along with this, you'll need the SDK if you don't have it already, you can get it from: – http://go.microsoft.com/fwlink/?LinkId=127912.

Creating the console application

Open up Visual Studio and create a new Console Application. In this demo I've called it DocumentBuilder. Next add a reference to the DocumentFormat.OpenXML and WindowsBase dlls.

In this example, to include the image, I've stored it as a Resource, so add a new Resources File to your project (I've called it DocumentResources.resx) and add your image, the image should get copied into a new Resources folder in your project.

Add these using statements to the Program.cs file: –

using System.IO;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using d = DocumentFormat.OpenXml.Drawing;

Now add the following constants to the Program class: –

private const double EMU_PER_PIXEL = 9525;
private const string GRAPHIC_DATA_URI = @"http://schemas.openxmlformats.org/drawingml/2006/picture";

To keep a bit of structure about the program I've implemented three methods, one to build the document itself, one to build the header and finally one to insert the image. Firstly, add the following code for the BuildDocument method: –

private static void BuildDocument(string fileName)
{
    using (WordprocessingDocument w = WordprocessingDocument.Create(fileName, WordprocessingDocumentType.Document))
    {
        MainDocumentPart mp = w.AddMainDocumentPart();
        Document d = new Document();
        Body b = new Body();
        Paragraph p = new Paragraph();
        Run r = new Run();
        Text t = new Text();
        t.Text = "This is some body text.";
        r.Append(t);
        p.Append(r);
        b.Append(p);
        HeaderPart hp = mp.AddNewPart<HeaderPart>();
        string headerRelationshipID = mp.GetIdOfPart(hp);
        SectionProperties sectPr = new SectionProperties();
        HeaderReference headerReference= newHeaderReference();
        headerReference.Id = headerRelationshipID;
        headerReference.Type = HeaderFooterValues.Default;
        sectPr.Append(headerReference);
        b.Append(sectPr);
        d.Append(b);
        hp.Header = BuildHeader(hp, "This is some header text.");
        hp.Header.Save();
        mp.Document = d;
        mp.Document.Save();
        w.Close();
    }
}

Next, add the following for the BuildHeader method: –

private static Header BuildHeader(HeaderPart hp, string title)
{
    // Add an ImagePart.
    ImagePart ip = hp.AddImagePart(ImagePartType.Jpeg);
    string imageRelationshipID = hp.GetIdOfPart(ip);
    using (Stream imgStream = ip.GetStream())
    {
        System.Drawing.Bitmap logo = DocumentResources.sw;
        logo.Save(imgStream, System.Drawing.Imaging.ImageFormat.Jpeg);
    }
    Header h = new Header();
    Paragraph p = new Paragraph();
    Run r = new Run();
    Drawing drawing = BuildImage(imageRelationshipID, "sw.gif", 48, 48);
    r.Append(drawing);
    p.Append(r);
    r = new Run();
    RunProperties rPr = new RunProperties();
    TabChar tab = new TabChar();
    Bold b = new Bold();
    Color color = new Color { Val = "006699" };
    FontSize sz = new FontSize { Val = 40 };
    Text t = new Text { Text = title };
    rPr.Append(b);
    rPr.Append(color);
    rPr.Append(sz);
    r.Append(rPr);
    r.Append(tab);
    r.Append(t);
    p.Append(r);
    h.Append(p);
    return h;
}

Lastly, add the BuildImage method: –

private static Drawing BuildImage(string imageRelationshipID, string imageName,
    int pixelWidth, int pixelHeight)
{
    int emuWidth = (int)(pixelWidth * EMU_PER_PIXEL);
    int emuHeight = (int)(pixelHeight * EMU_PER_PIXEL);
    Drawing drawing = new Drawing();
    d.Wordprocessing.Inline inline = new d.Wordprocessing.Inline { DistanceFromTop = 0, DistanceFromBottom = 0, DistanceFromLeft = 0, DistanceFromRight = 0 };
    d.Wordprocessing.Anchor anchor = new d.Wordprocessing.Anchor();
    d.Wordprocessing.SimplePosition simplePos = new d.Wordprocessing.SimplePosition { X = 0, Y = 0 };
    d.Wordprocessing.Extent extent = new d.Wordprocessing.Extent { Cx = emuWidth, Cy = emuHeight };
    d.Wordprocessing.DocProperties docPr = new d.Wordprocessing.DocProperties { Id = 1, Name = imageName };
    d.Graphic graphic = new d.Graphic();
    // We don't have to hard code a URI anywhere else in the document but if we don't do it here 
    // we end up with a corrupt document.
    d.GraphicData graphicData = new d.GraphicData { Uri = GRAPHIC_DATA_URI };
    d.Pictures.Picture pic = new d.Pictures.Picture();
    d.Pictures.NonVisualPictureProperties nvPicPr = new d.Pictures.NonVisualPictureProperties();
    d.Pictures.NonVisualDrawingProperties cNvPr = new d.Pictures.NonVisualDrawingProperties { Id = 2, Name = imageName };
    d.Pictures.NonVisualPictureDrawingProperties cNvPicPr = new d.Pictures.NonVisualPictureDrawingProperties();
    d.Pictures.BlipFill blipFill = new d.Pictures.BlipFill();
    d.Blip blip = new d.Blip { Embed = imageRelationshipID };
    d.Stretch stretch = new d.Stretch();
    d.FillRectangle fillRect = new d.FillRectangle();
    d.Pictures.ShapeProperties spPr = new d.Pictures.ShapeProperties();
    d.Transform2D xfrm = new d.Transform2D();
    d.Offset off = new d.Offset { X = 0, Y = 0 };
    d.Extents ext = new d.Extents { Cx = emuWidth, Cy = emuHeight };
    d.PresetGeometry prstGeom = new d.PresetGeometry { Preset = d.ShapeTypeValues.Rectangle };
    d.AdjustValueList avLst = new d.AdjustValueList();
    xfrm.Append(off);
    xfrm.Append(ext);
    prstGeom.Append(avLst);
    stretch.Append(fillRect);
    spPr.Append(xfrm);
    spPr.Append(prstGeom);
    blipFill.Append(blip);
    blipFill.Append(stretch);
    nvPicPr.Append(cNvPr);
    nvPicPr.Append(cNvPicPr);
    pic.Append(nvPicPr);
    pic.Append(blipFill);
    pic.Append(spPr);
    graphicData.Append(pic);
    graphic.Append(graphicData);
    inline.Append(extent);
    inline.Append(docPr);
    inline.Append(graphic);
    drawing.Append(inline);
    return drawing;
}

All that remains is to call the BuildDocument from the Main method: –

public static void Main(string[] args)
{
    BuildDocument(@"C:\TempTest.docx");
}

Press F5 to run your app and you should get your Word document. If you're using your own image in the header you'll notice that it's a bit mis-shapen (unless you happen to have picked a 48 x 48 pixel image). To correct this just change the pixelHeight and pixelWidth parameters passed into the BuildImage method to suit.

References


Profile picture

Written by Stuart Whiteford
A software developer with over 20 years' experience developing business applications primarily using Microsoft technologies including ASP.NET (Web Forms, MVC and Core), SQL Server and Azure.