Table of Contents

Class DocxEditor

Namespace
DocToolkit
Assembly
DocToolkit.Docx.dll

Creates, reads and edits Word (.docx) documents.

public static class DocxEditor
Inheritance
DocxEditor
Inherited Members

Methods

AddBookmark(byte[], int, string)

Adds a bookmark named name to the paragraph at paragraphIndex, and returns the updated document.

public static byte[] AddBookmark(byte[] docx, int paragraphIndex, string name)

Parameters

docx byte[]

The document to edit. It is not modified.

paragraphIndex int

The 0-based paragraph to mark.

name string

The bookmark name.

Returns

byte[]

Remarks

paragraphIndex is 0-based, matching ReadTable(byte[], int) and everything else this class indexes.

Exceptions

ArgumentNullException

docx or name is null.

ArgumentException

docx is empty, or name is blank.

ArgumentOutOfRangeException

paragraphIndex is negative, or at or beyond the paragraph count.

DocumentConversionException

The document could not be opened or written.

AddBookmarkAsync(Stream, int, string, Stream, CancellationToken)

Adds a bookmark named name to the paragraph at paragraphIndex, and returns the updated document.

public static Task AddBookmarkAsync(Stream source, int paragraphIndex, string name, Stream destination, CancellationToken ct = default)

Parameters

source Stream

The stream the document is read from.

paragraphIndex int

The 0-based paragraph to mark.

name string

The bookmark name.

destination Stream

The stream the updated document is written to.

ct CancellationToken

Cancels the read, the edit and the write.

Returns

Task

Remarks

source is read to its end and destination is written; neither is disposed, closed or sought.

Exceptions

ArgumentNullException

An argument is null.

ArgumentException

source is not readable or held no bytes, name is blank, or destination is not writable.

ArgumentOutOfRangeException

paragraphIndex is negative, or at or beyond the paragraph count.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be opened or written.

AddEndnote(byte[], string, string)

Adds an endnote at every occurrence of placeholder, inline, across the document body — see AddFootnote(byte[], string, string) for exactly what is matched and how each occurrence is handled. The only difference is where the note ends up: the document's endnotes, not its footnotes.

Endnote content added this way survives a DocxToPdfConverter conversion — measured, not inferred: the endnote text is present in the rendered PDF alongside the rest of the document. So DocxToPdfPreflight carries no Endnote finding; there is nothing for it to report.

public static byte[] AddEndnote(byte[] docx, string placeholder, string endnoteText)

Parameters

docx byte[]
placeholder string
endnoteText string

Returns

byte[]

Exceptions

ArgumentNullException

Any of the three required arguments is null.

ArgumentException

docx is empty, or placeholder is blank.

DocumentConversionException

The package could not be edited, or placeholder does not appear in the body — a call matching nothing is a bug in the call or the template, not a no-op.

AddEndnoteAsync(Stream, string, string, Stream, CancellationToken)

Reads a .docx from source, adds an endnote at every occurrence of placeholder, and writes the result to destination — see AddEndnote(byte[], string, string) for exactly what is matched.

source is read to its end and destination is written; neither is disposed, closed or sought, and neither has to be seekable.

public static Task AddEndnoteAsync(Stream source, string placeholder, string endnoteText, Stream destination, CancellationToken ct = default)

Parameters

source Stream

The stream the .docx package is read from.

placeholder string

The literal placeholder text, braces included.

endnoteText string

The endnote's own text.

destination Stream

The stream the edited .docx package is written to.

ct CancellationToken

Cancels the read, the edit and the write.

Returns

Task

Exceptions

ArgumentNullException

Any argument is null.

ArgumentException

source is not readable or held no bytes, destination is not writable, or placeholder is blank.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The package could not be edited, or the placeholder was not found.

AddEndnoteAsync(string, string, string, string, CancellationToken)

Reads a .docx from inputPath, adds an endnote at every occurrence of placeholder, and writes the result to outputPath — see AddEndnote(byte[], string, string) for exactly what is matched. The two paths may be the same file: the updated bytes are computed in full before outputPath is opened.

public static Task AddEndnoteAsync(string inputPath, string outputPath, string placeholder, string endnoteText, CancellationToken ct = default)

Parameters

inputPath string

The .docx to read.

outputPath string

Where to write the result. Overwritten if it exists.

placeholder string

The literal placeholder text, braces included.

endnoteText string

The endnote's own text.

ct CancellationToken

Cancels the read and the write.

Returns

Task

Exceptions

ArgumentNullException

A path or placeholder is null.

ArgumentException

A path is blank, the file at inputPath is empty, or placeholder is blank.

FileNotFoundException

inputPath does not exist.

DirectoryNotFoundException

inputPath's or outputPath's directory does not exist.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The package could not be edited, or the placeholder was not found.

AddFootnote(byte[], string, string)

Adds a footnote at every occurrence of placeholder, inline, across the document body.

Only the matched text goes: text sharing a run with the placeholder keeps its place and its formatting, so See the note{{note}} here. becomes See the note, the footnote reference, then here. Each occurrence gets its own footnote entry, all carrying footnoteText.

placeholder is the literal text including braces, like ReplaceImage(byte[], string, byte[], double?, double?). Only the document body is searched — headers, footers, and content already inside another footnote or endnote are not.

A document built with this method has its footnote content survive a DocxToPdfConverter conversion — measured; see DocxToPdfPreflight's own remarks. DocxToPdfPreflight does not report it, because there is nothing lost to report.

public static byte[] AddFootnote(byte[] docx, string placeholder, string footnoteText)

Parameters

docx byte[]
placeholder string
footnoteText string

Returns

byte[]

Exceptions

ArgumentNullException

Any of the three required arguments is null.

ArgumentException

docx is empty, or placeholder is blank.

DocumentConversionException

The package could not be edited, or placeholder does not appear in the body — a call matching nothing is a bug in the call or the template, not a no-op.

AddFootnoteAsync(Stream, string, string, Stream, CancellationToken)

Reads a .docx from source, adds a footnote at every occurrence of placeholder, and writes the result to destination — see AddFootnote(byte[], string, string) for exactly what is matched.

source is read to its end and destination is written; neither is disposed, closed or sought, and neither has to be seekable.

public static Task AddFootnoteAsync(Stream source, string placeholder, string footnoteText, Stream destination, CancellationToken ct = default)

Parameters

source Stream

The stream the .docx package is read from.

placeholder string

The literal placeholder text, braces included.

footnoteText string

The footnote's own text.

destination Stream

The stream the edited .docx package is written to.

ct CancellationToken

Cancels the read, the edit and the write.

Returns

Task

Exceptions

ArgumentNullException

Any argument is null.

ArgumentException

source is not readable or held no bytes, destination is not writable, or placeholder is blank.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The package could not be edited, or the placeholder was not found.

AddFootnoteAsync(string, string, string, string, CancellationToken)

Reads a .docx from inputPath, adds a footnote at every occurrence of placeholder, and writes the result to outputPath — see AddFootnote(byte[], string, string) for exactly what is matched. The two paths may be the same file: the updated bytes are computed in full before outputPath is opened.

public static Task AddFootnoteAsync(string inputPath, string outputPath, string placeholder, string footnoteText, CancellationToken ct = default)

Parameters

inputPath string

The .docx to read.

outputPath string

Where to write the result. Overwritten if it exists.

placeholder string

The literal placeholder text, braces included.

footnoteText string

The footnote's own text.

ct CancellationToken

Cancels the read and the write.

Returns

Task

Exceptions

ArgumentNullException

A path or placeholder is null.

ArgumentException

A path is blank, the file at inputPath is empty, or placeholder is blank.

FileNotFoundException

