Pacem.Extensions.OfficeWord 0.8.10-fibonacci

This is a prerelease version of Pacem.Extensions.OfficeWord.
There is a newer version of this package available.
See the version list below for details.
dotnet add package Pacem.Extensions.OfficeWord --version 0.8.10-fibonacci
                    
NuGet\Install-Package Pacem.Extensions.OfficeWord -Version 0.8.10-fibonacci
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Pacem.Extensions.OfficeWord" Version="0.8.10-fibonacci" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Pacem.Extensions.OfficeWord" Version="0.8.10-fibonacci" />
                    
Directory.Packages.props
<PackageReference Include="Pacem.Extensions.OfficeWord" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Pacem.Extensions.OfficeWord --version 0.8.10-fibonacci
                    
#r "nuget: Pacem.Extensions.OfficeWord, 0.8.10-fibonacci"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Pacem.Extensions.OfficeWord@0.8.10-fibonacci
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Pacem.Extensions.OfficeWord&version=0.8.10-fibonacci&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Pacem.Extensions.OfficeWord&version=0.8.10-fibonacci&prerelease
                    
Install as a Cake Tool

Office Word Extensions

About

Pacem.Extensions.OfficeWord provides a lightweight set of utilities that ease the handling of .docx (OpenXml) documents.

The library is mainly about retrieving and replacing content inside a Word document.
The convenient part is that content gets found despite being messily positioned inside the (Open)Xml nodes of the document itself.

The following paragraphs exemplify the library utilization given common use cases.

Getting started

Inject the IOfficeWordProcessor singleton service into the DI container.

Currently, there's no other [than the dependency injection] way to reach the instance of the processor service.

Like this:

// Program.cs
services.AddPacemOfficeWordProcessor();

Then use it in your services, like this:

namespace Pacem.OpenXml.OfficeWord;

public class MyController : ControllerBase
{
    private readonly IOfficeWordProcessor _word;

    public MyController(IOfficeWordProcessor word)
    {
        _word = word;
    }

    // [...]
}

Placeholder replacement

Straight match and replace

Goal: find the occurrences of a given string and replace them with a precise value.
Write a placeholder string (e.g. [_my_placeholder_]) in your document file (e.g. file1.docx) and replace it programmatically with a new string (e.g. "Hello, World!").

string root = AppContext.BaseDirectory;
string source = Path.Combine(root, "file1.docx");
string target = Path.Combine(root, "file1-replaced.docx");
_word.Open(source)
    .Find("[_my_placeholder_]")
    .ReplaceWith("Hello, World!")
    .Flush(target);

Conditional replacement

Goal: find consistently formatted placeholders and replace them 1-by-1.
You can gather all the occurrences of your document that obey to a regular expression and then apply a replacement given the actual match value, like in the following example.

