Aspose.Words文档对象模型(DOM)

Aspose.Words文档对象模型(DOM)是Word文档的内存表示形式。 Aspose.WordsDOM允许您以编程方式读取、操作和修改Word文档的内容和格式。

本节介绍Aspose.WordsDOM的主要类及其关系。 通过使用Aspose.WordsDOM类,您可以获得对文档元素和格式的编程访问。

创建文档对象树

当一个文档被读入Aspose.WordsDOM时,就会构建一个对象树,源文档的不同类型的元素都有自己的DOM树对象和各种属性。

构建文档节点树

当Aspose.Words将Word文档读入内存时,它会创建表示各种文档元素的不同类型的对象。 文本、段落、表或节的每次运行都是一个节点,甚至文档本身也是一个节点。 Aspose.Words为每个文档节点类型定义一个类。

Aspose.Words中的文档树遵循复合设计模式:

  • 所有节点类最终都派生自Node类,该类是Aspose.Words文档对象模型中的基类。
  • 可以包含其他节点(例如SectionParagraph)的节点派生自CompositeNode类,而CompositeNode类又派生自Node类。

下图显示了Aspose.Words文档对象模型(DOM)的节点类之间的继承。 抽象类的名称用斜体表示。

aspose-words-dom

让我们来看一个例子。 下图显示了具有不同类型内容的Microsoft Word文档。

document-example

将上述文档读入Aspose.WordsDOM时,将创建对象树,如下面的模式所示。

document-example-dom

Document, Section, Paragraph, Table, Shape, Run, 图上的所有其他椭圆都是表示Word文档元素的Aspose.Words对象。

获取Node类型

虽然Node类足以将不同的节点彼此区分开来,但Aspose.Words提供了NodeType枚举来简化一些API任务,例如选择特定类型的节点。

每个节点的类型可以使用NodeType属性获得。 此属性返回NodeType枚举值。 例如,Paragraph类表示的段落节点返回NodeType.ParagraphTable类表示的表节点返回NodeType.Table

下面的示例演示如何使用NodeType枚举获取节点类型:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-Java
Document doc = new Document();
// Returns NodeType.Document
int type = doc.getNodeType();

文档树导航

Aspose.Words将文档表示为节点树,使您能够在节点之间导航。 本节介绍如何在Aspose.Words中浏览和导航文档树。

当您在文档资源管理器中打开前面介绍的示例文档时,节点树的显示与Aspose.Words中所表示的完全相同。

document-in-document-explorer

文档节点关系

树中的节点之间具有关系:

  • 包含另一个节点的节点是parent.
  • 所述父节点包含的节点为同一父节点的child.个子节点为sibling个节点。
  • root节点始终是Document节点。

可以包含其他节点的节点派生自CompositeNode类,所有节点最终派生自Node类。 这两个基类为树结构导航和修改提供了常用的方法和属性。

下面的UML对象图显示了示例文档的几个节点及其通过父、子和兄弟属性相互之间的关系:

document-nodes-relationships

文档是节点所有者

节点始终属于特定文档,即使它刚刚创建或从树中删除,因为重要的文档范围结构(如样式和列表)存储在Document节点中。 例如,不可能有一个没有DocumentParagraph,因为每个段落都有一个为文档全局定义的指定样式。 创建任何新节点时使用此规则。 将新的Paragraph直接添加到DOM需要传递给构造函数的document对象。

使用DocumentBuilder创建新段落时,构建器始终具有通过DocumentBuilder.Document属性链接到它的Document类。

下面的代码示例显示,在创建任何节点时,始终定义将拥有该节点的文档:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-Java
// Open a file from disk.
Document doc = new Document();
// Creating a new node of any type requires a document passed into the constructor.
Paragraph para = new Paragraph(doc);
// The new paragraph node does not yet have a parent.
System.out.println("Paragraph has no parent node: " + (para.getParentNode() == null));
// But the paragraph node knows its document.
System.out.println("Both nodes' documents are the same: " + (para.getDocument() == doc));
// The fact that a node always belongs to a document allows us to access and modify
// properties that reference the document-wide data such as styles or lists.
para.getParagraphFormat().setStyleName("Heading 1");
// Now add the paragraph to the main text of the first section.
doc.getFirstSection().getBody().appendChild(para);
// The paragraph node is now a child of the Body node.
System.out.println("Paragraph has a parent node: " + (para.getParentNode() != null));

父节点

每个节点都有一个由ParentNode属性指定的父节点。 在以下情况下,节点没有父节点,即ParentNode为null:

  • 节点刚刚创建,尚未添加到树中。
  • 节点已从树中删除。
  • 这是根Document节点,它总是有一个空的父节点.