inputPath does not exist.

DirectoryNotFoundException

inputPath's or outputPath's directory does not exist.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The package could not be edited, or the placeholder was not found.

AddTableOfContents(byte[], string, int, int)

Replaces the paragraph containing only placeholder with a table of contents spanning heading levels minLevel through maxLevel.

public static byte[] AddTableOfContents(byte[] docx, string placeholder, int minLevel = 1, int maxLevel = 3)

Parameters

docx byte[]

The .docx to edit. It is not modified.

placeholder string

The literal placeholder text, braces included. Must be the entire text of the paragraph it appears in — see the remarks.

minLevel int

The shallowest heading level to include. 1-9.

maxLevel int

The deepest heading level to include. 1-9, and at least minLevel.

Returns

byte[]

Remarks

The placeholder paragraph must contain nothing else. A footnote or an image can be spliced inline, preserving whatever text shares its run — a table of contents cannot: its content is whole paragraphs, and replacing a paragraph has no way to keep a neighbour's text. A placeholder paragraph carrying anything besides the placeholder is refused rather than silently trimmed — other text, an inline image, a text box and a tab alike, and so is a paragraph whose own w:pPr holds a w:sectPr, since that is a section break carrying its section's paper size, margins and page numbering rather than formatting the paragraph could lose harmlessly. A bookmark is the one exception: it is preserved, wrapping the inserted content, rather than discarded with the paragraph.

Only a top-level paragraph is matched. A placeholder that lives only inside a text box counts as absent, for the same reason every other edit in this class stays out of w:txbxContent: a table of contents spliced into a text box is not what any caller writing {{toc}} meant.

The field is written dirty on purpose, not populated with real heading text. Measured against the pinned OfficeIMO.Word 3.2.6: the field this produces carries w:dirty="true", and the document gains w:updateFields="true" in its settings — both regardless of anything else. Word recomputes a dirty field before display, and this library's own DocxToPdfConverter recomputes it live rather than trusting the cache — so a caller sees the real table of contents either way, even though the file itself never contains one as plain, pre-rendered text.

Exceptions

ArgumentNullException

docx or placeholder is null.

ArgumentException

docx is empty, placeholder is blank, or minLevel is greater than maxLevel.

ArgumentOutOfRangeException

minLevel or maxLevel is outside 1-9.

DocumentConversionException

The package could not be edited; no paragraph containing only the placeholder was found; the paragraph holding it also holds content other than plain text; that paragraph's w:pPr carries a w:sectPr, so replacing it would discard a section break; or more than one paragraph's text exactly matches the placeholder, since AddTableOfContents replaces a single paragraph and refuses to guess which one was meant.

AddTableOfContentsAsync(Stream, string, Stream, int, int, CancellationToken)

Reads a .docx from source, replaces the paragraph containing only placeholder with a table of contents, and writes the result to destination — see AddTableOfContents(byte[], string, int, int) for exactly what is matched.

source is read to its end and destination is written; neither is disposed, closed or sought, and neither has to be seekable.

public static Task AddTableOfContentsAsync(Stream source, string placeholder, Stream destination, int minLevel = 1, int maxLevel = 3, CancellationToken ct = default)

Parameters

source Stream

The stream the .docx package is read from.

placeholder string

The literal placeholder text, braces included.

destination Stream

The stream the edited .docx package is written to.

minLevel int

The shallowest heading level to include. 1-9.

maxLevel int

The deepest heading level to include. 1-9, and at least minLevel.

ct CancellationToken

Cancels the read, the edit and the write.

Returns

Task

Exceptions

ArgumentNullException

Any argument is null.

ArgumentException

source is not readable or held no bytes, destination is not writable, placeholder is blank, or minLevel is greater than maxLevel.

ArgumentOutOfRangeException

minLevel or maxLevel is outside 1-9.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The package could not be edited; no matching paragraph was found; the paragraph holding the placeholder also holds content other than plain text; that paragraph's w:pPr carries a w:sectPr, so replacing it would discard a section break; or more than one paragraph's text exactly matches the placeholder, since AddTableOfContents replaces a single paragraph and refuses to guess which one was meant.

AddTableOfContentsAsync(string, string, string, int, int, CancellationToken)

Reads a .docx from inputPath, replaces the paragraph containing only placeholder with a table of contents, and writes the result to outputPath — see AddTableOfContents(byte[], string, int, int) for exactly what is matched. The two paths may be the same file: the updated bytes are computed in full before outputPath is opened.

public static Task AddTableOfContentsAsync(string inputPath, string outputPath, string placeholder, int minLevel = 1, int maxLevel = 3, CancellationToken ct = default)

Parameters

inputPath string

The .docx to read.

outputPath string

Where to write the result. Overwritten if it exists.

placeholder string

The literal placeholder text, braces included.

minLevel int

The shallowest heading level to include. 1-9.

maxLevel int

The deepest heading level to include. 1-9, and at least minLevel.

ct CancellationToken

Cancels the read and the write.

Returns

Task

Exceptions

ArgumentNullException

A path or placeholder is null.

ArgumentException

A path is blank, the file at inputPath is empty, placeholder is blank, or minLevel is greater than maxLevel.

ArgumentOutOfRangeException

minLevel or maxLevel is outside 1-9.

FileNotFoundException

inputPath does not exist.

DirectoryNotFoundException

inputPath's or outputPath's directory does not exist.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The package could not be edited; no matching paragraph was found; the paragraph holding the placeholder also holds content other than plain text; that paragraph's w:pPr carries a w:sectPr, so replacing it would discard a section break; or more than one paragraph's text exactly matches the placeholder, since AddTableOfContents replaces a single paragraph and refuses to guess which one was meant.

AddWatermark(byte[], string)

Stamps text across the page as a watermark, and returns the updated document.

public static byte[] AddWatermark(byte[] docx, string text)

Parameters

docx byte[]

The document to stamp. It is not modified.

text string

The watermark text.

Returns

byte[]

Remarks

Applied to every section the document reports, which is not always one per w:sectPr in the body. Measured: a document produced by Merge(IEnumerable<byte[]>) carries two w:sectPr elements but reports a single section, so it receives one watermark rather than one per merged document. Pages belonging to the later part may therefore be unmarked — see the package README's Known Limitations.

This is not the PDF watermarking the backlog declined. That would have put a third PDF library inside PdfEditor; this is OfficeIMO.Word, already the package behind every other DocxEditor operation.

Exceptions

ArgumentNullException

docx or text is null.

ArgumentException

docx is empty, or text is blank.

DocumentConversionException

The document could not be opened or written.

AddWatermarkAsync(Stream, string, Stream, CancellationToken)

Stamps text across the page as a watermark, and returns the updated document.

public static Task AddWatermarkAsync(Stream source, string text, Stream destination, CancellationToken ct = default)

Parameters

source Stream

The stream the document is read from.

text string

The watermark text.

destination Stream

The stream the stamped document is written to.

ct CancellationToken

Cancels the read, the edit and the write.

Returns

Task

Remarks

Applied to every section, for the reason AddWatermark(byte[], string) records. source is read to its end and destination is written; neither is disposed, closed or sought.

Exceptions

ArgumentNullException

An argument is null.

ArgumentException

source is not readable or held no bytes, text is blank, or destination is not writable.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be opened or written.

Create(IEnumerable<DocxBlock>)

Creates a document from blocks.

A DOCX can also be produced by converting HTML with HtmlToDocxConverter. This exists for the case where the content comes from data rather than from markup: there is no HTML to escape, so a value containing < cannot corrupt the document's structure, and the same blocks produce the same CONTENT on every machine — nothing here consults the current culture. Not the same BYTES: the OpenXml SDK mints fresh relationship ids per package, so two calls with identical blocks in the same process differ. Do not build a cache key, a content hash or a golden-file test on the bytes.