string root = AppContext.BaseDirectory;
string source = Path.Combine(root, "file2.docx");
string target = Path.Combine(root, "file2-replaced.docx");
_word.Open(source)
    // placeholders are strings like "{_content_}", "{_stuff_}", "{_something_}", etc.
    .Find(new Regex((@"\{_[\w]+_\}"))
    .ReplaceWith(match => match switch
    {
        "{_Heading1_}" => "Replaced Heading",
        "{_Body1_}" => "Replaced Body",
        "{_Footer1_}" => "Replaced Footer",
        _ => match // ...else return the input match itself.
    })
    .Flush(target);

Table databinding

Goal: fill a table with data.
Prepare a table in your Word document with 2 rows:

  • first row is the heading row,
  • last row is the templated row.

Remarks: You can include none or multiple heading rows, as well as multiple foot rows.

Example:

Prop 1 Prop 2 Prop 3
[_prop1_] [_prop2_] Property3

Datasource for the Word table can be shaped using a set of objects whose type has proper metadata set. Like this:

class Poco {
    
    [Placeholder("[_prop1_]", /* format */"Price {0:C4}")]
    public double Property1 { get; set; }
    
    [Placeholder("[_prop2_]")]
    public string Property2 { get; set; }
    
    [IgnorePlaceholder()]
    public string Property3 { get; set; }
}

When PlaceholderAttribute is not provided in association with a property, then the property name itself will be used as a placeholder.

string root = AppContext.BaseDirectory;
string source = Path.Combine(root, "file2.docx");
string target = Path.Combine(root, "file2-databound.docx");

_word.Open(source)
    .Tables()
    // the datasource keys define the tables to be databound,
    // based on placeholders' matching!
    .DataBind(
        new CultureInfo("it-IT"),
        new Poco2 { Property1 = 1D, Property2 = "1st item's property 2", /* ignored */Property3 = "1st item's property 3" },
        new Poco2 { Property1 = 2D, Property2 = "2nd item's property 2", /* ignored */Property3 = "2nd item's property 3" },
        new Poco2 { Property1 = 3D, Property2 = "3rd item's property 2", /* ignored */Property3 = "3rd item's property 3" }
    )
    .Flush(target);

Result:

Prop 1 Prop 2 Prop 3
Price 1,000 € 1st item's property 2 Property3
Price 2,000 € 2nd item's property 2 Property3
Price 3,000 € 3rd item's property 2 Property3

Picture replacement

Goal: replace an existing image with another.
Prepare a document having named pictures.

To name a picture use Office Word Selection Pane and type a label (e.g. MyPic).

string root = AppContext.BaseDirectory;
string pic = Path.Combine(root, "pic3.jpg");
string source = Path.Combine(root, "file3.docx");
string target = Path.Combine(root, "file3-replaced.docx");

// load the picture file
using var reader = File.OpenRead(pic);
byte[] data = new byte[reader.Length];
reader.Read(data, 0, data.Length);
// define the desired picture proportion, usually by providing its dimensions
// (new picture will be adapted and 'contained' into the original box)
var proportion = Extent.FromSize(1368,642);

_word.Open(source)
    .Pictures("MyPic")
    .ReplaceWith(data, KnownMimeTypes.ImageJpeg, proportion)
    .Flush(target);

Picture enumeration

Goal: enumerate (and extract data from) the pictures embedded into the document.

string root = AppContext.BaseDirectory;
string source = Path.Combine(root, "file4.docx");

using (var docx = _word.Open(source))
{
    docx.Pictures(/* either include or not a selection filter */)
        .Read(picture =>
        {
            // `picture` is an instance of
            // PictureMatch which gathers
            // useful - readonly - properties
            // about the whole picture object
            Console.WriteLine($"Picture {picture.Id} found at page {picture.PageIndex+1}.");
        });

    // remember to explicitly close
    // the document when it is not 
    // meant to be flushed, eventually.
}

Picture removal

Goal: remove all the pictures that match provided criteria.

string root = AppContext.BaseDirectory;
string source = Path.Combine(root, "file5.docx");
string target = Path.Combine(root, "file5-replaced.docx");
string selectionFilter = "MyPics";

_word.Open(source)
.Pictures(selectionFilter)
.Remove(picture =>
{
    var pos = picture.Position;
    if (pos is null)
    {
        return false;
    }

    // remove only floating images
    // whose anchor is the `page`
    return pos.Value.Y.RelativeFrom == RelativeFrom.Page
        && pos.Value.X.RelativeFrom == RelativeFrom.Page;
})
.Flush(target);

Page cloning

🆕 v0.8.10

Goal: use a single page or a range of pages as a template and clone it n-times while manipulating content at every step.

string root = AppContext.BaseDirectory;
string source = Path.Combine(root, "file6.docx");
string target = Path.Combine(root, "file6-replaced.docx");
string selectionFilter = "MyPics";

// number of copies of the selected page range to create
int copies = 2;

_word.Open(source)
    // select pages (last one only in this specific case)
    .Pages(pageCount => 
        // picks last two pages
        (pageCount - 2)..(pageCount - 1)
    )
    // clone 'em/it (twice in this case)
    .Clone(copies, page =>
    {
        // ranges from 0 to copies-1
        int iteration = page.CloneIndex;

        // source/template page index
        int originalPageIndex = page.PageIndex;

        // external convenient calls
        var (picturePayload, extent) = BuildPicture(originalPageIndex, iteration);
        var datasource = BuildDatasource(originalPageIndex, iteration);

        // page actions use the same
        // syntax of the above document actions
        page.
            // text replacement
            .Find("[_Placeholder_]")
            .ReplaceWith("Iteration #"+ iteration.ToString())
            // picture replacement
            .Pictures("MyPic")
            .ReplaceWith(picturePayload, "image/jpeg", extent)
            // table databinding
            .Tables()
            .DataBind(datasource);
    })
    .Flush(target);

Please notice that the range of pages used as a template will be removed, eventually.

Section changes

This current version does not handle section (cfr. <w:sectPr>) changes.

Section changes handling are planned in future releases.

Breaking Changes from previous versions

  • (0.7.2) Refactored PictureMatch class:
    • ReferenceId corresponds to the previous Id (tracks the "embed id");
    • Id gets the unique id of the object inside the document.
  • (0.7.1) Deprecating table databinding using arrays of string dictionaries.
  • (0.7.0-cumae) Replacing pictures needs to provide relevant mime-type.

Bonus tip: Managed/supported mime-types are exposed by KnownMimeTypes.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Pacem.Extensions.OfficeWord:

Package Downloads
Pacem.Extensions.OfficeWord.Pdf

Utilities to convert .docx files into PDFs.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.9.0-dirac 67 8/2/2025
0.9.0-cayley 117 7/31/2025
0.9.0-boyle 114 7/31/2025
0.9.0-abel 138 7/17/2025
0.8.11 165 5/27/2025
0.8.10 166 5/7/2025
0.8.10-fourier 103 5/2/2025
0.8.10-fibonacci 112 2/7/2025
0.8.10-fermat 102 2/3/2025
0.8.10-euler 109 2/3/2025
0.8.10-euclid 113 2/1/2025
0.8.10-dalembert 114 1/31/2025
0.8.10-cauchy 102 1/26/2025
0.8.10-cantor 121 12/18/2024
0.8.10-caccioppoli 108 12/6/2024
0.8.10-bourbaki 105 12/6/2024
0.8.10-abel 195 4/22/2024
0.8.6 194 3/17/2024
0.8.6-abel 116 3/14/2024
0.8.5 165 3/13/2024
0.8.4 147 3/12/2024
0.8.3 145 3/12/2024
0.8.2 164 3/11/2024
0.8.1 152 3/11/2024
0.8.0 155 2/4/2024
0.8.0-akkad 204 12/8/2023
0.7.4 497 11/20/2023
0.7.3 159 11/17/2023
0.7.2 172 11/16/2023
0.7.2-atlantis 114 11/16/2023
0.7.1 153 11/15/2023
0.7.0-cumae 187 8/26/2023
0.7.0-corcyra 166 8/17/2023
0.7.0-byzantium 172 8/16/2023
0.7.0-bithynia 172 8/4/2023