Introduction
This guide looks at how Aspose.Words FOSS for .NET represents a Word document in memory, and at the APIs used to build, navigate, and change that representation: DocumentBuilder for sequential authoring, the node tree (Node, CompositeNode, NodeCollection) for direct structural access, DocumentVisitor for processing every node type in one pass, find-and-replace via Range and FindReplaceOptions, and the methods for combining and cloning documents. Where the announcement post introduces the library as a whole, this one stays focused on the document object model (DOM) — the single largest area of the API surface — and how its pieces fit together.
Aspose.Words FOSS for .NET is released under the MIT license with no native dependencies; it targets .NET Standard 2.0, so the DOM described here is available on .NET Framework 4.6.2+ and .NET 6, 8, and 10, on Windows, Linux, and macOS. A NuGet package has not been published yet — see Quick Start below for the current build-from-source setup.
Every task described below — writing a report from scratch, restructuring an existing .docx file, walking its contents for analysis, or assembling several documents into one — starts from the same handful of base types: Document, DocumentBuilder, and the Node hierarchy underneath them.
The Document Object Model
Building Documents with DocumentBuilder
DocumentBuilder is a cursor-based writer that sits on top of a Document and inserts content sequentially. Write(text) and Writeln(text) add text at the current position; InsertParagraph() and InsertBreak(breakType) add paragraph and page/column/section breaks. Formatting is stateful: the builder’s Font, ParagraphFormat, ListFormat, and PageSetup properties apply to everything written afterward, and PushFont() / PopFont() save and restore the current font state so a temporary style change does not have to be undone by hand. Navigation methods reposition the cursor anywhere in an existing document — MoveToDocumentStart(), MoveToDocumentEnd(), MoveToSection(sectionIndex), MoveToBookmark(bookmarkName), MoveToParagraph(paragraphIndex, characterIndex), and the general-purpose MoveTo(node) — and DocumentBuilder.CurrentNode, DocumentBuilder.CurrentParagraph, and DocumentBuilder.CurrentSection report where the builder currently sits. For read-only text extraction where a full DOM is not needed, PlainTextDocument offers a lighter path: new PlainTextDocument(fileName) exposes just the PlainTextDocument.Text property and the document’s built-in and custom properties.
The Node Tree: Sections, Paragraphs, Runs, and Tables
A Document is a tree of Node objects rooted at the document itself: Section → Body → Paragraph → Run for body text, with Table → Row → Cell branching off wherever a table appears. CompositeNode, the base class for every container node, exposes CompositeNode.FirstChild, CompositeNode.LastChild, Node.NextSibling, and Node.PreviousSibling for direct traversal, GetChildNodes(nodeType, isDeep) to collect every node of a given NodeType (Paragraph, Run, Table, and so on) at any depth, and GetChild(nodeType, index, isDeep) for indexed lookups. AppendChild(newChild), InsertBefore(newChild, refChild), InsertAfter(newChild, refChild), and RemoveChild(oldChild) mutate the tree directly. CompositeNode.SelectNodes(xpath) and SelectSingleNode(xpath) select nodes with an XPath-style expression instead of walking the tree by hand, and for very large documents, Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode) step through every node one at a time without recursion.
DocumentVisitor: Processing Every Node Type in One Pass
Subclassing DocumentVisitor is the way to process a document without hard-coding its structure. It defines paired Visit-Start / Visit-End methods for every composite node type, including DocumentVisitor.VisitSectionStart, DocumentVisitor.VisitParagraphStart, DocumentVisitor.VisitTableStart, DocumentVisitor.VisitRowStart, DocumentVisitor.VisitCellStart, and DocumentVisitor.VisitBookmarkStart — each with a matching End callback — plus DocumentVisitor.VisitFieldStart, DocumentVisitor.VisitFieldSeparator, DocumentVisitor.VisitFieldEnd, and DocumentVisitor.VisitRun for individual text runs. Call Accept(visitor) on a Document, Section, or any other node to run the visitor over that node and everything beneath it; AcceptStart(visitor) and AcceptEnd(visitor) invoke only the entry and exit callbacks for a single composite node. Each Visit method returns a VisitorAction value that controls how traversal continues — Continue into the subtree, SkipThisNode to skip it, or Stop to halt entirely.
Finding and Replacing Text
Range.Replace(pattern, replacement) runs a find-and-replace over the Range that owns it — an entire Document, a Section, or any node’s own Range property — with the search pattern accepted as either a literal string or a regular expression. The Range.Replace(pattern, replacement, options) overload takes a FindReplaceOptions instance to control FindReplaceOptions.MatchCase, FindReplaceOptions.FindWholeWordsOnly, search FindReplaceOptions.Direction (a FindReplaceDirection value of Forward or Backward), formatting applied to replacement text (FindReplaceOptions.ApplyFont, FindReplaceOptions.ApplyParagraphFormat), and which surrounding content to leave alone (FindReplaceOptions.IgnoreFields, FindReplaceOptions.IgnoreFootnotes, FindReplaceOptions.IgnoreFieldCodes, and related Ignore* flags). For per-match logic beyond a simple substitution, implement the Replacing(args) method of IReplacingCallback and assign the implementation to FindReplaceOptions.ReplacingCallback; each match calls back with a ReplacingArgs describing the match, and the return value of the callback — a ReplaceAction value of Replace, Skip, or Stop — decides what happens to it.
Combining and Cloning Documents
Document.AppendDocument(srcDoc, importFormatMode) appends the full content of one Document onto the end of another. Its importFormatMode argument — an ImportFormatMode value of UseDestinationStyles, KeepSourceFormatting, or KeepDifferentStyles — decides how style conflicts between the source and destination are resolved, and the Document.AppendDocument(srcDoc, importFormatMode, importFormatOptions) overload adds finer control through ImportFormatOptions (ImportFormatOptions.KeepSourceNumbering, ImportFormatOptions.IgnoreHeaderFooter, ImportFormatOptions.MergePastedLists, and similar flags). To insert another document’s content at a specific position instead of the end, DocumentBuilder.InsertDocument(srcDoc, importFormatMode, importFormatOptions) does the equivalent from the builder’s current cursor position, and DocumentBuilder.InsertDocumentInline(srcDoc, importFormatMode, importFormatOptions) inserts the same content without adding a paragraph or section break at the insertion point. Document.ImportNode(srcNode, isImportChildren) copies one node — optionally with its children — from a different document so it can be appended into the current one, and the Node.Clone(isCloneChildren) method available on any node duplicates structure within the same document, such as reusing a Section as a template for repeated content.
Bookmarks and Document Variables
The Range.Bookmarks property exposes a BookmarkCollection of named anchors within that range. DocumentBuilder.StartBookmark(bookmarkName) and EndBookmark(bookmarkName) mark a bookmark’s extent while building; the BookmarkCollection indexer, bookmarks[name], retrieves one later for DocumentBuilder.MoveToBookmark(bookmarkName) navigation or for reading Bookmark.Text. Separately, Document.Variables — a VariableCollection — stores arbitrary name/value string pairs directly on the document itself, useful for carrying small pieces of state through a document-generation pipeline without adding visible content.
Quick Start
Build the library from source — a NuGet package has not been published yet:
git clone https://github.com/aspose-words-foss/Aspose.Words-FOSS-for-.NET.git
cd Aspose.Words-FOSS-for-.NET
dotnet build Aspose.Words.sln -c Release
With a project reference to Aspose.Words.csproj in place, the shortest path to a document follows the same pattern used throughout this post: construct a Document, wrap it in a DocumentBuilder, call Write() or Writeln() to add text — formatting set on the builder’s Font and ParagraphFormat properties carries into every subsequent write — and call Document.Save(fileName) to write it out as .docx, .docm, .dotx, .dotm, Flat OPC, Markdown, or plain text. To edit an existing file instead of starting from scratch, load it directly with new Document(fileName), move the builder to a specific location with MoveToBookmark(), MoveToParagraph(), or MoveTo(node), and continue writing from there.
Supported Formats
| Format | Extension | Read | Write |
|---|---|---|---|
| DOCX | .docx | ✓ | ✓ |
| DOCM | .docm | ✓ | ✓ |
| DOTX | .dotx | ✓ | ✓ |
| DOTM | .dotm | ✓ | ✓ |
| Flat OPC (all variants) | (various) | ✓ | ✓ |
| Markdown | .md | ✓ | ✓ |
| Text | .txt | ✓ | ✓ |
The node tree described above is available the same way regardless of which of these formats a document was loaded from or will be saved to — the DOM is format-independent internally. What is not included in this edition is anything that depends on page layout: no PDF, XPS, or image export, and no printing, so layout-dependent field values such as page numbers evaluate to placeholders rather than being computed. The additional format converters (DOC, RTF, ODT, HTML, EPUB, MHTML, MOBI, AZW3, and WordML) are not read or written, and mail merge execution, LINQ Reporting, and document comparison are not included. Developers who need those capabilities can move to the commercial Aspose.Words for .NET without rewriting the DOM code shown here, since both editions share the same underlying API.
Open Source & Licensing
Aspose.Words FOSS for .NET is released under the MIT license, free for commercial and personal use with no royalties or redistribution restrictions. The source, including the Document, DocumentBuilder, and node-tree implementation described above, is available in the Aspose.Words FOSS for .NET repository on GitHub.