An empty sequence is valid and produces a valid empty document.

public static byte[] Create(IEnumerable<DocxBlock> blocks)

Parameters

blocks IEnumerable<DocxBlock>

The content, written in order.

Returns

byte[]

Examples

byte[] docx = DocxEditor.Create(new[]
{
    DocxBlock.Heading("Quarterly Report", 1),
    DocxBlock.Paragraph("Revenue was up 12%."),
    DocxBlock.Table(
        new[] { "Region", "Total" },
        new[] { new object?[] { "North", 1200 } }),
});

Remarks

The document is laid out on A4. Use Create(IEnumerable<DocxBlock>, PageSetup) for anything else.

Exceptions

ArgumentNullException

blocks is null.

ArgumentException

An element of blocks is null.

DocumentConversionException

The document could not be built.

Create(IEnumerable<DocxBlock>, PageSetup)

Builds a document from blocks, laid out on page. See Create(IEnumerable<DocxBlock>) for the block semantics — this overload applies identical logic and differs only in the paper.

public static byte[] Create(IEnumerable<DocxBlock> blocks, PageSetup page)

Parameters

blocks IEnumerable<DocxBlock>

The content, written in order.

page PageSetup

The page size, orientation and margins.

Returns

byte[]

Exceptions

ArgumentNullException

blocks or page is null.

ArgumentException

An element of blocks is null.

DocumentConversionException

The document could not be built.

CreateAsync(IEnumerable<DocxBlock>, PageSetup, Stream, CancellationToken)

Builds a document from blocks, laid out on page, and writes it to destination. See Create(IEnumerable<DocxBlock>, PageSetup) for the semantics.

destination is written, from its current position, and is not disposed, closed or sought — it belongs to the caller, and may be write-only and forward-only, such as an HTTP response body.

public static Task CreateAsync(IEnumerable<DocxBlock> blocks, PageSetup page, Stream destination, CancellationToken ct = default)

Parameters

blocks IEnumerable<DocxBlock>

The content, written in order.

page PageSetup

The page size, orientation and margins.

destination Stream

The stream the document is written to.

ct CancellationToken

Cancels the build and the write to destination.

Returns

Task

Exceptions

ArgumentNullException

Any argument is null.

ArgumentException

An element of blocks is null, or destination is not writable.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be built or written.

CreateAsync(IEnumerable<DocxBlock>, Stream, CancellationToken)

Builds a document from blocks and writes it to destination. See Create(IEnumerable<DocxBlock>) for the block semantics — this overload applies identical logic, writing to destination instead of returning an array.

destination is written, from its current position, and is not disposed, closed or sought — it belongs to the caller, and may be write-only and forward-only, such as an HTTP response body.

public static Task CreateAsync(IEnumerable<DocxBlock> blocks, Stream destination, CancellationToken ct = default)

Parameters

blocks IEnumerable<DocxBlock>

The content, written in order.

destination Stream

The stream the document is written to.

ct CancellationToken

Cancels the build and the write to destination.

Returns

Task

Remarks

The document is laid out on A4. Use CreateAsync(IEnumerable<DocxBlock>, PageSetup, Stream, CancellationToken) for anything else.

Exceptions

ArgumentNullException

blocks or destination is null.

ArgumentException

An element of blocks is null, or destination is not writable.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be built or written.

CreateToFileAsync(IEnumerable<DocxBlock>, PageSetup, string, CancellationToken)

Builds a document from blocks, laid out on page, and writes it to outputPath. See Create(IEnumerable<DocxBlock>, PageSetup) for the semantics.

The document is built completely before the output is opened, so a failed build cannot truncate a file that was already there.

public static Task CreateToFileAsync(IEnumerable<DocxBlock> blocks, PageSetup page, string outputPath, CancellationToken ct = default)

Parameters

blocks IEnumerable<DocxBlock>

The content, written in order.

page PageSetup

The page size, orientation and margins.

outputPath string

Where to write the document. Overwritten if it exists.

ct CancellationToken

Cancels the write to outputPath.

Returns

Task

Exceptions

ArgumentNullException

Any argument is null.

ArgumentException

outputPath is blank, or an element of blocks is null.

DirectoryNotFoundException

outputPath's directory does not exist.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be built.

CreateToFileAsync(IEnumerable<DocxBlock>, string, CancellationToken)

Builds a document from blocks and writes it to outputPath. See Create(IEnumerable<DocxBlock>) for the block semantics.

Named CreateToFileAsync rather than a third CreateAsync overload, matching WorkbookEditor.CreateToFileAsync: the distinct name keeps which kind of destination a call writes to visible at the call site, rather than resting on the argument type alone.

The document is built completely before the output is opened. That ordering is the reason a failed build cannot truncate a file that was already there, and it is pinned by FilePathOverloadTests rather than left as a comment — it survives only as long as nobody rewrites this into a streaming write.

public static Task CreateToFileAsync(IEnumerable<DocxBlock> blocks, string outputPath, CancellationToken ct = default)

Parameters

blocks IEnumerable<DocxBlock>

The content, written in order.

outputPath string

Where to write the document. Overwritten if it exists.

ct CancellationToken

Cancels the write to outputPath.

Returns

Task

Remarks

The document is laid out on A4. Use CreateToFileAsync(IEnumerable<DocxBlock>, PageSetup, string, CancellationToken) for anything else.

Exceptions

ArgumentNullException

blocks or outputPath is null.

ArgumentException

outputPath is blank, or an element of blocks is null.

DirectoryNotFoundException

outputPath's directory does not exist.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be built.

ExtractText(byte[])

Returns the plain text of the document body. Headers, footers, footnotes and endnotes are not included — call ExtractText(byte[], bool) for those.

public static string ExtractText(byte[] docx)

Parameters

docx byte[]

Returns

string

Exceptions

ArgumentNullException

docx is null.

ArgumentException

docx is empty.

DocumentConversionException

The package could not be opened or read.

ExtractText(byte[], bool)

Returns the plain text of the document. When includeHeadersAndFooters is true the body text is followed by each header part and then each footer part, separated by newlines; footnotes and endnotes are never included.

public static string ExtractText(byte[] docx, bool includeHeadersAndFooters)

Parameters

docx byte[]
includeHeadersAndFooters bool

Returns

string

Exceptions

ArgumentNullException

docx is null.

ArgumentException

docx is empty.

DocumentConversionException

The package could not be opened or read.

ExtractTextAsync(Stream, bool, CancellationToken)

Reads a .docx from source and returns its plain text. When includeHeadersAndFooters is true the body text is followed by each header part and then each footer part; footnotes and endnotes are never included. source is read to its end and is neither disposed, closed nor sought.

public static Task<string> ExtractTextAsync(Stream source, bool includeHeadersAndFooters, CancellationToken ct = default)

Parameters

source Stream
includeHeadersAndFooters bool
ct CancellationToken

Returns

Task<string>

Exceptions

ArgumentNullException

source is null.

ArgumentException

source is not readable or held no bytes.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The package could not be opened or read.

ExtractTextAsync(Stream, CancellationToken)

Reads a .docx from source and returns the plain text of its body. Headers, footers, footnotes and endnotes are not included — call ExtractTextAsync(Stream, bool, CancellationToken) for those. source is read to its end and is neither disposed, closed nor sought.

public static Task<string> ExtractTextAsync(Stream source, CancellationToken ct = default)

Parameters

source Stream
ct CancellationToken

Returns

Task<string>

Exceptions

ArgumentNullException

source is null.

ArgumentException

