如何在 .NET 中使用节点
本指南展示了如何直接在 Aspose.Words 开源软件用于 .NET, 使用 Node and CompositeNode 基类,这些 Document, Section, Paragraph, Table, 以及所有其他文档元素都是基于它们构建的。它涵盖了通过 NodeType, 以编程方式添加和删除节点,响应结构变化,并使用 DocumentVisitor 进行类型分发的遍历。
分步指南
步骤 1:安装包
Aspose.Words FOSS for .NET 尚未发布 NuGet 包——请改为从源码构建库。克隆 https://github.com/aspose-words-foss/Aspose.Words-FOSS-for-.NET.git,然后使用 .NET SDK 在 Release 配置下构建 Aspose.Words.sln,并在项目中引用生成的 Aspose.Words 程序集。该库目标为 .NET Standard 2.0,因此可在 .NET Framework 4.6.2+ 和 .NET 6/8/10 上运行。
步骤 2:导入所需类
添加一个 using Aspose.Words; 指令——Node、CompositeNode、NodeType、NodeCollection、NodeList、INodeChangingCallback 和 DocumentVisitor 都位于 Aspose.Words 命名空间中,与 Document 同在。
步骤 3:了解节点和 CompositeNode 基类
文档树中的每个对象都派生自抽象的 Node 类,该类公开 NodeType(标识节点的类型)、ParentNode(所在的 CompositeNode)、Document(所属文档),以及用于在同一级别节点之间移动的 PreviousSibling / NextSibling。在进行类型转换之前,检查 Node.IsComposite 以确定给定节点是否可以包含子节点。CompositeNode 继承自 Node 用于容器元素——FirstChild、LastChild 和 Count 描述其直接子节点,HasChildNodes 则报告是否存在子节点。
步骤 4:按类型查询子节点
调用 CompositeNode.GetChildNodes(nodeType, isDeep) 以检索给定 NodeType 的所有子节点,传入 isDeep: true 可搜索整个子树而非仅限直接子节点,或传入 NodeType.Any 以匹配所有类型的节点而不考虑具体种类。CompositeNode.GetChild(nodeType, index, isDeep) 按索引检索单个匹配的子节点。对于结构节点类型(Document、Section、Body、Paragraph、Table、Row、Cell)、内联内容类型(Run、Shape、Comment)以及字段机械类型(FieldStart、FieldSeparator、FieldEnd,它们标记 Word 字段的边界并在运行流中切换文本),请传入特定的 NodeType 值以指明您要查找的内容。
步骤 5:在子树上运行 XPath 查询
CompositeNode 还实现了 SelectNodes(xpath) 和 SelectSingleNode(xpath),用于在子树上进行基于 XPath 的查询,返回的匹配项以 NodeList 形式(或单个 Node)呈现。当 XPath 查询超出任务需求时,CompositeNode.GetEnumerator() 支持对节点直接子项的普通 foreach。
步骤 6:添加、移动和删除节点
要修改树结构,调用 AppendChild(newChild) 或 PrependChild(newChild) 在容器的子节点开头或结尾添加节点,使用 InsertBefore(newChild, refChild) 或 InsertAfter(newChild, refChild) 在现有子节点相对位置插入。RemoveChild(oldChild) 删除单个子节点,RemoveAllChildren() 一次性清除所有子节点;CompositeNode.IndexOf(child) 报告子节点的位置。在 Node 本身上,Clone(isCloneChildren) 复制节点——可选地包括其子节点——而 Remove() 将节点从其父节点分离。
步骤 7:在变更实时集合之前实现节点实体化
在 foreach 循环遍历集合期间修改该集合——无论它是 NodeCollection 还是 GetChildNodes 的结果——都会抛出错误。首先对集合调用 ToArray() 将节点实体化为固定数组,然后在遍历该数组时调用 Remove() 或对树进行其他更改。
步骤 8:按文档顺序扫描子树
Node.NextPreOrder(rootNode) 和 PreviousPreOrder(rootNode) 按文档顺序遍历树,受给定根节点的限制——这是一种常见的方式,可在不编写递归遍历代码的情况下逐节点扫描整个子树。
步骤 9:使用 DocumentVisitor 处理每种节点类型
为了以结构化方式处理许多不同的节点类型,继承抽象的 DocumentVisitor 类,并为相关的节点类型覆盖 Visit*Start / Visit*End 方法——例如 VisitParagraphStart / VisitParagraphEnd、VisitSectionStart / VisitSectionEnd、VisitBodyStart / VisitBodyEnd——然后在子树根节点上调用 Node.Accept(visitor) 来执行。这是导出、转换或检查文档的标准模式,无需为每种使用场景编写自定义递归遍历代码。
步骤 10:对其他代码的结构性更改作出响应
当代码需要对其他代码(包括内置操作)所做的插入或删除做出响应——而不仅是直接进行的更改时,实现 INodeChangingCallback。其 NodeInserting(args)、NodeInserted(args)、NodeRemoving(args) 和 NodeRemoved(args) 方法各自接收一个 NodeChangingArgs,描述受影响的 Node、其旧父节点和新父节点,以及发生的 NodeChangingAction(Insert 或 Remove)。
常见问题及解决方案
将 Node 强制转换为 CompositeNode 会抛出无效转换错误 该节点是叶子类型——例如 Run or FieldStart ——它不能包含子节点。检查 Node.IsComposite 首先,或使用 NodeType 以确认节点在强制转换前是容器类型。
GetChildNodes 在 isDeep 为 false 时返回直接子节点 isDeep 被留下 false (或在重载默认浅层时省略)。传递 isDeep: true to GetChildNodes() 以搜索整个子树而不是直接子节点。
在遍历集合时修改它会抛出错误 节点在 a 内部被添加或删除 foreach 在 a 上 NodeCollection 或 … 的结果 GetChildNodes。调用 ToArray() 首先将要修改的节点具体化,然后在变更树结构时遍历该数组。
一个 DocumentVisitor 子类不会在某些内容上被调用 访问者是通过 Accept() 在一个实际上不包含目标内容的节点上,或相应的 Visit* 方法未被覆盖。调用 Accept() 在包含内容的祖先节点上,并覆盖每个 Visit*Start / Visit*End 与正在处理的节点类型相关的配对。
INodeChangingCallback 未收到关于更改的通知 回调仅报告在 Document 它被注册的对象上的更改。确认回调已附加到正确的 Document 实例上,在更改发生之前,而不是之后。
常见问题解答
Node 和 CompositeNode 有什么区别?
Node 是所有文档元素派生的基类。CompositeNode 扩展自 Node,用于可以包含子元素的元素(例如 Body、Paragraph 和 Table);像 Run 这样的叶子元素直接派生自 Node,且不能包含子元素。
我如何在文档中查找所有特定类型的节点?
在作为搜索根节点的节点上调用 CompositeNode.GetChildNodes(nodeType, isDeep) —— 通常是 Document 本身 —— 传入目标 NodeType 和 isDeep: true,以搜索整个子树,而不仅仅是直接子节点。
在迭代时,我如何安全地删除节点?
首先将要删除的节点收集到数组中 —— 例如使用 NodeCollection.ToArray() —— 然后遍历该数组,对每个节点调用 Remove(),而不是在 foreach 正在迭代时修改实时集合。
什么是 FieldStart、FieldSeparator 和 FieldEnd 节点?
它们是标记嵌入在运行流中的 Word 域机制的 NodeType 值:FieldStart 和 FieldEnd 绑定该域,而 FieldSeparator 将域的代码与当前显示的结果分开。
我如何处理文档中的每个段落、表格和 run?
为 DocumentVisitor 创建子类,覆盖所需节点类型的 Visit*Start / Visit*End 方法,然后在 Document(或任何子树根)上调用 Accept(visitor) 来执行它。