Table of Contents

Find and replace

Search and replace text in the body, headers, footers, notes and text boxes, with plain text or regular expressions. Matches are found even when Word has split a word into several formatting runs internally.

The code

using DocWright;
using DocWright.Dom;
using DocWright.Dom.Editing;

using FileStream input = File.OpenRead("letterhead.docx");
using WordDocument document = converter.Load(input);

// Plain text: every occurrence, in the body, headers, footers and text boxes.
int count = document.ReplaceText("DocWright Letterhead", "Acme Components Ltd.");
Console.WriteLine($"Replaced {count} occurrence(s) of the heading.");

// A regular expression with a group reference: "paragraph 3 of eight" -> "paragraph 3/8".
int numbered = document.ReplaceRegex(@"paragraph (\d+) of eight", "paragraph $1/8");
Console.WriteLine($"Rewrote {numbered} numbered paragraph(s).");

// Find without replacing. Groups holds the capture groups only: Groups[0] is group 1.
IReadOnlyList<TextMatch> tags = document.FindRegex(@"\[MIX-(\d+)\]");
Console.WriteLine($"Found {tags.Count} tag(s); the first is {tags[0].Value} (number {tags[0].Groups[0]}).");

using (FileStream pdf = File.Create("letterhead-edited.pdf"))
{
    converter.Convert(document, pdf);
}

Output

Replaced 1 occurrence(s) of the heading.
Rewrote 8 numbered paragraph(s).
Found 12 tag(s); the first is [MIX-000] (number 000).
letterhead-edited.pdf
The letterhead with its heading replaced by Acme Components Ltd.

The methods

All four are extension methods on any DomNode (namespace DocWright.Dom.Editing). Call them on a document, a section, a table or a single cell, and they touch nothing outside it.

Method Returns
ReplaceText(find, replacement, FindOptions?, ReplaceOptions?) Number of replacements.
ReplaceRegex(pattern, replacement, …) Number of replacements. The replacement can use $1, $2 group references.
FindText(text, FindOptions?) A list of TextMatch.
FindRegex(pattern, FindOptions?) A list of TextMatch.

A TextMatch has the matched Value, its Index in the paragraph's text, the Paragraph, and Groups.

Warning

TextMatch.Groups holds the capture groups only, starting with group 1. Groups[0] is the first capture group, not the whole match as in System.Text.RegularExpressions. Use Value for the whole match.

What a match can and can't span

  • ✅ Any number of runs with different formatting, inside one paragraph.
  • ✅ Bookmark markers, which have no width.
  • ❌ A field, hyperlink boundary, line or page break, image, shape, note reference or content control. Each of those has a meaning of its own, and text spliced across one would change it.
  • ❌ Deleted tracked-change text, which isn't searchable. Inserted text is, and a replacement keeps the revision mark of the run where the match started.

Formatting of the replacement

By default the replacement takes the formatting of the first matched run. Set ReplaceOptions.Formatting to a CharacterFormat to apply your own. Adjacent runs with identical formatting are merged afterwards, so repeated edits don't fragment the document.

Regular expressions are time-limited

A pattern that backtracks without end could hang your service. FindOptions.RegexTimeout (one second by default) stops it. Treat user-supplied patterns as untrusted input.

Templates

To replace {{Name}}-style markers from a dictionary or JSON, use ReplacePlaceholders. It's built on the same engine. See the JSON template tutorial.