source is not readable or held no bytes.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The package could not be opened or read.

ExtractTextAsync(string, bool, CancellationToken)

Reads a .docx from path and returns its text, optionally including headers and footers.

public static Task<string> ExtractTextAsync(string path, bool includeHeadersAndFooters, CancellationToken ct = default)

Parameters

path string

The .docx to read.

includeHeadersAndFooters bool

Whether to include header and footer text.

ct CancellationToken

Cancels the read.

Returns

Task<string>

The document's text.

Exceptions

ArgumentNullException

path is null.

ArgumentException

path is blank, or the file it names is empty.

FileNotFoundException

path does not exist.

DirectoryNotFoundException

path's directory does not exist.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be processed.

ExtractTextAsync(string, CancellationToken)

Reads a .docx from path and returns its body text.

public static Task<string> ExtractTextAsync(string path, CancellationToken ct = default)

Parameters

path string

The .docx to read.

ct CancellationToken

Cancels the read.

Returns

Task<string>

The document's body text.

Exceptions

ArgumentNullException

path is null.

ArgumentException

path is blank, or the file it names is empty.

FileNotFoundException

path does not exist.

DirectoryNotFoundException

path's directory does not exist.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be processed.

FillRows(byte[], string, IEnumerable<IReadOnlyDictionary<string, string>>)

Expands a table row once per record, so a template can render a variable-length list such as invoice line items.

A template row wrapped in a content control — or sitting in a wrapped table — is found and expanded like any other, and its clones stay inside the control the author put it in.

A row is a template row when one of its cells contains a placeholder prefixed with collection{{item.Desc}} when collection is item. Each record deep-clones that row, so every clone keeps the template's run formatting, cell shading and borders, and substitution runs through the same splicer ReplaceText(byte[], IReadOnlyDictionary<string, string>) uses — a placeholder split across runs is still replaced, and a hyperlink in a cell is left intact.

Keys are bare field names (Desc), not full placeholders — unlike ReplaceText(byte[], IReadOnlyDictionary<string, string>), whose keys are the placeholder text including braces. collection is already an argument, so repeating it in every key of every record would duplicate it many times over.

A placeholder with no matching key resolves to empty rather than staying visible. Placeholders for other prefixes are untouched, so a second call fills a second table. An empty rows removes the template row, and removes the whole table when that row was its only one — an empty frame left on the page reads worse than rendering nothing.

Compose with ReplaceText(byte[], IReadOnlyDictionary<string, string>) for document-level scalars, expanding rows first.

public static byte[] FillRows(byte[] docx, string collection, IEnumerable<IReadOnlyDictionary<string, string>> rows)

Parameters

docx byte[]
collection string
rows IEnumerable<IReadOnlyDictionary<string, string>>

Returns

byte[]

Exceptions

ArgumentNullException

Any argument is null.

ArgumentException

docx is empty, or collection is blank.

DocumentConversionException

The package could not be opened or edited, or no template row was found for collection — a mismatch between the call and the template is a bug in one of them, not a no-op.

FillRowsAsync(Stream, string, IEnumerable<IReadOnlyDictionary<string, string>>, Stream, CancellationToken)

Reads a .docx from source, expands the template row once per record, and writes the result to destination. See FillRows(byte[], string, IEnumerable<IReadOnlyDictionary<string, string>>) for what counts as a template row and how formatting survives — this overload applies the identical logic via streams instead of a byte array.

source is read to its end and destination is written; neither is disposed, closed or sought, and neither has to be seekable, so both may be sockets, files or HTTP message bodies.

public static Task FillRowsAsync(Stream source, string collection, IEnumerable<IReadOnlyDictionary<string, string>> rows, Stream destination, CancellationToken ct = default)

Parameters

source Stream

The stream the .docx package is read from.

collection string

The placeholder prefix marking the template row, without braces.

rows IEnumerable<IReadOnlyDictionary<string, string>>

One dictionary per record, keyed by bare field name.

destination Stream

The stream the edited .docx package is written to.

ct CancellationToken

Cancels the read, the edit and the write.

Returns

Task

Exceptions

ArgumentNullException

Any argument is null.

ArgumentException

source is not readable or held no bytes, destination is not writable, or collection is blank.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The package could not be opened or edited, or no template row was found.

FillRowsAsync(string, string, string, IEnumerable<IReadOnlyDictionary<string, string>>, CancellationToken)

Reads a .docx from inputPath, expands one table row per record, and writes the result to outputPath. The two may be the same file: the updated bytes are computed in full before outputPath is opened, so a document that fails to process — cannot be read, or cannot be edited — leaves outputPath untouched. That guarantee does not extend to a failure during the write itself: a full disk, a cancellation, or the process dying mid-write can still leave a partial file, so in-place editing of an irreplaceable document is not crash-safe.

public static Task FillRowsAsync(string inputPath, string outputPath, string collection, IEnumerable<IReadOnlyDictionary<string, string>> rows, CancellationToken ct = default)

Parameters

inputPath string

The .docx to read.

outputPath string

Where to write the result. Overwritten if it exists.

collection string

The collection name used in the row's placeholders.

rows IEnumerable<IReadOnlyDictionary<string, string>>

One dictionary per record.

ct CancellationToken

Cancels the read and the write.

Returns

Task

Exceptions

ArgumentNullException

A path, collection or rows is null.

ArgumentException

A path is blank, or the file at inputPath is empty.

FileNotFoundException

inputPath does not exist.

DirectoryNotFoundException

inputPath's or outputPath's directory does not exist.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be processed.

InspectSignatures(byte[])

Inspects docx for digital signatures — whether it carries one, how many, and who claims to have signed it. Does not validate anything cryptographically; see ValidateSignatures(byte[], DocumentSignatureValidationOptions?).

public static DocumentSignatureInfo InspectSignatures(byte[] docx)

Parameters

docx byte[]

Returns

DocumentSignatureInfo

Exceptions

ArgumentNullException

docx is null.

ArgumentException

docx is empty.

DocumentConversionException

The document could not be inspected.

InspectSignaturesAsync(Stream, CancellationToken)

Reads a .docx from source and inspects it for digital signatures — see InspectSignatures(byte[]). source is read to its end and is neither disposed, closed nor sought.

public static Task<DocumentSignatureInfo> InspectSignaturesAsync(Stream source, CancellationToken ct = default)

Parameters

source Stream
ct CancellationToken

Returns

Task<DocumentSignatureInfo>

Exceptions

ArgumentNullException

source is null.

ArgumentException

source is not readable or held no bytes.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be inspected.

IsProtected(byte[])

Whether docx is an ENCRYPTED Office document.

public static bool IsProtected(byte[] docx)

Parameters

docx byte[]

The bytes to inspect.

Returns

bool

Remarks

This is not a validity check, and a false is not a promise that anything else will succeed. It distinguishes an encrypted document from a plain one; input that is neither — an image, a PDF, a text file, random bytes — is not encrypted, so this answers false for it, while every other method on this class refuses it. Measured over real files: a JPEG and a PDF both return false here and both throw from ExtractText.

The summary used to say "that is, whether the other methods on this class will refuse it". That reads as a guard — test it, and if false, proceed — and takes the wrong branch for every input that is not a document at all. The behaviour was always right and only the sentence was wrong, which is why the fix is here and not in the code.

Reads the file signature; it does not try the password and does not need one. A plain DOCX is a ZIP package, an encrypted one is a compound file, and the two are distinguishable from their first eight bytes.

Exceptions

ArgumentNullException

docx is null.

IsProtectedAsync(Stream, CancellationToken)

Whether docx is an ENCRYPTED Office document.

