{"id":401305,"date":"2024-06-29T15:46:06","date_gmt":"2024-06-29T15:46:06","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=401305"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=401305","title":{"rendered":"<span>Tutorial: how to port a project from Interop Word API to Open XML SDK<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>With the .NET5 release further development of some projects was questionable due to the complexity of porting. One can abandon small outdated libraries or find a replacement. But it&#8217;s hard to throw away Microsoft.Office.Interop.Word.dll. Microsoft doesn&#8217;t plan to add compatibility with .NET Core\/5+, so in this article we focus on creating Word files with Open XML SDK.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/390\/b8b\/f17\/390b8bf17270dea1fcba430b78fd3408.png\" width=\"450\" height=\"255\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/390\/b8b\/f17\/390b8bf17270dea1fcba430b78fd3408.png\"\/><figcaption><\/figcaption><\/figure>\n<h3>Introduction<\/h3>\n<p>Office Open XML aka OpenXML or OOXML, is an XML-based format for office documents. It includes text files, spreadsheets, presentations, as well as diagrams, shapes, and other graphic material. In June 2014 Microsoft released Open XML SDK source code on <a href=\"https:\/\/github.com\/OfficeDev\/Open-XML-SDK\">GitHub<\/a> to work with this format.<\/p>\n<p>This library has impressive advantages:<\/p>\n<ul>\n<li>\n<p>compatible with .NET 5+,<\/p>\n<\/li>\n<li>\n<p>does not require Microsoft Office installation,<\/p>\n<\/li>\n<li>\n<p>high-speed operation,<\/p>\n<\/li>\n<li>\n<p>open source code.<\/p>\n<\/li>\n<\/ul>\n<p>The disadvantages include:<\/p>\n<ul>\n<li>\n<p>complex API,<\/p>\n<\/li>\n<li>\n<p>scant documentation.<\/p>\n<\/li>\n<\/ul>\n<p>The disadvantages definitely complement each other. Actually, it was the reason to create this article.<\/p>\n<p>But the open source code was the big plus. If we had COM libraries&#8217; open source code, the developer community would help with porting to .NET Core\/5+. Besides attracting third-party developers, open source code allows everyone to find and fix errors and vulnerabilities. Or at least to report them. The quality of open libraries is <a href=\"https:\/\/pvs-studio.com\/en\/blog\/posts\/cpp\/0762\/\">crucial<\/a> for all projects that can use them. For example, we conducted a small <a href=\"https:\/\/pvs-studio.com\/en\/blog\/posts\/csharp\/0777\/\">audit<\/a> of the Open XML SDK code when we first got acquainted with this library.<\/p>\n<h3>Office developers&#8217; pain<\/h3>\n<p>Third-party developers created a lot of software for Office products. These are plugins for Word, Excel, Outlook. Many companies implemented themselves convenient plugins and report generators in Word format. On July 3, 2021 a terrible thing happened &#8212; Microsoft closed all the tickets on .NET 5+ in VSTO \/ COM support with a comment from its representatives:<\/p>\n<blockquote>\n<p>&#8230;The VSTO\/COM Add-Ins platform is very important to Microsoft, and we plan to continue to support it in Office with .NET Framework 4.8 as the last major version&#8230;VSTO\/COM Add-Ins cannot be created with .NET Core and .NET 5+. This is because .NET Core\/.NET 5+ cannot work together with .NET Framework in the same process and may lead to add-in load failures. Microsoft will not be updating VSTO or the COM Add-in platform to use .NET Core or .NET 5+&#8230; <\/p>\n<\/blockquote>\n<p>According to their information, .NET 5+ support is not expected. Here&#8217;s one of the discussions that hasn&#8217;t stopped after the announcement: &#171;<a href=\"https:\/\/developercommunity.visualstudio.com\/t\/Please-port-Visual-Studio-Tools-For-Offi\/757925\">Please port Visual Studio Tools For Office (VSTO) to .NET 5\/7, to enable VSTO add-in development in C# in .Net 5\/7<\/a>&#171;.<\/p>\n<p>The plugin developers were offered to switch to Office JavaScript API. This is a completely different language, where the API does not allow you to do even the smallest part of what it could do. However, one may switch to Open XML SDK (<a href=\"https:\/\/www.nuget.org\/packages\/Open-XML-SDK\">nuget<\/a>) library to create documents from C# code.<\/p>\n<h3>The basics<\/h3>\n<p>Before we analyze examples, we should understand what these two libraries work with in general and what is the difference between their approaches.<\/p>\n<p>A Word file is a set of boxed xml documents. All elements are structured by tags.<\/p>\n<p>For example, a paragraph inside a document will look as follows:<\/p>\n<pre><code class=\"xml\">&lt;w:p w:rsidR=\"007D2247\" w:rsidRDefault=\"009A4B44\"          xmlns:w=\"http:\/\/schemas.openxmlformats.org\/wordprocessingml\/2006\/main\">   &lt;w:r>     &lt;w:t>test&lt;\/w:t>   &lt;\/w:r>   &lt;w:bookmarkStart w:name=\"_GoBack\" w:id=\"0\" \/>   &lt;w:bookmarkEnd w:id=\"0\" \/> &lt;\/w:p><\/code><\/pre>\n<p>The Interop.Word assembly provides higher abstraction level than this structure and often works with a part of the document &#8212; Range. However, Open XML SDK follows the path of reflecting the document&#8217;s inner structure in the code itself.  <em>&lt;w:p><\/em> paragraphs, *&lt;w:t> *sections of text and everything else become objects in code. If you don&#8217;t create the body of the document, the paragraph and other mandatory &#171;parents&#187;, then there will be no place to insert text.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d86\/c81\/5e8\/d86c815e8862f10b6216f250654ad442.png\" width=\"300\" height=\"293\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d86\/c81\/5e8\/d86c815e8862f10b6216f250654ad442.png\"\/><figcaption><\/figcaption><\/figure>\n<p>The screenshot shows the inner structure of the main file for a Word document &#8212; document.xml. The file contains the content of the document itself.<\/p>\n<p>The screenshot was taken in the Open XML SDK 2.5 Productivity Tool which is necessary for working with Open XML. By the time of writing this article, Microsoft removed the utility from its website. And a link to <a href=\"https:\/\/github.com\/rmboggs\/DocxToSource\">DocxToSource<\/a> was added to the <a href=\"https:\/\/github.com\/OfficeDev\/Open-XML-SDK\">Open-XML-SDK<\/a> repository, which should be a replacement for the outdated Productivity Tool. However, this replacement is still a prototype, so for now it&#8217;s better to find the good old Productivity Tool. The old utility allows you to view the structure of the document, get acquainted with the autogenerated code. <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/6a5\/10b\/d76\/6a510bd765d1d43f80e2b7ca99c2255a.png\" width=\"580\" height=\"261\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6a5\/10b\/d76\/6a510bd765d1d43f80e2b7ca99c2255a.png\"\/><figcaption><\/figcaption><\/figure>\n<p>It also allows you to compare two different documents &#8212; both code for their creation and inner structure. <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b96\/98d\/09b\/b9698d09bfc7cd7bf9a0dfc9e63b33f4.png\" width=\"580\" height=\"284\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b96\/98d\/09b\/b9698d09bfc7cd7bf9a0dfc9e63b33f4.png\"\/><figcaption><\/figcaption><\/figure>\n<h3>Examples<\/h3>\n<p>In the entire article, we use this alias for Interop.Word for the sake of readability:<\/p>\n<pre><code class=\"cs\">using MicrosoftWord = Microsoft.Office.Interop.Word;<\/code><\/pre>\n<p>Also, we will call the Open XML SDK simply Open XML.<\/p>\n<h4>How to create Word document<\/h4>\n<p><strong><em>Interop.Word:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">MicrosoftWord.Application wordApp = new MicrosoftWord.Application(); MicrosoftWord.Document wordDoc = wordApp.Documents.Add(); MicrosoftWord.Range docRange = wordDoc.Range(); .... \/\/ here we work with the document, if necessary wordDoc.SaveAs2(pathToDocFile); wordApp.Quit();<\/code><\/pre>\n<p>Everything is quite simple here, but there are also some pitfalls. When working with Interop we interact not just with some object in memory, but with a COM object. That&#8217;s why we have to terminate all the processes after the program finishes working. This problem has been raised more than once on Stack Overflow (<a href=\"https:\/\/stackoverflow.com\/questions\/158706\/how-do-i-properly-clean-up-excel-interop-objects?page=1&amp;tab=active\">1<\/a>,<a href=\"https:\/\/stackoverflow.com\/questions\/25134024\/clean-up-excel-interop-objects-with-idisposable\/25135685\">2<\/a>) and people proposed various solutions to it.<\/p>\n<p>There is a solution with <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.runtime.interopservices.marshal?view=net-5.0\">Marshal Class<\/a>, which is a part of InteropServices.<\/p>\n<pre><code class=\"cs\">finally {   if (Marshal.IsComObject(wordDoc))     try     {       Marshal.FinalReleaseComObject(wordDoc);     }     catch { throw; }     if (Marshal.IsComObject(wordApp))     try     {       Marshal.FinalReleaseComObject(wordApp);     }     catch { throw; } }<\/code><\/pre>\n<p>However, in this case we may miss some processes.<\/p>\n<p>A more reliable option with a <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.gc?view=net-5.0\">GC<\/a> call:<\/p>\n<pre><code class=\"cs\">GC.Collect(); GC.WaitForPendingFinalizers();<\/code><\/pre>\n<p>These methods should be called after all work with COM objects is finished.<\/p>\n<p>If we don&#8217;t stop the processes, we can cause this situation when debugging:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/768\/988\/e92\/768988e927b0b90953e3b1517e0ec1fd.png\" width=\"491\" height=\"347\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/768\/988\/e92\/768988e927b0b90953e3b1517e0ec1fd.png\"\/><figcaption><\/figcaption><\/figure>\n<p>But even if the code fragment contained termination of processes after it finishes work, some of them would remain running after manual interruption or crash. There is no such drawback when we work with a document via Open XML.<\/p>\n<p><strong><em>Open XML:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">using (WordprocessingDocument doc =           WordprocessingDocument.Create(pathToDocFile,                                        WordprocessingDocumentType.Document,                                        true)) {   MainDocumentPart mainPart = doc.AddMainDocumentPart();   mainPart.Document = new Document();   Body body = mainPart.Document.AppendChild(new Body());   SectionProperties props = new SectionProperties();   body.AppendChild(props); }<\/code><\/pre>\n<p>Pay attention to the addition of <em>SectionProperties<\/em>, we will need them later.<\/p>\n<h4>Insert a new paragraph to Word<\/h4>\n<p><strong><em>Interop.Word<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void InsertWordText(MicrosoftWord.Document doc,                                       string text) {   MicrosoftWord.Paragraph paragraph = doc.Paragraphs.Add(Missing.Value);   paragraph.Range.Text = text;   paragraph.Range.InsertParagraphAfter(); }<\/code><\/pre>\n<p>We can also make text bold or italic with the <em>Font<\/em> parameter:<\/p>\n<pre><code class=\"cs\">paragraph.Range.Font.Bold = 1; paragraph.Range.Font.Italic = 1;<\/code><\/pre>\n<p>We can change the font size with:<\/p>\n<pre><code class=\"cs\">paragraph.Range.Font.Size = 14;<\/code><\/pre>\n<p>Text alignment is performed via <em>ParagraphFormat.Alignment<\/em>:<\/p>\n<pre><code class=\"cs\">paragraph.Range.ParagraphFormat.Alignment = MicrosoftWord.WdParagraphAlignment                                                         .wdAlignParagraphCenter;<\/code><\/pre>\n<p><strong><em>Open XML:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void AddText(WordprocessingDocument doc, string text) {   MainDocumentPart mainPart = doc.MainDocumentPart;   Body body = mainPart.Document.Body;   Paragraph paragraph = body.AppendChild(new Paragraph());    Run run = paragraph.AppendChild(new Run());   run.AppendChild(new Text(text));   run.PrependChild(new RunProperties()); }<\/code><\/pre>\n<p>With Open XML we can make text bold or italic this way:<\/p>\n<pre><code class=\"cs\">run.RunProperties.AddChild(new Bold()); run.RunProperties.AddChild(new Italic());<\/code><\/pre>\n<p>Changing the font size is a bit unintuitive, but it corresponds with the general logic of working with Open XML:<\/p>\n<pre><code class=\"cs\">run.RunProperties.AddChild(new FontSize(){ Val = \"14\"});<\/code><\/pre>\n<p>Text alignment:<\/p>\n<pre><code class=\"cs\">paragraph.ParagraphProperties.AddChild(new Justification()                                        {                                          Val = JustificationValues.Center                                        });<\/code><\/pre>\n<p>Don&#8217;t forget to add properties to the paragraph:<\/p>\n<pre><code class=\"cs\">paragraph.AppendChild(new ParagraphProperties());<\/code><\/pre>\n<h4>Insert header to Word<\/h4>\n<p>Let&#8217;s assume that we need to insert a header in the document. In the case of Interop.Word we only need a small addition to text insertion to get a header:<\/p>\n<p><strong><em>Interop.Word:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void InsertWordHeading1(MicrosoftWord.Document doc,                                       string headingText) {   MicrosoftWord.Paragraph paragraph = doc.Paragraphs.Add(Missing.Value);   paragraph.Range.Text = headingText;   paragraph.Range.set_Style(\"Heading 1\");   paragraph.Range.InsertParagraphAfter(); }<\/code><\/pre>\n<p>In this case, first we set the Range for writing the new text and assign it the *Heading 1 *style.<\/p>\n<p><strong><em>Open XML:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void InsertWordHeading1(WordprocessingDocument doc,                                       string headingText) {   MainDocumentPart mainPart = doc.MainDocumentPart;   Paragraph para = mainPart.Document.Body.AppendChild(new Paragraph());   Run run = para.AppendChild(new Run());   run.AppendChild(new Text(headingText));   para.ParagraphProperties = new ParagraphProperties(                                new ParagraphStyleId() { Val = \"Heading1\" }); }<\/code><\/pre>\n<p>Here everything seems similar. We add a paragraph and in the case of Open XML we set the necessary hierarchy of objects.<\/p>\n<p>However, in the case of Open XML, adding a style turns out to be insidious. Interop.Word works with a real complete document as if you ran Word and clicked create. But Open XML only works with what was created. And if you add text to a document which was created via Open XML and not via Interop.Word, it will lack styles, for example. Accordingly, there is no <em>Heading1<\/em> style in this document. We need to add it first. <\/p>\n<p>It is more convenient to add the needed style when we create the document. There are two options: to port ready-made styles from the Word document or add them manually.<\/p>\n<p>In the first case, we have to apply the needed style in the document from which the style will be taken. The porting itself requires a lot of code. Fortunately, the <a href=\"https:\/\/docs.microsoft.com\/en-us\/office\/open-xml\/how-to-replace-the-styles-parts-in-a-word-processing-document\">manual on this topic<\/a> is available in the documentation.<\/p>\n<p>Productivity Tool for Open XML will help us with the second option. To get the code needed to add the desired style, we create a blank Word document, apply the desired style to it and then &#171;feed&#187; it to the utility. Next, we use the Reflect Code button on \/word\/styles.xml in the document structure and get the implementation of the <em>GeneratePartContent<\/em> method. In this method we are looking for the implementation of the desired style and everything connected to it. This includes <em>StyleParagraphProperties<\/em>, <em>StyleRunProperties<\/em>, etc. <\/p>\n<p>For <em>Heading1<\/em> style the generated code looks like this:<\/p>\n<pre><code class=\"cs\">Style style2 = new Style() { Type = StyleValues.Paragraph,                              StyleId = \"Heading1\" }; StyleName styleName2 = new StyleName(){ Val = \"heading 1\" }; .... style2.Append(styleRunProperties1);<\/code><\/pre>\n<p>To add the ported style to the generated document, we need to create a set of Styles and add the style. Next, we need to add <em>StyleDefinitionsPart<\/em> to the document and assign the style group. It looks like this:<\/p>\n<pre><code class=\"cs\">var styles = new Styles(); styles.Append(style2); wordDocument.MainDocumentPart.AddNewPart&lt;StyleDefinitionsPart>(); wordDocument.MainDocumentPart.StyleDefinitionsPart.Styles = styles;<\/code><\/pre>\n<p>Here, we decided to use the template document option. In the future, when we need a style, we&#8217;ll just use it in the template document and work with it in code. It is easier than digging into ProductivityTool every time and copying lots of code with the declaration of the style we need.<\/p>\n<h4>Change the orientation of a page in Word<\/h4>\n<p>For our report we needed a landscape page layout.<\/p>\n<p><strong><em>Interop.Word:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">MicrosoftWord.Document wordDoc = wordApp.Documents.Add(); MicrosoftWord.Range docRange = wordDoc.Range(); docRange.PageSetup.Orientation = MicrosoftWord.WdOrientation                                               .wdOrientLandscape;<\/code><\/pre>\n<p>We get the desired Range from the document and set the landscape orientation.<\/p>\n<p><strong><em>Open XML:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">var sectionProperties = mainPart.Document                                 .Body                                 .GetFirstChild&lt;SectionProperties>(); sectionProperties.AddChild(new PageSize() {   Width = (UInt32Value)15840U,   Height = (UInt32Value)12240U,   Orient = PageOrientationValues.Landscape });<\/code><\/pre>\n<p>With Open XML, in this case everything is not as abstract as we wanted. If we initialize only the <em>Orient<\/em> field in <em>PageSize<\/em>, nothing changes. We must also change <em>Width<\/em> and <em>Height<\/em>.<\/p>\n<p>Additionally, the landscape orientation usually has different margins, so if you have requirements for them, you can fix it this way:<\/p>\n<pre><code class=\"cs\">sectionProperties.AddChild(new PageMargin() {   Top = 720,   Right = Convert.ToUInt32(1440.0),   Bottom = 360,   Left = Convert.ToUInt32(1440.0),   Header = (UInt32Value)450U,   Footer = (UInt32Value)720U,   Gutter = (UInt32Value)0U });<\/code><\/pre>\n<h4>Insert hyperlink to Word<\/h4>\n<p><strong><em>Interop.Word:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void AddHyperlinkedText(MicrosoftWord.Document doc,                                       string text,                                       string url) {   MicrosoftWord.Range wrdRng = doc.Bookmarks                                   .get_Item(\"\\\\endofdoc\")                                   .Range;   doc.Hyperlinks.Add(wrdRng, url, TextToDisplay: text); }<\/code><\/pre>\n<p>Everything is simple here: we get the desired Range and add a hyperlink. The <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.office.interop.word.hyperlinks.add?view=word-pia\">Add<\/a> method has many parameters, and we can create a more complex link.<\/p>\n<p><strong><em>Open XML:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void AddHyperlinkedText(WordprocessingDocument doc,                                       string text,                                       string url) {   MainDocumentPart mainPart = doc.MainDocumentPart;   Body body = mainPart.Document.Body;   Paragraph paragraph = body.AppendChild(new Paragraph());    var rel = mainPart.AddHyperlinkRelationship(new Uri(url), true);    Hyperlink hyperlink = new Hyperlink(new Run(                                     new RunProperties(                                       new RunStyle                                        {                                         Val = \"Hyperlink\",                                       },                                       new Underline                                       {                                         Val = UnderlineValues.Single                                       },                                       new Color                                       {                                         ThemeColor = ThemeColorValues.Hyperlink                                       }),                                       new Text                                       {                                         Text = text                                       }))                      {                       Id = rel.Id                      };    paragraph.AppendChild(hyperlink); }<\/code><\/pre>\n<p>Significant differences: we must wrap a <em>url<\/em> into <em>Uri<\/em> and connect the <em>url<\/em> with the hyperlink via <em>AddHyperlinkRelationship<\/em>. First, we create the relationship between the link, wrapped in <em>Uri<\/em> class, and the document itself. After that, when creating an object of the <em>Hyperlink<\/em> class, we assign the <em>rel.Id<\/em> value to its <em>Id<\/em> field.<\/p>\n<h4>Insert a picture to Word<\/h4>\n<p><strong><em>Interop.Word:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void InsertWordPicture(MicrosoftWord.Document doc,                                      string picturePath) {   MicrosoftWord.Range wrdRng = doc.Bookmarks.get_Item(\"\\\\endofdoc\")                                             .Range;   wrdRng.InlineShapes.AddPicture(picturePath); }<\/code><\/pre>\n<p>This example doesn&#8217;t look complicated, right? But it gets harder with Open XML. <\/p>\n<p><strong><em>Open XML:<\/em><\/strong><\/p>\n<p>To insert a picture, we need to comply with a complex hierarchy of objects with certain parameters. Good thing we have <a href=\"https:\/\/docs.microsoft.com\/en-us\/office\/open-xml\/how-to-insert-a-picture-into-a-word-processing-document\">documentation on this case<\/a>. Therefore, we skip the code needed to insert a picture in this article. Let&#8217;s analyze another moment that wasn&#8217;t mentioned in the documentation. You can notice that the size of the image was not passed anywhere in that code. Its size is set as follows:<\/p>\n<pre><code class=\"cs\">new DW.Extent() { Cx = 990000L, Cy = 792000L }<\/code><\/pre>\n<p>and here<\/p>\n<pre><code class=\"cs\">new A.Extents() { Cx = 990000L, Cy = 792000L }<\/code><\/pre>\n<p>If we use this code, it inserts a tiny image instead of a normal-sized one. The image&#8217;s real size doesn&#8217;t change in any way. But when displayed, it will be scaled to this size:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/04d\/a3b\/05a\/04da3b05a4a48e236942c1c496281784.png\" width=\"176\" height=\"110\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/04d\/a3b\/05a\/04da3b05a4a48e236942c1c496281784.png\"\/><figcaption><\/figcaption><\/figure>\n<p>That&#8217;s because the scale of the displayed image here is tied to <a href=\"https:\/\/en.wikipedia.org\/wiki\/Office_Open_XML_file_formats\">EMU<\/a> (English Metric Units).<\/p>\n<p>In order to pass the usual pixel dimensions to the image insertion method, we need the following transformation:<\/p>\n<pre><code class=\"cs\">double englishMetricUnitsPerInch = 914400; double pixelsPerInch = 96; double englishMetricUnitsPerPixel = englishMetricUnitsPerInch \/ pixelsPerInch;  double emuWidth = width * englishMetricUnitsPerPixel; double emuHeight = height * englishMetricUnitsPerPixel;<\/code><\/pre>\n<p>Here we get the number of EMUs per pixel, taking the PPI value as 96. Then we multiply the resulting value by the desired number of pixels for width and height. As a result, we get the desired width and height in EMU. And we pass them as <em>Cx<\/em> and <em>Cy<\/em> to Extent and Extents.<\/p>\n<pre><code class=\"cs\">Cx = (Int64Value)emuWidth, Cy = (Int64Value)emuHeight<\/code><\/pre>\n<h4>Insert a table to Word<\/h4>\n<p><strong><em>Interop.Word:<\/em><\/strong><\/p>\n<p>Table generation via Interop.Word is quite straightforward. Let&#8217;s analyze an example of how we can insert a table from a square matrix of strings.<\/p>\n<pre><code class=\"cs\">public static void InsertWordTable(MicrosoftWord.Document doc,                                    string[,] table) {   MicrosoftWord.Table oTable;   MicrosoftWord.Range wrdRng = doc.Bookmarks                                   .get_Item(\"\\\\endofdoc\")                                   .Range;    int rowCount = table.GetLength(0);   int columnCount = table.GetLength(1);    oTable = doc.Tables.Add(wrdRng,                     rowCount,                     columnCount,                     DefaultTableBehavior: MicrosoftWord.WdDefaultTableBehavior                                                        .wdWord9TableBehavior,                     AutoFitBehavior: MicrosoftWord.WdAutoFitBehavior                                                   .wdAutoFitWindow);    for (int i = 0; i &lt; rowCount; i++)     for (int j = 0; j &lt; columnCount; j++)       oTable.Cell(i + 1, j + 1).Range.Text = table[i,j]; }<\/code><\/pre>\n<p>Parameters of the <em>Add<\/em> method &#8212; <em>DefaultTableBehavior<\/em> and <em>AutoFitBehavior<\/em> &#8212; are responsible for auto-fitting cell size to contents. They are assigned the values of the <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.office.interop.word.wddefaulttablebehavior?view=word-pia\">WdDefaultTableBehavior<\/a> and <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.office.interop.word.wdautofitbehavior?view=word-pia\">WdAutoFitBehavior<\/a> enumerators, respectively. The Add method itself creates a table in the document with the desired parameters.<\/p>\n<p>The style to the table can be applied the following way:<\/p>\n<pre><code class=\"cs\">oTable.set_Style(\"Grid Table 4 - Accent 1\");<\/code><\/pre>\n<p>Also, if you want a nice highlight of the first column, you can assign <em>true<\/em> to the <em>oTable.ApplyStyleFirstColumn<\/em> field.<\/p>\n<p>*oTable.Range.ParagraphFormat.SpaceAfter *controls the spacing between paragraphs. For a compact table display you can use<\/p>\n<pre><code class=\"cs\">oTable.Range.ParagraphFormat.SpaceAfter = 0;<\/code><\/pre>\n<p>You can also assign text style to rows or columns:<\/p>\n<pre><code class=\"cs\">oTable.Rows[1].Range.Font.Bold = 1; oTable.Column[1].Range.Font.Italic = 1;<\/code><\/pre>\n<p>Using these features, you can get this table:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/599\/46c\/623\/59946c62316c895c4f11ddbfff2c57e1.png\" width=\"580\" height=\"44\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/599\/46c\/623\/59946c62316c895c4f11ddbfff2c57e1.png\"\/><figcaption><\/figcaption><\/figure>\n<p><strong><em>Open XML:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void InsertWordTable(WordprocessingDocument doc,                                    string[,] table) {   DocumentFormat.OpenXml.Wordprocessing.Table dTable =     new DocumentFormat.OpenXml.Wordprocessing.Table();    TableProperties props = new TableProperties();    dTable.AppendChild&lt;TableProperties>(props);    for (int i = 0; i &lt; table.GetLength(0); i++)   {     var tr = new TableRow();      for (int j = 0; j &lt; table.GetLength(1); j++)     {       var tc = new TableCell();       tc.Append(new Paragraph(new Run(new Text(table[i, j]))));        tc.Append(new TableCellProperties());        tr.Append(tc);     }     dTable.Append(tr);   }   doc.MainDocumentPart.Document.Body.Append(dTable); }<\/code><\/pre>\n<p>When creating a table from scratch with Open XML, remember that no cells or rows exist when you start entering data. We must create them first, following the inner hierarchy.<\/p>\n<p>Therefore, when traversing the matrix, we create <em>TableRow<\/em> for each element. Then for every new element in the line we create <em>TableCell<\/em>, where we add the new <em>Paragraph<\/em>, <em>Run<\/em> and <em>Text<\/em> with the corresponding matrix value. It&#8217;s also better to add <em>TableCellProperties<\/em> immediately. Otherwise later, when working with the table, you&#8217;ll get a <em>System.NullReferenceException<\/em> when trying to add a property to the cell.<\/p>\n<p>If we don&#8217;t set any style or Borders in <em>TableProperties<\/em>, the table will look like this:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/aac\/aed\/53c\/aacaed53ccfae51683ccd169b7a1cbf7.png\" width=\"169\" height=\"76\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/aac\/aed\/53c\/aacaed53ccfae51683ccd169b7a1cbf7.png\"\/><figcaption><\/figcaption><\/figure>\n<p><em>TableBorders<\/em> form the table borders.<\/p>\n<pre><code class=\"cs\">var borderValues = new EnumValue&lt;BorderValues>(BorderValues.Single); var tableBorders = new TableBorders(                       new TopBorder { Val = borderValues, Size = 4 },                      new BottomBorder {  Val = borderValues,  Size = 4 },                      new LeftBorder { Val = borderValues, Size = 4 },                      new RightBorder { Val = borderValues, Size = 4 },                      new InsideHorizontalBorder { Val= borderValues, Size = 4 },                      new InsideVerticalBorder { Val= borderValues, Size = 4 }));<\/code><\/pre>\n<p>The <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/documentformat.openxml.vml.wordprocessing.bordervalues?view=openxml-2.8.1\">BorderValues<\/a> enumeration here sets the border style. <\/p>\n<p>We need to add* TableBorders to TableProperties *via<\/p>\n<pre><code class=\"cs\">props.Append(tableBorders);<\/code><\/pre>\n<p>If we set a style to the table, we may not set the table borders. But don&#8217;t forget to add the style to the document.<\/p>\n<p>The style is set quite simply:<\/p>\n<pre><code class=\"cs\">TableStyle tableStyle = new TableStyle()                         {                           Val = \"GridTable4-Accent5\"                         };<\/code><\/pre>\n<p>It should be added to <em>TableProperties<\/em> the same way as the borders:<\/p>\n<pre><code class=\"cs\">props.Append(tableStyle);<\/code><\/pre>\n<p>In order to extend the table for the entire page width, use <em>TableWidth<\/em> set as follows:<\/p>\n<pre><code class=\"cs\">var tableWidth = new TableWidth()                  {                    Width = \"5000\",                    Type = TableWidthUnitValues.Pct                  };<\/code><\/pre>\n<p>We didn&#8217;t take the 5000 value out of nowhere. We set the type of the width unit via <em>TableWidthUnitValues.Pct<\/em>\u2013the width unit of 1\/50 of the percent or 0,02%. As a result, 5000 Pct is 100% of the page width.<\/p>\n<p>This parameter is added to <em>TableProperties<\/em> in a similar way:<\/p>\n<pre><code class=\"cs\">props.Append(tableWidth);<\/code><\/pre>\n<p>Note: <em>TableProperties<\/em> should be added to the table before the data itself. This way <em>TableProperties<\/em> will work correctly. You can add it after other objects, but it this case you should use<\/p>\n<pre><code class=\"cs\">dTable.PrependChild&lt;TableProperties>(props);<\/code><\/pre>\n<h4>Table coloring<\/h4>\n<p>To form our report, we needed to color the cells in some document tables.<\/p>\n<p><strong><em>Interop.Word:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">oTable.Cell(i, j).Range.Shading.BackgroundPatternColor = MicrosoftWord.WdColor                                                                     .wdColorRed;<\/code><\/pre>\n<p>where <em>oTable<\/em> is the previously made table, <em>i<\/em> and <em>j<\/em> are indexes of the desired cell. <\/p>\n<p>The assigned value is the <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.office.interop.word.wdcolor?view=word-pia\">WdColor<\/a> enumeration. <\/p>\n<p><strong><em>Open XML:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">tc.Append(new TableCellProperties(             new Shading { Fill = \"FF0000\" }));<\/code><\/pre>\n<p>where <em>tc<\/em> is <em>TableCell<\/em> that we work with. The <em>Fill<\/em> field is assigned a string with the Hex color value.<\/p>\n<h4>Insert page break to Word<\/h4>\n<p>In our case, the report is generated step-by-step. That&#8217;s why we had to insert the page break after the last added text.<\/p>\n<p><strong><em>Interop.Word:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void InsertWordBreak(MicrosoftWord.Document doc) {   MicrosoftWord.Range wrdRng = doc.Bookmarks.get_Item(\"\\\\endofdoc\")                                             .Range;   wrdRng.InsertBreak(); }<\/code><\/pre>\n<p><strong><em>Open XML:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void InsertWordBreak(WordprocessingDocument doc) {   MainDocumentPart mainPart = doc.MainDocumentPart;   mainPart.Document.Body.InsertAfter(new Paragraph(                                        new Run(                                          new Break()                                          {                                             Type = BreakValues.Page                                          })),                                      mainPart.Document.Body.LastChild); }<\/code><\/pre>\n<p>The break type in changed via the <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/documentformat.openxml.wordprocessing.breakvalues?view=openxml-2.8.1\">BreakValues<\/a> enumeration.<\/p>\n<h4>Insert a footer\/header to Word<\/h4>\n<p>We also needed footers\/headers in the document.<\/p>\n<p><strong><em>Interop.Word:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void InsertWordFooter(   MicrosoftWord.Document doc,   string headerText) {   MicrosoftWord.Range headerRange = doc.Sections                                  .Last                                  .Headers[MicrosoftWord.WdHeaderFooterIndex                                                        .wdHeaderFooterPrimary]                                  .Range;    headerRange.Fields.Add(headerRange, MicrosoftWord.WdFieldType.wdFieldPage);   headerRange.Text = headerText; }<\/code><\/pre>\n<p>We can change the text parameters &#8212; size, font, color, etc &#8212; via *headerRange.Font. *As you might guess, <em>headerRange.ParagraphFormat.Alignment<\/em> sets the text alignment. This field takes the values of <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.office.interop.word.wdparagraphalignment?view=word-pia\">WdParagraphAlignment<\/a>.<\/p>\n<p><strong><em>Open XML:<\/em><\/strong><\/p>\n<p>The problem here is that the footer\/header itself is stored in a separate .xml file. That&#8217;s why we need to link footer\/header to the document content via <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/documentformat.openxml.wordprocessing.sectionproperties?view=openxml-2.8.1\">SectionProperties<\/a>.<\/p>\n<pre><code class=\"cs\">static void InsertWordHeader(HeaderPart part,                              string headerText) {   MainDocumentPart mainPart = doc.MainDocumentPart;    if (mainPart.HeaderParts.Any())     return;    HeaderPart headerPart = mainPart.AddNewPart&lt;HeaderPart>();    string headerPartId = mainPart.GetIdOfPart(headerPart);    part.Header = new Header(                   new Paragraph(                     new ParagraphProperties(                       new ParagraphStyleId() { Val = \"Header\" }),                       new Run( new Text() { Text = headerText })));    var sectionProperties = mainPart.Document                                   .Body                                   .GetFirstChild&lt;SectionProperties>();   sectionProperties.PrependChild&lt;HeaderReference>(new HeaderReference()                                                   {                                                     Id = headerPartId                                                   }); }<\/code><\/pre>\n<p>If you want the text to be overwritten with a new one when calling the header addition method, then instead of <\/p>\n<pre><code class=\"cs\">if (mainPart.HeaderParts.Any())   return;<\/code><\/pre>\n<p>you can use<\/p>\n<pre><code class=\"cs\">mainDocumentPart.DeleteParts(mainDocumentPart.HeaderParts);<\/code><\/pre>\n<p>For the footer, we need to pass <em>mainDocumentPart.FooterParts<\/em>.<\/p>\n<h3>Conclusion<\/h3>\n<p>We compiled all these methods for Open XML SDK into a class library for internal use. Creating Word documents became even more convenient than it was with Word Interop API.<\/p>\n<p>Here one might ask a question: is there any ready-made libraries based on Open XML SDK for simplified work with the documents? The answer is definitely yes. But unfortunately, developers rapidly stop the maintenance of such libraries. Stories of creating such projects are the same. Developers start working with Word and realize the inconvenience of the existing infrastructure. They modify it and post some libraries on GitHub. Even if we&#8217;re lucky to find a fresh version of such a library, someone might have adapted it for certain project&#8217;s objectives. It will probably be inconvenient to use in your project. Plus, there&#8217;s a risk of being left with a library that is not maintained.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/573860\/\"> https:\/\/habr.com\/ru\/articles\/573860\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>With the .NET5 release further development of some projects was questionable due to the complexity of porting. One can abandon small outdated libraries or find a replacement. But it&#8217;s hard to throw away Microsoft.Office.Interop.Word.dll. Microsoft doesn&#8217;t plan to add compatibility with .NET Core\/5+, so in this article we focus on creating Word files with Open XML SDK.<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<h3>Introduction<\/h3>\n<p>Office Open XML aka OpenXML or OOXML, is an XML-based format for office documents. It includes text files, spreadsheets, presentations, as well as diagrams, shapes, and other graphic material. In June 2014 Microsoft released Open XML SDK source code on <a href=\"https:\/\/github.com\/OfficeDev\/Open-XML-SDK\">GitHub<\/a> to work with this format.<\/p>\n<p>This library has impressive advantages:<\/p>\n<ul>\n<li>\n<p>compatible with .NET 5+,<\/p>\n<\/li>\n<li>\n<p>does not require Microsoft Office installation,<\/p>\n<\/li>\n<li>\n<p>high-speed operation,<\/p>\n<\/li>\n<li>\n<p>open source code.<\/p>\n<\/li>\n<\/ul>\n<p>The disadvantages include:<\/p>\n<ul>\n<li>\n<p>complex API,<\/p>\n<\/li>\n<li>\n<p>scant documentation.<\/p>\n<\/li>\n<\/ul>\n<p>The disadvantages definitely complement each other. Actually, it was the reason to create this article.<\/p>\n<p>But the open source code was the big plus. If we had COM libraries&#8217; open source code, the developer community would help with porting to .NET Core\/5+. Besides attracting third-party developers, open source code allows everyone to find and fix errors and vulnerabilities. Or at least to report them. The quality of open libraries is <a href=\"https:\/\/pvs-studio.com\/en\/blog\/posts\/cpp\/0762\/\">crucial<\/a> for all projects that can use them. For example, we conducted a small <a href=\"https:\/\/pvs-studio.com\/en\/blog\/posts\/csharp\/0777\/\">audit<\/a> of the Open XML SDK code when we first got acquainted with this library.<\/p>\n<h3>Office developers&#8217; pain<\/h3>\n<p>Third-party developers created a lot of software for Office products. These are plugins for Word, Excel, Outlook. Many companies implemented themselves convenient plugins and report generators in Word format. On July 3, 2021 a terrible thing happened &#8212; Microsoft closed all the tickets on .NET 5+ in VSTO \/ COM support with a comment from its representatives:<\/p>\n<blockquote>\n<p>&#8230;The VSTO\/COM Add-Ins platform is very important to Microsoft, and we plan to continue to support it in Office with .NET Framework 4.8 as the last major version&#8230;VSTO\/COM Add-Ins cannot be created with .NET Core and .NET 5+. This is because .NET Core\/.NET 5+ cannot work together with .NET Framework in the same process and may lead to add-in load failures. Microsoft will not be updating VSTO or the COM Add-in platform to use .NET Core or .NET 5+&#8230; <\/p>\n<\/blockquote>\n<p>According to their information, .NET 5+ support is not expected. Here&#8217;s one of the discussions that hasn&#8217;t stopped after the announcement: &#171;<a href=\"https:\/\/developercommunity.visualstudio.com\/t\/Please-port-Visual-Studio-Tools-For-Offi\/757925\">Please port Visual Studio Tools For Office (VSTO) to .NET 5\/7, to enable VSTO add-in development in C# in .Net 5\/7<\/a>&#171;.<\/p>\n<p>The plugin developers were offered to switch to Office JavaScript API. This is a completely different language, where the API does not allow you to do even the smallest part of what it could do. However, one may switch to Open XML SDK (<a href=\"https:\/\/www.nuget.org\/packages\/Open-XML-SDK\">nuget<\/a>) library to create documents from C# code.<\/p>\n<h3>The basics<\/h3>\n<p>Before we analyze examples, we should understand what these two libraries work with in general and what is the difference between their approaches.<\/p>\n<p>A Word file is a set of boxed xml documents. All elements are structured by tags.<\/p>\n<p>For example, a paragraph inside a document will look as follows:<\/p>\n<pre><code class=\"xml\">&lt;w:p w:rsidR=\"007D2247\" w:rsidRDefault=\"009A4B44\"          xmlns:w=\"http:\/\/schemas.openxmlformats.org\/wordprocessingml\/2006\/main\">   &lt;w:r>     &lt;w:t>test&lt;\/w:t>   &lt;\/w:r>   &lt;w:bookmarkStart w:name=\"_GoBack\" w:id=\"0\" \/>   &lt;w:bookmarkEnd w:id=\"0\" \/> &lt;\/w:p><\/code><\/pre>\n<p>The Interop.Word assembly provides higher abstraction level than this structure and often works with a part of the document &#8212; Range. However, Open XML SDK follows the path of reflecting the document&#8217;s inner structure in the code itself.  <em>&lt;w:p><\/em> paragraphs, *&lt;w:t> *sections of text and everything else become objects in code. If you don&#8217;t create the body of the document, the paragraph and other mandatory &#171;parents&#187;, then there will be no place to insert text.<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<p>The screenshot shows the inner structure of the main file for a Word document &#8212; document.xml. The file contains the content of the document itself.<\/p>\n<p>The screenshot was taken in the Open XML SDK 2.5 Productivity Tool which is necessary for working with Open XML. By the time of writing this article, Microsoft removed the utility from its website. And a link to <a href=\"https:\/\/github.com\/rmboggs\/DocxToSource\">DocxToSource<\/a> was added to the <a href=\"https:\/\/github.com\/OfficeDev\/Open-XML-SDK\">Open-XML-SDK<\/a> repository, which should be a replacement for the outdated Productivity Tool. However, this replacement is still a prototype, so for now it&#8217;s better to find the good old Productivity Tool. The old utility allows you to view the structure of the document, get acquainted with the autogenerated code. <\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>It also allows you to compare two different documents &#8212; both code for their creation and inner structure. <\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<h3>Examples<\/h3>\n<p>In the entire article, we use this alias for Interop.Word for the sake of readability:<\/p>\n<pre><code class=\"cs\">using MicrosoftWord = Microsoft.Office.Interop.Word;<\/code><\/pre>\n<p>Also, we will call the Open XML SDK simply Open XML.<\/p>\n<h4>How to create Word document<\/h4>\n<p><strong><em>Interop.Word:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">MicrosoftWord.Application wordApp = new MicrosoftWord.Application(); MicrosoftWord.Document wordDoc = wordApp.Documents.Add(); MicrosoftWord.Range docRange = wordDoc.Range(); .... \/\/ here we work with the document, if necessary wordDoc.SaveAs2(pathToDocFile); wordApp.Quit();<\/code><\/pre>\n<p>Everything is quite simple here, but there are also some pitfalls. When working with Interop we interact not just with some object in memory, but with a COM object. That&#8217;s why we have to terminate all the processes after the program finishes working. This problem has been raised more than once on Stack Overflow (<a href=\"https:\/\/stackoverflow.com\/questions\/158706\/how-do-i-properly-clean-up-excel-interop-objects?page=1&amp;tab=active\">1<\/a>,<a href=\"https:\/\/stackoverflow.com\/questions\/25134024\/clean-up-excel-interop-objects-with-idisposable\/25135685\">2<\/a>) and people proposed various solutions to it.<\/p>\n<p>There is a solution with <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.runtime.interopservices.marshal?view=net-5.0\">Marshal Class<\/a>, which is a part of InteropServices.<\/p>\n<pre><code class=\"cs\">finally {   if (Marshal.IsComObject(wordDoc))     try     {       Marshal.FinalReleaseComObject(wordDoc);     }     catch { throw; }     if (Marshal.IsComObject(wordApp))     try     {       Marshal.FinalReleaseComObject(wordApp);     }     catch { throw; } }<\/code><\/pre>\n<p>However, in this case we may miss some processes.<\/p>\n<p>A more reliable option with a <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.gc?view=net-5.0\">GC<\/a> call:<\/p>\n<pre><code class=\"cs\">GC.Collect(); GC.WaitForPendingFinalizers();<\/code><\/pre>\n<p>These methods should be called after all work with COM objects is finished.<\/p>\n<p>If we don&#8217;t stop the processes, we can cause this situation when debugging:<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<p>But even if the code fragment contained termination of processes after it finishes work, some of them would remain running after manual interruption or crash. There is no such drawback when we work with a document via Open XML.<\/p>\n<p><strong><em>Open XML:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">using (WordprocessingDocument doc =           WordprocessingDocument.Create(pathToDocFile,                                        WordprocessingDocumentType.Document,                                        true)) {   MainDocumentPart mainPart = doc.AddMainDocumentPart();   mainPart.Document = new Document();   Body body = mainPart.Document.AppendChild(new Body());   SectionProperties props = new SectionProperties();   body.AppendChild(props); }<\/code><\/pre>\n<p>Pay attention to the addition of <em>SectionProperties<\/em>, we will need them later.<\/p>\n<h4>Insert a new paragraph to Word<\/h4>\n<p><strong><em>Interop.Word<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void InsertWordText(MicrosoftWord.Document doc,                                       string text) {   MicrosoftWord.Paragraph paragraph = doc.Paragraphs.Add(Missing.Value);   paragraph.Range.Text = text;   paragraph.Range.InsertParagraphAfter(); }<\/code><\/pre>\n<p>We can also make text bold or italic with the <em>Font<\/em> parameter:<\/p>\n<pre><code class=\"cs\">paragraph.Range.Font.Bold = 1; paragraph.Range.Font.Italic = 1;<\/code><\/pre>\n<p>We can change the font size with:<\/p>\n<pre><code class=\"cs\">paragraph.Range.Font.Size = 14;<\/code><\/pre>\n<p>Text alignment is performed via <em>ParagraphFormat.Alignment<\/em>:<\/p>\n<pre><code class=\"cs\">paragraph.Range.ParagraphFormat.Alignment = MicrosoftWord.WdParagraphAlignment                                                         .wdAlignParagraphCenter;<\/code><\/pre>\n<p><strong><em>Open XML:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void AddText(WordprocessingDocument doc, string text) {   MainDocumentPart mainPart = doc.MainDocumentPart;   Body body = mainPart.Document.Body;   Paragraph paragraph = body.AppendChild(new Paragraph());    Run run = paragraph.AppendChild(new Run());   run.AppendChild(new Text(text));   run.PrependChild(new RunProperties()); }<\/code><\/pre>\n<p>With Open XML we can make text bold or italic this way:<\/p>\n<pre><code class=\"cs\">run.RunProperties.AddChild(new Bold()); run.RunProperties.AddChild(new Italic());<\/code><\/pre>\n<p>Changing the font size is a bit unintuitive, but it corresponds with the general logic of working with Open XML:<\/p>\n<pre><code class=\"cs\">run.RunProperties.AddChild(new FontSize(){ Val = \"14\"});<\/code><\/pre>\n<p>Text alignment:<\/p>\n<pre><code class=\"cs\">paragraph.ParagraphProperties.AddChild(new Justification()                                        {                                          Val = JustificationValues.Center                                        });<\/code><\/pre>\n<p>Don&#8217;t forget to add properties to the paragraph:<\/p>\n<pre><code class=\"cs\">paragraph.AppendChild(new ParagraphProperties());<\/code><\/pre>\n<h4>Insert header to Word<\/h4>\n<p>Let&#8217;s assume that we need to insert a header in the document. In the case of Interop.Word we only need a small addition to text insertion to get a header:<\/p>\n<p><strong><em>Interop.Word:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void InsertWordHeading1(MicrosoftWord.Document doc,                                       string headingText) {   MicrosoftWord.Paragraph paragraph = doc.Paragraphs.Add(Missing.Value);   paragraph.Range.Text = headingText;   paragraph.Range.set_Style(\"Heading 1\");   paragraph.Range.InsertParagraphAfter(); }<\/code><\/pre>\n<p>In this case, first we set the Range for writing the new text and assign it the *Heading 1 *style.<\/p>\n<p><strong><em>Open XML:<\/em><\/strong><\/p>\n<pre><code class=\"cs\">public static void InsertWordHeading1(WordprocessingDocument doc,                                       string headingText) {   MainDocumentPart mainPart = doc.MainDocumentPart;   Paragraph para = mainPart.Document.Body.AppendChild(new Paragraph());   Run run = para.AppendChild(new Run());   run.AppendChild(new Text(headingText));   para.ParagraphProperties = new ParagraphProperties(                                new ParagraphStyleId() { Val = \"Heading1\" }); }<\/code><\/pre>\n<p>Here everything seems similar. We add a paragraph and in the case of Open XML we set the necessary hierarchy of objects.<\/p>\n<p>However, in the case of Open XML, adding a style turns out to be insidious. Interop.Word works with a real complete document as if you ran Word and clicked create. But Open XML<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-401305","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/401305","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=401305"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/401305\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=401305"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=401305"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=401305"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}