简介
本指南探讨 Aspose.Words FOSS 对 .NET 如何在内存中表示 Word 文档,以及用于构建、导航和修改该表示的 API:用于顺序创作的 DocumentBuilder、用于直接结构访问的节点树(Node、CompositeNode、NodeCollection)、用于一次性处理所有节点类型的 DocumentVisitor、通过 Range 和 FindReplaceOptions 实现的查找替换,以及用于合并和克隆文档的方法。Where 公告帖子 介绍了整个库,而本篇则专注于文档对象模型(DOM)——API 表面中最大的单一区域——以及其各部分如何协同工作。
Aspose.Words FOSS for .NET 在 MIT 许可证下发布,且没有本机依赖;它面向 .NET Standard 2.0,因此此处描述的 DOM 可在 .NET Framework 4.6.2+ 以及 .NET 6、8、10 上使用,支持 Windows、Linux 和 macOS。通过 NuGet 安装,或从源码构建——请参见下文的快速入门。
下面描述的每项任务——从头编写报告、重构现有的 .docx 文件、遍历其内容进行分析,或将多个文档合并为一个——都始于相同的少数基础类型:Document、DocumentBuilder,以及其下的 Node 层次结构。
文档对象模型
使用 DocumentBuilder 构建文档
DocumentBuilder 是一种基于光标的写入器,位于 Document 之上,按顺序插入内容。Write(text) 和 Writeln(text) 在当前位置添加文本;InsertParagraph() 和 InsertBreak(breakType) 添加段落以及页面/列/节分隔符。格式设置是有状态的:构建器的 Font、ParagraphFormat、ListFormat 和 PageSetup 属性会应用于随后写入的所有内容,PushFont() / PopFont() 用于保存和恢复当前字体状态,从而无需手动撤销临时样式更改。导航方法可以将光标重新定位到现有文档中的任意位置——MoveToDocumentStart()、MoveToDocumentEnd()、MoveToSection(sectionIndex)、MoveToBookmark(bookmarkName)、MoveToParagraph(paragraphIndex, characterIndex),以及通用的 MoveTo(node)——并且 DocumentBuilder.CurrentNode、DocumentBuilder.CurrentParagraph 和 DocumentBuilder.CurrentSection 会报告构建器当前所在的位置。对于只读文本提取且不需要完整 DOM 的情况,PlainTextDocument 提供了一条更轻量的路径:new PlainTextDocument(fileName) 只暴露 PlainTextDocument.Text 属性以及文档的内置和自定义属性。
节点树:Sections、Paragraphs、Runs 和 Tables
A Document 是一个以文档本身为根的 Node 对象树:Section → Body → Paragraph → Run 用于正文文本,出现表格时会分支为 Table → Row → Cell。CompositeNode 是所有容器节点的基类,公开 CompositeNode.FirstChild、CompositeNode.LastChild、Node.NextSibling 和 Node.PreviousSibling 用于直接遍历,GetChildNodes(nodeType, isDeep) 用于收集任意深度的指定 NodeType(Paragraph、Run、Table 等)的所有节点,GetChild(nodeType, index, isDeep) 用于按索引查找。AppendChild(newChild)、InsertBefore(newChild, refChild)、InsertAfter(newChild, refChild) 和 RemoveChild(oldChild) 直接修改树结构。CompositeNode.SelectNodes(xpath) 和 SelectSingleNode(xpath) 使用 XPath 样式表达式选择节点,而不是手动遍历树;对于非常大的文档,Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode) 可以在不使用递归的情况下一次遍历一个节点。
DocumentVisitor:一次遍历处理每种节点类型
子类化 DocumentVisitor 是在不硬编码文档结构的情况下处理文档的方式。它为每种复合节点类型定义配对的 Visit-Start / Visit-End 方法,包括 DocumentVisitor.VisitSectionStart、DocumentVisitor.VisitParagraphStart、DocumentVisitor.VisitTableStart、DocumentVisitor.VisitRowStart、DocumentVisitor.VisitCellStart、DocumentVisitor.VisitBookmarkStart —— 每个都有对应的 End 回调 —— 以及用于单个文本运行的 DocumentVisitor.VisitFieldStart、DocumentVisitor.VisitFieldSeparator、DocumentVisitor.VisitFieldEnd 和 DocumentVisitor.VisitRun。在 Document、Section 或任何其他节点上调用 Accept(visitor) 可对该节点及其所有子节点运行访问器;AcceptStart(visitor) 和 AcceptEnd(visitor) 仅对单个复合节点触发入口和退出回调。每个 Visit 方法返回一个 VisitorAction 值,用以控制遍历的继续方式——Continue 进入子树,SkipThisNode 跳过该节点,或 Stop 完全停止。
查找和替换文本
Range.Replace(pattern, replacement) 在拥有它的 Range 上执行查找替换——可以是整个 Document、一个 Section,或任意节点的 Range 属性——搜索模式可以是字面字符串或正则表达式。Range.Replace(pattern, replacement, options) 重载接受一个 FindReplaceOptions 实例,用来控制 FindReplaceOptions.MatchCase、FindReplaceOptions.FindWholeWordsOnly、搜索方向 FindReplaceOptions.Direction(FindReplaceDirection 的 Forward 或 Backward 值)、替换文本的格式化(FindReplaceOptions.ApplyFont、FindReplaceOptions.ApplyParagraphFormat),以及保留哪些周围内容(FindReplaceOptions.IgnoreFields、FindReplaceOptions.IgnoreFootnotes、FindReplaceOptions.IgnoreFieldCodes 等 Ignore* 标志)。若需要在每次匹配时执行超出简单替换的逻辑,需实现 IReplacingCallback 的 Replacing(args) 方法并将实现分配给 FindReplaceOptions.ReplacingCallback;每次匹配都会回调一个描述匹配的 ReplacingArgs,回调的返回值——ReplaceAction 的 Replace、Skip 或 Stop——决定对该匹配的处理方式。
合并与克隆文档
Document.AppendDocument(srcDoc, importFormatMode) 将一个 Document 的全部内容追加到另一个文档的末尾。它的 importFormatMode 参数——一个 ImportFormatMode 的值,可为 UseDestinationStyles、KeepSourceFormatting 或 KeepDifferentStyles——决定了源文档和目标文档之间的样式冲突如何解决,且 Document.AppendDocument(srcDoc, importFormatMode, importFormatOptions) 重载通过 ImportFormatOptions(ImportFormatOptions.KeepSourceNumbering、ImportFormatOptions.IgnoreHeaderFooter、ImportFormatOptions.MergePastedLists 等标志)提供更细粒度的控制。要在特定位置而不是文档末尾插入另一个文档的内容,DocumentBuilder.InsertDocument(srcDoc, importFormatMode, importFormatOptions) 可以在构建器当前光标位置执行相同操作,而 DocumentBuilder.InsertDocumentInline(srcDoc, importFormatMode, importFormatOptions) 则在插入点插入相同内容且不添加段落或节分隔符。Document.ImportNode(srcNode, isImportChildren) 从不同文档复制一个节点——可选地包括其子节点——以便将其追加到当前文档中,而任意节点上可用的 Node.Clone(isCloneChildren) 方法则在同一文档内复制结构,例如将 Section 作为模板重复使用。
书签和文档变量
Range.Bookmarks 属性公开一个 BookmarkCollection,其中包含该范围内的具名锚点。DocumentBuilder.StartBookmark(bookmarkName) 和 EndBookmark(bookmarkName) 在构建时标记书签的范围;BookmarkCollection 索引器 bookmarks[name] 稍后可用于 DocumentBuilder.MoveToBookmark(bookmarkName) 导航或读取 Bookmark.Text。另外,Document.Variables — 一个 VariableCollection — 将任意名称/值字符串对直接存储在文档本身上,便于在文档生成管道中携带少量状态而不添加可见内容。
快速入门
Aspose.Words FOSS 用于 .NET 可通过 NuGet 获得:
dotnet add package Aspose.Words.FOSS改为从源码构建:
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
在项目引用了 Aspose.Words.csproj 的情况下,创建文档的最简路径遵循本文中一直使用的模式:构造一个 Document,将其包装在 DocumentBuilder 中,调用 Write() 或 Writeln() 添加文本 — 在构建器的 Font 和 ParagraphFormat 属性上设置的格式会传递到后续的每一次写入 — 然后调用 Document.Save(fileName) 将其保存为 .docx、.docm、.dotx、.dotm、Flat OPC、Markdown 或纯文本。若要编辑已有文件而不是从头开始,直接使用 new Document(fileName) 加载它,使用 MoveToBookmark()、MoveToParagraph() 或 MoveTo(node) 将构建器移动到特定位置,然后继续写入。
支持的格式
| 格式 | 扩展名 | 读取 | 写入 |
|---|---|---|---|
| DOCX | .docx | ✓ | ✓ |
| DOCM | .docm | ✓ | ✓ |
| DOTX | .dotx | ✓ | ✓ |
| DOTM | .dotm | ✓ | ✓ |
| Flat OPC (所有变体) | (各种) | ✓ | ✓ |
| Markdown | .md | ✓ | ✓ |
| Text | .txt | ✓ | ✓ |
上述描述的节点树可在相同方式下使用,无论文档是从哪种格式加载或将保存到哪种格式——内部的 DOM 与格式无关。本版不包括任何依赖页面布局的功能:不支持 PDF、XPS 或图像导出,也不支持打印,因此布局相关的字段值(例如页码)会表现为占位符而非实际计算。额外的格式转换器(DOC、RTF、ODT、HTML、EPUB、MHTML、MOBI、AZW3 和 WordML)既不读取也不写入,邮件合并执行、LINQ 报告以及文档比较也未包含。需要这些功能的开发者可以迁移到商业版 适用于 .NET 的 Aspose.Words 无需重写此处展示的 DOM 代码,因为两个版本共享相同的底层 API。
开源与许可
Aspose.Words FOSS for .NET 在 MIT 许可证下发布,可免费用于商业和个人用途,且没有版税或再分发限制。源代码,包括 Document, DocumentBuilder,,以及上述描述的节点树实现,可在以下位置获取: Aspose.Words FOSS for .NET 仓库 位于 GitHub。