public static Task<bool> IsProtectedAsync(Stream source, CancellationToken ct = default)

Parameters

source Stream

The stream the document is read from.

ct CancellationToken

Cancels the read.

Returns

Task<bool>

Remarks

source is read to its end and is neither disposed, closed nor sought. Unlike IsProtected(byte[]), which answers false for an empty array, an empty source is rejected — every Stream overload in this package treats a source that held no bytes as a caller error rather than as content.

Exceptions

ArgumentNullException

source is null.

ArgumentException

source is not readable or held no bytes.

OperationCanceledException

ct was cancelled.

Merge(IEnumerable<byte[]>)

Joins docx end to end, in order, into one document — the DOCX counterpart of PdfEditor.Merge, and the way to put a DocxMailMerge.MergeBatch result back into a single file.

public static byte[] Merge(IEnumerable<byte[]> docx)

Parameters

docx IEnumerable<byte[]>

The documents to join, in order. At least one.

Returns

byte[]

A new document; none of the inputs is modified.

Remarks

Each document keeps its own page setup, as its own section. Measured: merging a portrait document with a landscape one produces a body carrying two w:sectPr elements, each with its own orientation and page size, and the last child of the body is still a w:sectPr — so Word does not report the file as damaged.

Where two documents define the SAME style id differently, the FIRST definition wins and the later content adopts it. Also measured, and it is silent: merging a document whose Heading1 is red with one whose Heading1 is blue produces a single Heading1 — the red one — and the second document's headings render red. No error is raised and no text is lost; only the appearance changes. If that matters, give the documents distinct style ids before merging, or merge documents that share a template.

Exceptions

ArgumentNullException

docx, or an element of it, is null.

ArgumentException

docx is empty, or an element holds no bytes.

DocumentConversionException

A document could not be opened or the result could not be written.

MergeAsync(IEnumerable<Stream>, Stream, CancellationToken)

Joins docx end to end, in order, into one document — the DOCX counterpart of PdfEditor.Merge, and the way to put a DocxMailMerge.MergeBatch result back into a single file.

public static Task MergeAsync(IEnumerable<Stream> sources, Stream destination, CancellationToken ct = default)

Parameters

sources IEnumerable<Stream>

The streams the documents are read from, in order. At least one.

destination Stream

The stream the joined document is written to.

ct CancellationToken

Cancels the reads, the merge and the write.

Returns

Task

Remarks

Every stream in sources is read to its end and destination is written; none is disposed, closed or sought, and none has to be seekable.

Exceptions

ArgumentNullException

sources or destination is null.

ArgumentException

sources is empty, one of them is not readable or held no bytes, or destination is not writable.

OperationCanceledException

ct was cancelled.

DocumentConversionException

A document could not be opened or the result could not be written.

Protect(byte[], string)

A copy of docx encrypted with password, so it cannot be opened without one.

public static byte[] Protect(byte[] docx, string password)

Parameters

docx byte[]

The document to encrypt.

password string

The password required to open the result. May not be empty.

Returns

byte[]

Remarks

This is file encryption, not document protection. Office offers both under the same menu and they are not the same thing: this scrambles the whole file, so nothing can be read without the password. The other kind - a flag asking a reader not to edit - is a request rather than a lock, and is deliberately not exposed here.

The result is not a DOCX package any more. An encrypted Office document is a compound file with the package sealed inside it, so every other method on this class refuses it - call Unprotect(byte[], string) first. That refusal is the honest behaviour: those methods could not read the content even if they tried.

Exceptions

ArgumentNullException

docx or password is null.

ArgumentException

docx is empty, or password is empty.

DocumentConversionException

The document could not be read or encrypted.

ProtectAsync(Stream, Stream, string, CancellationToken)

Reads a document from source and writes the encrypted copy to destination.

Neither stream is disposed, closed or sought.

public static Task ProtectAsync(Stream source, Stream destination, string password, CancellationToken ct = default)

Parameters

source Stream

The stream the document is read from.

destination Stream

The stream the encrypted document is written to.

password string

The password required to open the result. May not be empty.

ct CancellationToken

Cancels the read and the write.

Returns

Task

Remarks

This is file encryption, not document protection. Office offers both under the same menu and they are not the same thing: this scrambles the whole file, so nothing can be read without the password. The other kind - a flag asking a reader not to edit - is a request rather than a lock, and is deliberately not exposed here.

The result is not a DOCX package any more. An encrypted Office document is a compound file with the package sealed inside it, so every other method on this class refuses it - call Unprotect(byte[], string) first. That refusal is the honest behaviour: those methods could not read the content even if they tried.

ReadBookmarks(byte[])

Every bookmark name in the document, in the order the document declares them.

public static IReadOnlyList<string> ReadBookmarks(byte[] docx)

Parameters

docx byte[]

The document to read.

Returns

IReadOnlyList<string>

Remarks

Names only. A bookmark's position matters to Word and to AddTableOfContents, but there is no public position type here to return one against, and inventing one is a larger decision than reading the names.

Exceptions

ArgumentNullException

docx is null.

ArgumentException

docx is empty.

DocumentConversionException

The document could not be opened or read.

ReadBookmarksAsync(Stream, CancellationToken)

Every bookmark name in the document, in the order the document declares them.

public static Task<IReadOnlyList<string>> ReadBookmarksAsync(Stream source, CancellationToken ct = default)

Parameters

source Stream

The stream the document is read from.

ct CancellationToken

Cancels the read.

Returns

Task<IReadOnlyList<string>>

Remarks

source is read to its end and is neither disposed, closed nor sought.

Exceptions

ArgumentNullException

source is null.

ArgumentException

source is not readable or held no bytes.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be opened or read.

ReadMetadata(byte[])

The document properties docx carries.

public static DocumentMetadata ReadMetadata(byte[] docx)

Parameters

docx byte[]

Returns

DocumentMetadata

Exceptions

ArgumentNullException

docx is null.

ArgumentException

docx is empty.

DocumentConversionException

The document could not be read.

ReadMetadataAsync(Stream, CancellationToken)

The document properties docx carries.

public static Task<DocumentMetadata> ReadMetadataAsync(Stream source, CancellationToken ct = default)

Parameters

source Stream

The stream the document is read from.

ct CancellationToken

Cancels the read.

Returns

Task<DocumentMetadata>

Remarks

source is read to its end and is neither disposed, closed nor sought.

Exceptions

ArgumentNullException

source is null.

ArgumentException

source is not readable or held no bytes.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be read.

ReadTable(byte[], int)

The table at index, as rows of cell text.

public static IReadOnlyList<IReadOnlyList<string>> ReadTable(byte[] docx, int index)

Parameters

docx byte[]

The .docx content to read.

index int

0-based, indexing what TableCount(byte[]) reports — deliberately unlike PdfEditor.ExtractPages, whose firstPage is 1-based because that is how a reader numbers pages. A table has no such reader-facing numbering, and the rows and cells this returns are 0-based, so a 1-based selector here would be the odd one out.

Returns

IReadOnlyList<IReadOnlyList<string>>

Examples

int count = DocxEditor.TableCount(docx);
IReadOnlyList<IReadOnlyList<string>> table = DocxEditor.ReadTable(docx, 0);

Remarks

Cell text is produced the same way ExtractText(byte[]) produces it, so a cell holding several paragraphs is separated by newlines and a nested table keeps its own structure.

Rows are returned with the shape they have. A horizontally merged cell means a row genuinely holds fewer cells than its neighbours; padding the grid to a rectangle would invent cells that are not in the document.

Content controls are transparent, at every level. A table, a row or a cell wrapped in a w:sdt is read like any other. A wrapped row used to vanish from an otherwise correct table, and a wrapped cell used to shift every cell beside it — both of which look like data rather than an error. See TableCount(byte[]) for what this does to indexes.

