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
docxbyte[]The document to edit. It is not modified.
paragraphIndexintThe 0-based paragraph to mark.
namestringThe bookmark name.
Returns
- byte[]
Remarks
paragraphIndex is 0-based, matching
ReadTable(byte[], int) and everything else this class indexes.
Exceptions
- ArgumentNullException
docxornameis null.- ArgumentException
docxis empty, ornameis blank.- ArgumentOutOfRangeException
paragraphIndexis 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
sourceStreamThe stream the document is read from.
paragraphIndexintThe 0-based paragraph to mark.
namestringThe bookmark name.
destinationStreamThe stream the updated document is written to.
ctCancellationTokenCancels the read, the edit and the write.
Returns
Remarks
source is read to its end and destination is
written; neither is disposed, closed or sought.
Exceptions
- ArgumentNullException
An argument is null.
- ArgumentException
sourceis not readable or held no bytes,nameis blank, ordestinationis not writable.- ArgumentOutOfRangeException
paragraphIndexis negative, or at or beyond the paragraph count.- OperationCanceledException
ctwas 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
Returns
- byte[]
Exceptions
- ArgumentNullException
Any of the three required arguments is null.
- ArgumentException
docxis empty, orplaceholderis blank.- DocumentConversionException
The package could not be edited, or
placeholderdoes 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
sourceStreamThe stream the .docx package is read from.
placeholderstringThe literal placeholder text, braces included.
endnoteTextstringThe endnote's own text.
destinationStreamThe stream the edited .docx package is written to.
ctCancellationTokenCancels the read, the edit and the write.
Returns
Exceptions
- ArgumentNullException
Any argument is null.
- ArgumentException
sourceis not readable or held no bytes,destinationis not writable, orplaceholderis blank.- OperationCanceledException
ctwas 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
inputPathstringThe .docx to read.
outputPathstringWhere to write the result. Overwritten if it exists.
placeholderstringThe literal placeholder text, braces included.
endnoteTextstringThe endnote's own text.
ctCancellationTokenCancels the read and the write.
Returns
Exceptions
- ArgumentNullException
A path or
placeholderis null.- ArgumentException
A path is blank, the file at
inputPathis empty, orplaceholderis blank.- FileNotFoundException
inputPathdoes not exist.- DirectoryNotFoundException
inputPath's oroutputPath's directory does not exist.- OperationCanceledException
ctwas 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
Returns
- byte[]
Exceptions
- ArgumentNullException
Any of the three required arguments is null.
- ArgumentException
docxis empty, orplaceholderis blank.- DocumentConversionException
The package could not be edited, or
placeholderdoes 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
sourceStreamThe stream the .docx package is read from.
placeholderstringThe literal placeholder text, braces included.
footnoteTextstringThe footnote's own text.
destinationStreamThe stream the edited .docx package is written to.
ctCancellationTokenCancels the read, the edit and the write.
Returns
Exceptions
- ArgumentNullException
Any argument is null.
- ArgumentException
sourceis not readable or held no bytes,destinationis not writable, orplaceholderis blank.- OperationCanceledException
ctwas 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
inputPathstringThe .docx to read.
outputPathstringWhere to write the result. Overwritten if it exists.
placeholderstringThe literal placeholder text, braces included.
footnoteTextstringThe footnote's own text.
ctCancellationTokenCancels the read and the write.
Returns
Exceptions
- ArgumentNullException
A path or
placeholderis null.- ArgumentException
A path is blank, the file at
inputPathis empty, orplaceholderis blank.- FileNotFoundException
inputPathdoes not exist.- DirectoryNotFoundException
inputPath's oroutputPath's directory does not exist.- OperationCanceledException
ctwas 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
docxbyte[]The .docx to edit. It is not modified.
placeholderstringThe literal placeholder text, braces included. Must be the entire text of the paragraph it appears in — see the remarks.
minLevelintThe shallowest heading level to include. 1-9.
maxLevelintThe 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
docxorplaceholderis null.- ArgumentException
docxis empty,placeholderis blank, orminLevelis greater thanmaxLevel.- ArgumentOutOfRangeException
minLevelormaxLevelis 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:pPrcarries aw: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
sourceStreamThe stream the .docx package is read from.
placeholderstringThe literal placeholder text, braces included.
destinationStreamThe stream the edited .docx package is written to.
minLevelintThe shallowest heading level to include. 1-9.
maxLevelintThe deepest heading level to include. 1-9, and at least
minLevel.ctCancellationTokenCancels the read, the edit and the write.
Returns
Exceptions
- ArgumentNullException
Any argument is null.
- ArgumentException
sourceis not readable or held no bytes,destinationis not writable,placeholderis blank, orminLevelis greater thanmaxLevel.- ArgumentOutOfRangeException
minLevelormaxLevelis outside 1-9.- OperationCanceledException
ctwas 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:pPrcarries aw: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
inputPathstringThe .docx to read.
outputPathstringWhere to write the result. Overwritten if it exists.
placeholderstringThe literal placeholder text, braces included.
minLevelintThe shallowest heading level to include. 1-9.
maxLevelintThe deepest heading level to include. 1-9, and at least
minLevel.ctCancellationTokenCancels the read and the write.
Returns
Exceptions
- ArgumentNullException
A path or
placeholderis null.- ArgumentException
A path is blank, the file at
inputPathis empty,placeholderis blank, orminLevelis greater thanmaxLevel.- ArgumentOutOfRangeException
minLevelormaxLevelis outside 1-9.- FileNotFoundException
inputPathdoes not exist.- DirectoryNotFoundException
inputPath's oroutputPath's directory does not exist.- OperationCanceledException
ctwas 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:pPrcarries aw: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
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
docxortextis null.- ArgumentException
docxis empty, ortextis 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
sourceStreamThe stream the document is read from.
textstringThe watermark text.
destinationStreamThe stream the stamped document is written to.
ctCancellationTokenCancels the read, the edit and the write.
Returns
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
sourceis not readable or held no bytes,textis blank, ordestinationis not writable.- OperationCanceledException
ctwas 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
blocksIEnumerable<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
blocksis null.- ArgumentException
An element of
blocksis 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
blocksIEnumerable<DocxBlock>The content, written in order.
pagePageSetupThe page size, orientation and margins.
Returns
- byte[]
Exceptions
- ArgumentNullException
blocksorpageis null.- ArgumentException
An element of
blocksis 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
blocksIEnumerable<DocxBlock>The content, written in order.
pagePageSetupThe page size, orientation and margins.
destinationStreamThe stream the document is written to.
ctCancellationTokenCancels the build and the write to
destination.
Returns
Exceptions
- ArgumentNullException
Any argument is null.
- ArgumentException
An element of
blocksis null, ordestinationis not writable.- OperationCanceledException
ctwas 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
blocksIEnumerable<DocxBlock>The content, written in order.
destinationStreamThe stream the document is written to.
ctCancellationTokenCancels the build and the write to
destination.
Returns
Remarks
The document is laid out on A4. Use CreateAsync(IEnumerable<DocxBlock>, PageSetup, Stream, CancellationToken) for anything else.
Exceptions
- ArgumentNullException
blocksordestinationis null.- ArgumentException
An element of
blocksis null, ordestinationis not writable.- OperationCanceledException
ctwas 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
blocksIEnumerable<DocxBlock>The content, written in order.
pagePageSetupThe page size, orientation and margins.
outputPathstringWhere to write the document. Overwritten if it exists.
ctCancellationTokenCancels the write to
outputPath.
Returns
Exceptions
- ArgumentNullException
Any argument is null.
- ArgumentException
outputPathis blank, or an element ofblocksis null.- DirectoryNotFoundException
outputPath's directory does not exist.- OperationCanceledException
ctwas 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
blocksIEnumerable<DocxBlock>The content, written in order.
outputPathstringWhere to write the document. Overwritten if it exists.
ctCancellationTokenCancels the write to
outputPath.
Returns
Remarks
The document is laid out on A4. Use CreateToFileAsync(IEnumerable<DocxBlock>, PageSetup, string, CancellationToken) for anything else.
Exceptions
- ArgumentNullException
blocksoroutputPathis null.- ArgumentException
outputPathis blank, or an element ofblocksis null.- DirectoryNotFoundException
outputPath's directory does not exist.- OperationCanceledException
ctwas 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
docxbyte[]
Returns
Exceptions
- ArgumentNullException
docxis null.- ArgumentException
docxis 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
Returns
Exceptions
- ArgumentNullException
docxis null.- ArgumentException
docxis 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
sourceStreamincludeHeadersAndFootersboolctCancellationToken
Returns
Exceptions
- ArgumentNullException
sourceis null.- ArgumentException
sourceis not readable or held no bytes.- OperationCanceledException
ctwas 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
sourceStreamctCancellationToken
Returns
Exceptions
- ArgumentNullException
sourceis null.- ArgumentException
sourceis not readable or held no bytes.- OperationCanceledException
ctwas 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
pathstringThe .docx to read.
includeHeadersAndFootersboolWhether to include header and footer text.
ctCancellationTokenCancels the read.
Returns
Exceptions
- ArgumentNullException
pathis null.- ArgumentException
pathis blank, or the file it names is empty.- FileNotFoundException
pathdoes not exist.- DirectoryNotFoundException
path's directory does not exist.- OperationCanceledException
ctwas 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
pathstringThe .docx to read.
ctCancellationTokenCancels the read.
Returns
Exceptions
- ArgumentNullException
pathis null.- ArgumentException
pathis blank, or the file it names is empty.- FileNotFoundException
pathdoes not exist.- DirectoryNotFoundException
path's directory does not exist.- OperationCanceledException
ctwas 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
docxbyte[]collectionstringrowsIEnumerable<IReadOnlyDictionary<string, string>>
Returns
- byte[]
Exceptions
- ArgumentNullException
Any argument is null.
- ArgumentException
docxis empty, orcollectionis 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
sourceStreamThe stream the .docx package is read from.
collectionstringThe placeholder prefix marking the template row, without braces.
rowsIEnumerable<IReadOnlyDictionary<string, string>>One dictionary per record, keyed by bare field name.
destinationStreamThe stream the edited .docx package is written to.
ctCancellationTokenCancels the read, the edit and the write.
Returns
Exceptions
- ArgumentNullException
Any argument is null.
- ArgumentException
sourceis not readable or held no bytes,destinationis not writable, orcollectionis blank.- OperationCanceledException
ctwas 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
inputPathstringThe .docx to read.
outputPathstringWhere to write the result. Overwritten if it exists.
collectionstringThe collection name used in the row's placeholders.
rowsIEnumerable<IReadOnlyDictionary<string, string>>One dictionary per record.
ctCancellationTokenCancels the read and the write.
Returns
Exceptions
- ArgumentNullException
A path,
collectionorrowsis null.- ArgumentException
A path is blank, or the file at
inputPathis empty.- FileNotFoundException
inputPathdoes not exist.- DirectoryNotFoundException
inputPath's oroutputPath's directory does not exist.- OperationCanceledException
ctwas 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
docxbyte[]
Returns
Exceptions
- ArgumentNullException
docxis null.- ArgumentException
docxis 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
sourceStreamctCancellationToken
Returns
Exceptions
- ArgumentNullException
sourceis null.- ArgumentException
sourceis not readable or held no bytes.- OperationCanceledException
ctwas cancelled.- DocumentConversionException
The document could not be inspected.
IsProtected(byte[])
Whether docx is an ENCRYPTED Office document.
public static bool IsProtected(byte[] docx)
Parameters
docxbyte[]The bytes to inspect.
Returns
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
docxis null.
IsProtectedAsync(Stream, CancellationToken)
Whether docx is an ENCRYPTED Office document.
public static Task<bool> IsProtectedAsync(Stream source, CancellationToken ct = default)
Parameters
sourceStreamThe stream the document is read from.
ctCancellationTokenCancels the read.
Returns
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
sourceis null.- ArgumentException
sourceis not readable or held no bytes.- OperationCanceledException
ctwas 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
docxIEnumerable<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
docxis 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
sourcesIEnumerable<Stream>The streams the documents are read from, in order. At least one.
destinationStreamThe stream the joined document is written to.
ctCancellationTokenCancels the reads, the merge and the write.
Returns
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
sourcesordestinationis null.- ArgumentException
sourcesis empty, one of them is not readable or held no bytes, ordestinationis not writable.- OperationCanceledException
ctwas 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
docxbyte[]The document to encrypt.
passwordstringThe 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
docxorpasswordis null.- ArgumentException
docxis empty, orpasswordis 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
sourceStreamThe stream the document is read from.
destinationStreamThe stream the encrypted document is written to.
passwordstringThe password required to open the result. May not be empty.
ctCancellationTokenCancels the read and the write.
Returns
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
docxbyte[]The document to read.
Returns
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
docxis null.- ArgumentException
docxis 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
sourceStreamThe stream the document is read from.
ctCancellationTokenCancels the read.
Returns
Remarks
source is read to its end and is neither disposed, closed nor sought.
Exceptions
- ArgumentNullException
sourceis null.- ArgumentException
sourceis not readable or held no bytes.- OperationCanceledException
ctwas cancelled.- DocumentConversionException
The document could not be opened or read.
ReadMetadata(byte[])
The document properties docx carries.
public static DocumentMetadata ReadMetadata(byte[] docx)
Parameters
docxbyte[]
Returns
Exceptions
- ArgumentNullException
docxis null.- ArgumentException
docxis 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
sourceStreamThe stream the document is read from.
ctCancellationTokenCancels the read.
Returns
Remarks
source is read to its end and is neither disposed, closed nor sought.
Exceptions
- ArgumentNullException
sourceis null.- ArgumentException
sourceis not readable or held no bytes.- OperationCanceledException
ctwas 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
docxbyte[]The .docx content to read.
indexint0-based, indexing what TableCount(byte[]) reports — deliberately unlike
PdfEditor.ExtractPages, whosefirstPageis 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
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
docxis null.- ArgumentException
docxis empty.- ArgumentOutOfRangeException
indexis 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
sourceStreamindexint0-based, indexing what TableCount(byte[]) reports — deliberately unlike
PdfEditor.ExtractPages, whosefirstPageis 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.ctCancellationToken
Returns
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
docxis null.- ArgumentException
docxis empty.- ArgumentOutOfRangeException
indexis 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
pathstringindexint0-based, indexing what TableCount(byte[]) reports — deliberately unlike
PdfEditor.ExtractPages, whosefirstPageis 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.ctCancellationToken
Returns
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
docxis null.- ArgumentException
docxis empty.- ArgumentOutOfRangeException
indexis 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
docxbyte[]The document to clear. It is not modified.
Returns
- byte[]
Exceptions
- ArgumentNullException
docxis null.- ArgumentException
docxis 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
sourceStreamThe stream the document is read from.
destinationStreamThe stream the cleared document is written to.
ctCancellationTokenCancels the read, the edit and the write.
Returns
Remarks
source is read to its end and destination is
written; neither is disposed, closed or sought.
Exceptions
- ArgumentNullException
sourceordestinationis null.- ArgumentException
sourceis not readable or held no bytes, ordestinationis not writable.- OperationCanceledException
ctwas 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
Returns
- byte[]
Exceptions
- ArgumentNullException
Any of the three required arguments is null.
- ArgumentException
docxorimageis empty, orplaceholderis 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
placeholderdoes 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
sourceStreamThe stream the .docx package is read from.
placeholderstringThe literal placeholder text, braces included.
imagebyte[]PNG or JPEG bytes, identified by their magic bytes.
destinationStreamThe stream the edited .docx package is written to.
widthPointsdouble?Width in points, or null to derive it.
heightPointsdouble?Height in points, or null to derive it.
ctCancellationTokenCancels the read, the edit and the write.
Returns
Exceptions
- ArgumentNullException
Any argument is null.
- ArgumentException
sourceis not readable or held no bytes,destinationis not writable,imageis empty, orplaceholderis 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
ctwas 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
inputPathstringThe .docx to read.
outputPathstringWhere to write the result. Overwritten if it exists.
placeholderstringThe placeholder text to replace.
imagebyte[]PNG or JPEG bytes. The format is decided by the bytes, never a filename.
widthPointsdouble?Width in points. Give one dimension and the other scales.
heightPointsdouble?Height in points.
ctCancellationTokenCancels the read and the write.
Returns
Exceptions
- ArgumentNullException
A path,
placeholderorimageis null.- ArgumentException
A path is blank, or the file at
inputPathis empty.- FileNotFoundException
inputPathdoes not exist.- DirectoryNotFoundException
inputPath's oroutputPath's directory does not exist.- OperationCanceledException
ctwas 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
docxbyte[]replacementsIReadOnlyDictionary<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
docxis 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
docxbyte[]The .docx package to edit.
patternRegexThe pattern to find. Must have a finite
MatchTimeout.replacementstringThe 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
docxis empty, orpatternwas 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 asnew Regex(text, options, TimeSpan.FromSeconds(1)).- DocumentConversionException
The package could not be opened or edited.
- RegexMatchTimeoutException
patternexceeded its ownMatchTimeouton 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
sourceStreamThe stream the .docx package is read from.
replacementsIReadOnlyDictionary<string, string>Each key is replaced by its value, longest key wins per match.
destinationStreamThe stream the edited .docx package is written to.
ctCancellationTokenCancels the read, the edit and the write.
Returns
Exceptions
- ArgumentNullException
Any argument is null.
- ArgumentException
sourceis not readable or held no bytes, ordestinationis not writable.- OperationCanceledException
ctwas 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
sourceStreamThe stream the .docx package is read from.
patternRegexThe pattern to find. Must have a finite
MatchTimeout.replacementstringThe substitution template applied to each match.
destinationStreamThe stream the edited .docx package is written to.
ctCancellationTokenCancels the read, the edit and the write.
Returns
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
ctwas 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
inputPathstringThe .docx to read.
outputPathstringWhere to write the result. Overwritten if it exists.
replacementsIReadOnlyDictionary<string, string>Placeholder to replacement text.
ctCancellationTokenCancels the read and the write.
Returns
Exceptions
- ArgumentNullException
A path or
replacementsis null.- ArgumentException
A path is blank, or the file at
inputPathis empty.- FileNotFoundException
inputPathdoes not exist.- DirectoryNotFoundException
inputPath's oroutputPath's directory does not exist.- OperationCanceledException
ctwas cancelled.- DocumentConversionException
The document could not be processed.
TableCount(byte[])
How many tables the document body holds.
public static int TableCount(byte[] docx)
Parameters
docxbyte[]
Returns
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
docxis null.- ArgumentException
docxis 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
sourceStreamctCancellationToken
Returns
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
docxis null.- ArgumentException
docxis 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
pathstringctCancellationToken
Returns
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
docxis null.- ArgumentException
docxis 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
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
docxorpasswordis null.- ArgumentException
docxis empty, orpasswordis 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
sourceStreamThe stream the encrypted document is read from.
destinationStreamThe stream the unprotected document is written to.
passwordstringThe password the document was encrypted with.
ctCancellationTokenCancels the read and the write.
Returns
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
docxbyte[]optionsDocumentSignatureValidationOptions
Returns
Exceptions
- ArgumentNullException
docxis null.- ArgumentException
docxis 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
sourceStreamoptionsDocumentSignatureValidationOptionsctCancellationToken
Returns
Exceptions
- ArgumentNullException
sourceis null.- ArgumentException
sourceis not readable or held no bytes.- OperationCanceledException
ctwas 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
docxbyte[]metadataDocumentMetadata
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
docxormetadatais null.- ArgumentException
docxis 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
sourceStreamThe stream the document is read from.
metadataDocumentMetadataThe properties to stamp.
destinationStreamThe stream the updated document is written to.
ctCancellationTokenCancels the read, the edit and the write.
Returns
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,metadataordestinationis null.- ArgumentException
sourceis not readable or held no bytes, ordestinationis not writable.- OperationCanceledException
ctwas cancelled.- DocumentConversionException
The document could not be read or written.