您可以通过调用Remove方法从其父节点中删除节点。下面的代码示例演示如何访问父节点:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-Java
// Create a new empty document. It has one section.
Document doc = new Document();
// The section is the first child node of the document.
Node section = doc.getFirstChild();
// The section's parent node is the document.
System.out.println("Section parent is the document: " + (doc == section.getParentNode()));

子节点

访问CompositeNode子节点的最有效方法是通过FirstChildLastChild属性分别返回第一个和最后一个子节点。 如果没有子节点,这些属性返回null

CompositeNode

如果节点没有子节点,则ChildNodes属性返回一个空集合。 您可以使用HasChildNodes属性检查CompositeNode是否包含任何子节点。

下面的代码示例演示如何使用ChildNodes集合提供的枚举器枚举CompositeNode的立即子节点:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-Java
Document doc = new Document(dataDir + "Document.doc");
Paragraph paragraph = (Paragraph) doc.getChild(NodeType.PARAGRAPH, 0, true);
NodeCollection children = paragraph.getChildNodes();
for (Node child : (Iterable<Node>) children) {
// Paragraph may contain children of various types such as runs, shapes and so on.
if (child.getNodeType() == NodeType.RUN) {
// Say we found the node that we want, do something useful.
Run run = (Run) child;
System.out.println(run.getText());
}
}

下面的代码示例演示如何使用索引访问枚举CompositeNode的立即子节点:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-Java
Document doc = new Document(dataDir + "Document.doc");
Paragraph paragraph = (Paragraph) doc.getChild(NodeType.PARAGRAPH, 0, true);
NodeCollection children = paragraph.getChildNodes();
for (int i = 0; i < children.getCount(); i++) {
Node child = children.get(i);
// Paragraph may contain children of various types such as runs, shapes and so on.
if (child.getNodeType() == NodeType.RUN) {
// Say we found the node that we want, do something useful.
Run run = (Run) child;
System.out.println(run.getText());
}
}

同级节点

您可以分别使用PreviousSiblingNextSibling属性获取紧接在特定节点之前或之后的节点。 如果节点是其父节点的最后一个子节点,则NextSibling属性为null。 相反,如果节点是其父节点的第一个子节点,则PreviousSibling属性为null

下面的代码示例演示如何高效地访问复合节点的所有直接和间接子节点:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-Java
public static void main(String[] args) throws Exception {
String dataDir = Utils.getSharedDataDir(ChildNodes.class) + "DocumentObjectModel/";
recurseAllNodes(dataDir);
}
public static void recurseAllNodes(String dataDir) throws Exception {
// Open a document
Document doc = new Document(dataDir + "Node.RecurseAllNodes.doc");
// Invoke the recursive function that will walk the tree.
traverseAllNodes(doc);
}
/**
* A simple function that will walk through all children of a specified node
* recursively and print the type of each node to the screen.
*/
public static void traverseAllNodes(CompositeNode parentNode) throws Exception {
// This is the most efficient way to loop through immediate children of a node.
for (Node childNode = parentNode.getFirstChild(); childNode != null; childNode = childNode.getNextSibling()) {
// Do some useful work.
System.out.println(Node.nodeTypeToString(childNode.getNodeType()));
// Recurse into the node if it is a composite node.
if (childNode.isComposite())
traverseAllNodes((CompositeNode) childNode);
}
}

对子节点和父节点的类型化访问

到目前为止,我们已经讨论了返回基类型之一的属性–NodeCompositeNode。 但有时在某些情况下,您可能需要将值转换为特定的节点类,例如RunParagraph。 也就是说,在使用复合Aspose.WordsDOM时,您无法完全摆脱铸造。

为了减少强制转换的需要,大多数Aspose.Words类提供了提供强类型访问的属性和集合。 类型化访问有三种基本模式:

类型化属性只是有用的快捷方式,有时提供比从Node.ParentNodeCompositeNode.FirstChild继承的泛型属性更容易的访问。

下面的代码示例演示如何使用类型化属性访问文档树的节点:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-Java
Document doc = new Document();
// Quick typed access to the first child Section node of the Document.
Section section = doc.getFirstSection();
// Quick typed access to the Body child node of the Section.
Body body = section.getBody();
// Quick typed access to all Table child nodes contained in the Body.
TableCollection tables = body.getTables();
for (Table table : tables) {
// Quick typed access to the first row of the table.
if (table.getFirstRow() != null)
table.getFirstRow().remove();
// Quick typed access to the last row of the table.
if (table.getLastRow() != null)
table.getLastRow().remove();
}