Exceptions

ArgumentNullException

docx is null.

ArgumentException

docx is empty.

ArgumentOutOfRangeException

index is negative, or at or beyond TableCount(byte[]).

DocumentConversionException

The package could not be read.

ReadTableAsync(Stream, int, CancellationToken)

The table at index, as rows of cell text.

public static Task<IReadOnlyList<IReadOnlyList<string>>> ReadTableAsync(Stream source, int index, CancellationToken ct = default)

Parameters

source Stream
index int

0-based, indexing what TableCount(byte[]) reports — deliberately unlike PdfEditor.ExtractPages, whose firstPage is 1-based because that is how a reader numbers pages. A table has no such reader-facing numbering, and the rows and cells this returns are 0-based, so a 1-based selector here would be the odd one out.

ct CancellationToken

Returns

Task<IReadOnlyList<IReadOnlyList<string>>>

Examples

int count = DocxEditor.TableCount(docx);
IReadOnlyList<IReadOnlyList<string>> table = DocxEditor.ReadTable(docx, 0);

Remarks

Cell text is produced the same way ExtractText(byte[]) produces it, so a cell holding several paragraphs is separated by newlines and a nested table keeps its own structure.

Rows are returned with the shape they have. A horizontally merged cell means a row genuinely holds fewer cells than its neighbours; padding the grid to a rectangle would invent cells that are not in the document.

Content controls are transparent, at every level. A table, a row or a cell wrapped in a w:sdt is read like any other. A wrapped row used to vanish from an otherwise correct table, and a wrapped cell used to shift every cell beside it — both of which look like data rather than an error. See TableCount(byte[]) for what this does to indexes.

Exceptions

ArgumentNullException

docx is null.

ArgumentException

docx is empty.

ArgumentOutOfRangeException

index is negative, or at or beyond TableCount(byte[]).

DocumentConversionException

The package could not be read.

ReadTableAsync(string, int, CancellationToken)

The table at index, as rows of cell text.

public static Task<IReadOnlyList<IReadOnlyList<string>>> ReadTableAsync(string path, int index, CancellationToken ct = default)

Parameters

path string
index int

0-based, indexing what TableCount(byte[]) reports — deliberately unlike PdfEditor.ExtractPages, whose firstPage is 1-based because that is how a reader numbers pages. A table has no such reader-facing numbering, and the rows and cells this returns are 0-based, so a 1-based selector here would be the odd one out.

ct CancellationToken

Returns

Task<IReadOnlyList<IReadOnlyList<string>>>

Examples

int count = DocxEditor.TableCount(docx);
IReadOnlyList<IReadOnlyList<string>> table = DocxEditor.ReadTable(docx, 0);

Remarks

Cell text is produced the same way ExtractText(byte[]) produces it, so a cell holding several paragraphs is separated by newlines and a nested table keeps its own structure.

Rows are returned with the shape they have. A horizontally merged cell means a row genuinely holds fewer cells than its neighbours; padding the grid to a rectangle would invent cells that are not in the document.

Content controls are transparent, at every level. A table, a row or a cell wrapped in a w:sdt is read like any other. A wrapped row used to vanish from an otherwise correct table, and a wrapped cell used to shift every cell beside it — both of which look like data rather than an error. See TableCount(byte[]) for what this does to indexes.

Exceptions

ArgumentNullException

docx is null.

ArgumentException

docx is empty.

ArgumentOutOfRangeException

index is negative, or at or beyond TableCount(byte[]).

DocumentConversionException

The package could not be read.

RemoveWatermarks(byte[])

Removes every watermark from every section, and returns the updated document. A document with none comes back unchanged rather than refused.

public static byte[] RemoveWatermarks(byte[] docx)

Parameters

docx byte[]

The document to clear. It is not modified.

Returns

byte[]

Exceptions

ArgumentNullException

docx is null.

ArgumentException

docx is empty.

DocumentConversionException

The document could not be opened or written.

RemoveWatermarksAsync(Stream, Stream, CancellationToken)

Removes every watermark from every section, and returns the updated document. A document with none comes back unchanged rather than refused.

public static Task RemoveWatermarksAsync(Stream source, Stream destination, CancellationToken ct = default)

Parameters

source Stream

The stream the document is read from.

destination Stream

The stream the cleared document is written to.

ct CancellationToken

Cancels the read, the edit and the write.

Returns

Task

Remarks

source is read to its end and destination is written; neither is disposed, closed or sought.

Exceptions

ArgumentNullException

source or destination is null.

ArgumentException

source is not readable or held no bytes, or destination is not writable.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be opened or written.

ReplaceImage(byte[], string, byte[], double?, double?)

Replaces every occurrence of placeholder with image, inline, across the body, headers, footers, footnotes and endnotes.

Only the matched text goes: text sharing a run with the placeholder keeps its place and its formatting, so Signed: {{sig}} (authorised) becomes Signed: , the image, then (authorised).

placeholder is the literal text including braces, like ReplaceText(byte[], IReadOnlyDictionary<string, string>) — and unlike FillRows(byte[], string, IEnumerable<IReadOnlyDictionary<string, string>>), whose keys are bare field names only because the collection name is already an argument there.

Size is in points. Omit both and the image's intrinsic size is used, read from its own header at 96 DPI. Give one and the other scales to preserve the aspect ratio. Give both and the image is stretched to fit — distortion is the caller's choice, not an error.

PNG and JPEG only, detected from the image's magic bytes rather than any filename.

public static byte[] ReplaceImage(byte[] docx, string placeholder, byte[] image, double? widthPoints = null, double? heightPoints = null)

Parameters

docx byte[]
placeholder string
image byte[]
widthPoints double?
heightPoints double?

Returns

byte[]

Exceptions

ArgumentNullException

Any of the three required arguments is null.

ArgumentException

docx or image is empty, or placeholder is blank.

ArgumentOutOfRangeException

A supplied size is zero or negative, or the resulting size is larger than a drawing extent can hold (2,147,483,647 EMU per side — about 2,348 inches). The upper bound also applies when the side that overflows is the one DERIVED from the aspect ratio rather than the one supplied.

DocumentConversionException

The image is neither PNG nor JPEG, the package could not be edited, or placeholder does not appear anywhere — a call matching nothing is a bug in the call or the template, not a no-op.

ReplaceImageAsync(Stream, string, byte[], Stream, double?, double?, CancellationToken)

Reads a .docx from source, replaces every occurrence of placeholder with image, and writes the result to destination. See ReplaceImage(byte[], string, byte[], double?, double?) for what is matched and how it is sized — this overload applies the identical logic via streams instead of a byte array.

source is read to its end and destination is written; neither is disposed, closed or sought, and neither has to be seekable, so both may be sockets, files or HTTP message bodies.

public static Task ReplaceImageAsync(Stream source, string placeholder, byte[] image, Stream destination, double? widthPoints = null, double? heightPoints = null, CancellationToken ct = default)

Parameters

source Stream

The stream the .docx package is read from.

placeholder string

The literal placeholder text, braces included.

image byte[]

PNG or JPEG bytes, identified by their magic bytes.

destination Stream

The stream the edited .docx package is written to.

widthPoints double?

Width in points, or null to derive it.

heightPoints double?

Height in points, or null to derive it.

ct CancellationToken

Cancels the read, the edit and the write.

Returns

Task

Exceptions

ArgumentNullException

Any argument is null.

ArgumentException

source is not readable or held no bytes, destination is not writable, image is empty, or placeholder is blank.

ArgumentOutOfRangeException

