Class DocxEditor
- Namespace
- DocToolkit
- Assembly
- DocToolkit.dll
Creates, reads and edits Word (.docx) documents.
public static class DocxEditor
- Inheritance
-
DocxEditor
- Inherited Members
Methods
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
CreateToFileAsync(string, IEnumerable<IEnumerable<object?>>, string, CancellationToken):
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 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.
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.
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(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.