Introduction
Microsoft Open XML is a new technology that Microsoft recommends using to perform CRUD operations on Office documents (Word, Excel, Powerpoint, etc.). Microsoft recommends using OpenXML over MS Interop. Microsoft also does not recommend the use of server side automation of Microsoft Office applications through server side codes such as ASP.NET, DCOM, and WIndows NT services that use Microsoft Office Interop libraries.
Although OpenXML is the direction that Microsoft wants developers to use to manipulate office documents, it is not as easy to use as compared to using the Microsoft interop libraries. This tip will demonstrate one of the hardest challenges I faced using openXML, which is inserting an image into a shape. For Microsoft interop, this task could've been done in as much as 6 lines.
Background
Starting from Microsoft Office 2007, Open XML file format has become the default file format for Microsoft Office.
To understand this tutorial, make sure you know how to use the basics of the openxml SDK, such as opening and saving documents with C#.
This article assumes you know how to insert an image into a Word document, open and close a Word document using OpenXML. See the link below:
Using the Code
Add the following OpenXML namespaces to your class:
using System;
using System.Collections.Generic;
using System.Linq;
using DocumentFormat.OpenXml;
using A = DocumentFormat.OpenXml.Drawing;
using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing;
using PIC = DocumentFormat.OpenXml.Drawing.Pictures;
using DocumentFormat.OpenXml.Drawing.Wordprocessing;
using Wps = DocumentFormat.OpenXml.Office2010.Word.DrawingShape;
With the namespaces added, you can now use the required classes from OpenXML. Open the document using OpenXML. Add an imagePart
that references the image object (The image you want to insert) to the MaindocumentPart
object by using a file stream and get the ID of the image.
string temp;
MainDocumentPart mainPart = document.MainDocumentPart;
ImagePart imagePart = mainPart.AddImagePart(ImagePartType.Bmp);
using (FileStream stream = new FileStream(barcodepath, FileMode.Open))
{
imagePart.FeedData(stream);
}
temp = mainPart.GetIdOfPart(imagePart);
In office OpenXML, a picture that is inserted into a Word document is considered a "Blip
" Object or element. The class is derived from the DocumentFormat.OpenXml.Drawing the Blip
must have an Embed value that is an imagePart
ID. The Blip
object then goes inside a BlipFill Object/element
, and that also goes inside a graphicData Object/element and that in turn goes into a graphic object element. I'm pretty sure by now you've realized everything works like an XML tree. Sample Open XML tree below:
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<a:graphicData uri=
"http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
<wps:wsp>
<wps:cNvSpPr>
<a:spLocks noChangeArrowheads="1" />
</wps:cNvSpPr>
<wps:spPr bwMode="auto">
<a:xfrm>
<a:off x="0" y="0" />
<a:ext cx="1234440" cy="1234440" />
</a:xfrm>
<a:prstGeom prst="roundRect">
<a:avLst>
<a:gd name="adj" fmla="val 16667" />
</a:avLst>
</a:prstGeom>
<a:blipFill dpi="0" rotWithShape="1">
<a:blip r:embed="Raade88ffea8d4c1b" />
<a:stretch>
<a:fillRect l="10000" t="10000"
r="10000" b="10000" />
</a:stretch>
</a:blipFill>
</wps:spPr>
<wps:bodyPr rot="0" vert="horz"
wrap="square" lIns="91440" tIns="45720"
rIns="91440" bIns="45720" anchor="t"
anchorCtr="0" upright="1">
<a:noAutofit />
</wps:bodyPr>
</wps:wsp>
</a:graphicData>
</a:graphic>
Now I have a reference of the image(barcode) I'm trying to add to my document. I want to insert the image into the shapes in the template document. To do this, I would have to use some LINQ to iterate through the document and get a reference to all the shapes in the document. The wps:spPr
element you see in the above XML code is the XML element for the shapes in my document. The equivalent C# class is WordprocessingShape.
IEnumerable<DocumentFormat.OpenXml.Office2010.Word.DrawingShape.WordprocessingShape> shapes2 =
document.MainDocumentPart.document.Body.Descendants
<DocumentFormat.OpenXml.Office2010.Word.DrawingShape.WordprocessingShape>();
Now that I have a collection of all the shape references in my document, I loop through the collection with a foreach
, and through each iteration, I create a Blip
object. Set the Blip
object embed value to the picture ID reference I captured earlier form the image part. I also create a Stretch
object, and FillRectangle
object (these are not really necessary, I just used them for proper alignment of the barcode). And append each to its parent objects like the XML tree equivalent.
foreach (DocumentFormat.OpenXml.Office2010.Word.DrawingShape.WordprocessingShape sp in shapes2)
{
A.BlipFill blipFill = new A.BlipFill() { Dpi = (UInt32Value)0U, RotateWithShape = true };
A.Blip blip1 = new A.Blip() { Embed = temp };
A.Stretch stretch1 = new A.Stretch();
A.FillRectangle fillRectangle1 = new A.FillRectangle()
{ Left = 10000, Top = 10000, Right = 10000, Bottom = 10000 };
Wps.WordprocessingShape wordprocessingShape1 = new Wps.WordprocessingShape();
stretch1.Append(fillRectangle1);
blipFill.Append(blip1);
blipFill.Append(stretch1);
Wps.ShapeProperties shapeProperties1 = sp.Descendants<Wps.ShapeProperties>().First();
shapeProperties1.Append(blipFill);
}
That's pretty much it. Run the code and use a Word document that has inline shapes (rectangle, oval, etc.) and try to insert an image into them.