A supplied size is zero or negative, or the resulting size is larger than a drawing extent can hold (2,147,483,647 EMU per side — about 2,348 inches). The upper bound also applies when the side that overflows is the one DERIVED from the aspect ratio rather than the one supplied.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The image is neither PNG nor JPEG, the package could not be edited, or the placeholder was not found.

ReplaceImageAsync(string, string, string, byte[], double?, double?, CancellationToken)

Reads a .docx from inputPath, replaces an image placeholder, and writes the result to outputPath. The two may be the same file: the updated bytes are computed in full before outputPath is opened, so a document that fails to process — cannot be read, or cannot be edited — leaves outputPath untouched. That guarantee does not extend to a failure during the write itself: a full disk, a cancellation, or the process dying mid-write can still leave a partial file, so in-place editing of an irreplaceable document is not crash-safe.

public static Task ReplaceImageAsync(string inputPath, string outputPath, string placeholder, byte[] image, double? widthPoints = null, double? heightPoints = null, CancellationToken ct = default)

Parameters

inputPath string

The .docx to read.

outputPath string

Where to write the result. Overwritten if it exists.

placeholder string

The placeholder text to replace.

image byte[]

PNG or JPEG bytes. The format is decided by the bytes, never a filename.

widthPoints double?

Width in points. Give one dimension and the other scales.

heightPoints double?

Height in points.

ct CancellationToken

Cancels the read and the write.

Returns

Task

Exceptions

ArgumentNullException

A path, placeholder or image is null.

ArgumentException

A path is blank, or the file at inputPath is empty.

FileNotFoundException

inputPath does not exist.

DirectoryNotFoundException

inputPath's or outputPath's directory does not exist.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be processed.

ReplaceText(byte[], IReadOnlyDictionary<string, string>)

Replaces every key with its value across the document body, its headers and footers, and its footnotes and endnotes.

Word routinely splits a single visible word across several <w:t> runs (spell-check state, formatting changes, a language switch), so a naive per-run replace misses any placeholder that straddles a run boundary. Substitution therefore happens against the concatenated text of each paragraph, but the result is spliced back into only the runs the match actually overlaps: runs outside a match — including the runs inside a <w:hyperlink> — keep their text and their formatting untouched. When a placeholder does straddle runs, the replacement value is written into the run holding its first character and so inherits that run's formatting.

Text boxes (<w:txbxContent>) nest whole paragraphs inside a run of the enclosing paragraph. They are treated as the separate paragraphs they are, so a placeholder inside a text box is replaced and a text box without one is left alone.

Keys are matched in a single left-to-right pass and the longest key wins at any given offset, so a substituted value is never rescanned for further placeholders.

public static byte[] ReplaceText(byte[] docx, IReadOnlyDictionary<string, string> replacements)

Parameters

docx byte[]
replacements IReadOnlyDictionary<string, string>

Returns

byte[]

Examples

byte[] filled = DocxEditor.ReplaceText(template, new Dictionary<string, string>
{
    ["{{customer}}"] = "Acme Ltd",
    ["{{number}}"] = "2026-114",
});

Exceptions

ArgumentNullException

Either argument is null.

ArgumentException

docx is empty.

DocumentConversionException

The package could not be opened or edited.

ReplaceText(byte[], Regex, string)

Replaces every match of pattern with replacement, across the body, headers, footers, footnotes and endnotes (A116).

public static byte[] ReplaceText(byte[] docx, Regex pattern, string replacement)

Parameters

docx byte[]

The .docx package to edit.

pattern Regex

The pattern to find. Must have a finite MatchTimeout.

replacement string

The substitution template applied to each match.

Returns

byte[]

Remarks

The literal overload above matches keys; this one matches a pattern, and everything else about it is identical — the same walk, the same splice, the same run-boundary handling. A match spanning several runs has its replacement written into the run holding its first character, so it inherits that run's formatting, exactly as a literal key does.

replacement is a substitution template, not a literal. $1 and friends expand to captured groups the way Regex.Replace expands them, so a literal \(</code> must be written <code>$\).

Zero-width matches are skipped. One consumes no characters, so inserting a replacement for it would not advance through the text.

pattern must carry a match timeout. See the exception below — this refuses an unbounded one rather than risking a wedged caller.

Exceptions

ArgumentNullException

Any argument is null.

ArgumentException

docx is empty, or pattern was built without a match timeout. A pattern that can backtrack catastrophically has no upper bound on its running time, and a hang is worse than a failure because the caller cannot catch it — so the timeout is required here rather than defaulted to a number this library invented. Construct it as new Regex(text, options, TimeSpan.FromSeconds(1)).

DocumentConversionException

The package could not be opened or edited.

RegexMatchTimeoutException

pattern exceeded its own MatchTimeout on some paragraph.

ReplaceTextAsync(Stream, IReadOnlyDictionary<string, string>, Stream, CancellationToken)

Reads a .docx from source, replaces every key with its value, and writes the result to destination. See ReplaceText(byte[], IReadOnlyDictionary<string, string>) for exactly what counts as a match and how formatting survives it — this overload applies the identical logic via source and destination instead of a byte array.

source is read to its end and destination is written; neither is disposed, closed or sought, and neither has to be seekable, so both may be sockets, files or HTTP message bodies.

public static Task ReplaceTextAsync(Stream source, IReadOnlyDictionary<string, string> replacements, Stream destination, CancellationToken ct = default)

Parameters

source Stream

The stream the .docx package is read from.

replacements IReadOnlyDictionary<string, string>

Each key is replaced by its value, longest key wins per match.

destination Stream

The stream the edited .docx package is written to.

ct CancellationToken

Cancels the read, the edit and the write.

Returns

Task

Exceptions

ArgumentNullException

Any argument is null.

ArgumentException

source is not readable or held no bytes, or destination is not writable.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The package could not be opened or edited.

ReplaceTextAsync(Stream, Regex, string, Stream, CancellationToken)

Replaces every match of pattern with replacement, across the body, headers, footers, footnotes and endnotes (A116).

public static Task ReplaceTextAsync(Stream source, Regex pattern, string replacement, Stream destination, CancellationToken ct = default)

Parameters

source Stream

The stream the .docx package is read from.

pattern Regex

The pattern to find. Must have a finite MatchTimeout.

replacement string

The substitution template applied to each match.

destination Stream

The stream the edited .docx package is written to.

ct CancellationToken

Cancels the read, the edit and the write.

Returns

Task

Remarks

source is read to its end and destination is written; neither is disposed, closed or sought, and neither has to be seekable.

This remarks replaces the one on ReplaceText(byte[], Regex, string) rather than adding to it, so its warnings are restated rather than assumed to carry over: replacement is a template in which $1 expands to a captured group, zero-width matches are skipped, and pattern must carry a match timeout.

Exceptions

OperationCanceledException

ct was cancelled.

ReplaceTextAsync(string, string, IReadOnlyDictionary<string, string>, CancellationToken)

Reads a .docx from inputPath, substitutes placeholders, and writes the result to outputPath. The two may be the same file: the updated bytes are computed in full before outputPath is opened, so a document that fails to process — cannot be read, or cannot be edited — leaves outputPath untouched. That guarantee does not extend to a failure during the write itself: a full disk, a cancellation, or the process dying mid-write can still leave a partial file, so in-place editing of an irreplaceable document is not crash-safe.

public static Task ReplaceTextAsync(string inputPath, string outputPath, IReadOnlyDictionary<string, string> replacements, CancellationToken ct = default)

Parameters

inputPath string

The .docx to read.

outputPath string

Where to write the result. Overwritten if it exists.

replacements IReadOnlyDictionary<string, string>

Placeholder to replacement text.

ct CancellationToken

Cancels the read and the write.

Returns

Task

Exceptions

ArgumentNullException

A path or replacements is null.

ArgumentException

A path is blank, or the file at inputPath is empty.

FileNotFoundException

inputPath does not exist.

DirectoryNotFoundException

inputPath's or outputPath's directory does not exist.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be processed.

TableCount(byte[])

How many tables the document body holds.

public static int TableCount(byte[] docx)

Parameters

docx byte[]

Returns

int

Examples

int count = DocxEditor.TableCount(docx);
IReadOnlyList<IReadOnlyList<string>> table = DocxEditor.ReadTable(docx, 0);

Remarks

Top-level tables only. A table nested inside a cell is part of that cell's text rather than an entry of its own, so this count and the indexes it bounds stay stable.

A table wrapped in a content control counts. Word puts a w:sdt around content the author marked up, and it used to hide the table inside from this count entirely — so a document whose only table was wrapped reported 0. It reports 1.

That moves indexes, deliberately. Where a wrapped table precedes an ordinary one, index 0 used to return the ordinary one — the table that is physically second. Indexes are now positional over every table a reader can see, which is what ExtractText(byte[]) has always reported.

Exceptions

ArgumentNullException

docx is null.

ArgumentException

docx is empty.

DocumentConversionException

The package could not be read.

TableCountAsync(Stream, CancellationToken)

How many tables the document body holds.

public static Task<int> TableCountAsync(Stream source, CancellationToken ct = default)

Parameters

source Stream
ct CancellationToken

Returns

Task<int>

Examples

int count = DocxEditor.TableCount(docx);
IReadOnlyList<IReadOnlyList<string>> table = DocxEditor.ReadTable(docx, 0);

Remarks

Top-level tables only. A table nested inside a cell is part of that cell's text rather than an entry of its own, so this count and the indexes it bounds stay stable.

A table wrapped in a content control counts. Word puts a w:sdt around content the author marked up, and it used to hide the table inside from this count entirely — so a document whose only table was wrapped reported 0. It reports 1.

That moves indexes, deliberately. Where a wrapped table precedes an ordinary one, index 0 used to return the ordinary one — the table that is physically second. Indexes are now positional over every table a reader can see, which is what ExtractText(byte[]) has always reported.

Exceptions

ArgumentNullException

docx is null.

ArgumentException

docx is empty.

DocumentConversionException

The package could not be read.

TableCountAsync(string, CancellationToken)

How many tables the document body holds.

public static Task<int> TableCountAsync(string path, CancellationToken ct = default)

Parameters

path string
ct CancellationToken

Returns

Task<int>

Examples

int count = DocxEditor.TableCount(docx);
IReadOnlyList<IReadOnlyList<string>> table = DocxEditor.ReadTable(docx, 0);

Remarks

Top-level tables only. A table nested inside a cell is part of that cell's text rather than an entry of its own, so this count and the indexes it bounds stay stable.

A table wrapped in a content control counts. Word puts a w:sdt around content the author marked up, and it used to hide the table inside from this count entirely — so a document whose only table was wrapped reported 0. It reports 1.

That moves indexes, deliberately. Where a wrapped table precedes an ordinary one, index 0 used to return the ordinary one — the table that is physically second. Indexes are now positional over every table a reader can see, which is what ExtractText(byte[]) has always reported.

Exceptions

ArgumentNullException

docx is null.

ArgumentException

docx is empty.

DocumentConversionException

The package could not be read.

Unprotect(byte[], string)

A copy of docx with its encryption removed, so the rest of this class can work on it.

public static byte[] Unprotect(byte[] docx, string password)

Parameters

docx byte[]

The encrypted document.

password string

The password the document was encrypted with.

Returns

byte[]

Remarks

The output is not protected in any way. That is what was asked for, but the bytes this returns are readable by anyone who obtains them.

A document that was never encrypted is reported as such rather than passed through, because silently returning the input would make a broken pipeline look like a working one.

Exceptions

ArgumentNullException

docx or password is null.

ArgumentException

docx is empty, or password is empty.

DocumentConversionException

The password was wrong, the document was not encrypted, or it could not be read.

UnprotectAsync(Stream, Stream, string, CancellationToken)

Reads an encrypted document from source and writes the unprotected copy to destination.

Neither stream is disposed, closed or sought.

public static Task UnprotectAsync(Stream source, Stream destination, string password, CancellationToken ct = default)

Parameters

source Stream

The stream the encrypted document is read from.

destination Stream

The stream the unprotected document is written to.

password string

The password the document was encrypted with.

ct CancellationToken

Cancels the read and the write.

Returns

Task

Remarks

The output is not protected in any way. That is what was asked for, but the bytes this returns are readable by anyone who obtains them.

A document that was never encrypted is reported as such rather than passed through, because silently returning the input would make a broken pipeline look like a working one.

ValidateSignatures(byte[], DocumentSignatureValidationOptions?)

Validates every digital signature docx carries, returning the report-level tamper-detection verdict alongside each signature's own certificate chain trust and revocation status. Read DocumentSignatureValidationResult's own remarks before treating its CryptographicStatus as tamper detection — it is not; the report-level IsCryptographicallyValid is. Never performs revocation checking or certificate downloads over the network, regardless of options — see DocumentSignatureValidationOptions's own remarks.

public static DocumentSignatureValidationReport ValidateSignatures(byte[] docx, DocumentSignatureValidationOptions? options = null)

Parameters

docx byte[]
options DocumentSignatureValidationOptions

Returns

DocumentSignatureValidationReport

Exceptions

ArgumentNullException

docx is null.

ArgumentException

docx is empty.

DocumentConversionException

The document could not be validated.

ValidateSignaturesAsync(Stream, DocumentSignatureValidationOptions?, CancellationToken)

Reads a .docx from source and validates its digital signatures — see ValidateSignatures(byte[], DocumentSignatureValidationOptions?). source is read to its end and is neither disposed, closed nor sought.

public static Task<DocumentSignatureValidationReport> ValidateSignaturesAsync(Stream source, DocumentSignatureValidationOptions? options = null, CancellationToken ct = default)

Parameters

source Stream
options DocumentSignatureValidationOptions
ct CancellationToken

Returns

Task<DocumentSignatureValidationReport>

Exceptions

ArgumentNullException

source is null.

ArgumentException

source is not readable or held no bytes.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be validated.

WithMetadata(byte[], DocumentMetadata)

A copy of docx carrying metadata.

public static byte[] WithMetadata(byte[] docx, DocumentMetadata metadata)

Parameters

docx byte[]
metadata DocumentMetadata

Returns

byte[]

Remarks

A null property leaves what the document already had in place, so stamping a title does not silently erase an author. Pass an empty string to clear one.

Exceptions

ArgumentNullException

docx or metadata is null.

ArgumentException

docx is empty.

DocumentConversionException

The document could not be read or written.

WithMetadataAsync(Stream, DocumentMetadata, Stream, CancellationToken)

A copy of docx carrying metadata.

public static Task WithMetadataAsync(Stream source, DocumentMetadata metadata, Stream destination, CancellationToken ct = default)

Parameters

source Stream

The stream the document is read from.

metadata DocumentMetadata

The properties to stamp.

destination Stream

The stream the updated document is written to.

ct CancellationToken

Cancels the read, the edit and the write.

Returns

Task

Remarks

source is read to its end and destination is written; neither is disposed, closed or sought, and neither has to be seekable.

Exceptions

ArgumentNullException

source, metadata or destination is null.

ArgumentException

source is not readable or held no bytes, or destination is not writable.

OperationCanceledException

ct was cancelled.

DocumentConversionException

The document could not be read or written.