Aspose.words编程指南之DOM树再识,各层结构之间的关系

来源:互联网 发布:集成电路板编程 编辑:程序博客网 时间:2024/06/05 02:34

转载请注明 http://blog.csdn.net/sinat_30276961/article/details/48155223

上一篇Aspose.words编程指南之DOM树结构初识,Node类继承关系及说明我运行了第一个简单的应用,并且讲述了它的加载、保存和转换方式。然后从它设计理念,讲解了DOM的概念。这一篇将会继续讲解DOM基本概念和节点之间获取方式。我相信,如果你仔细看了这篇博客,会对Aspose.words的DOM结构有个深入的了解。

Document Object Model

结构图

上一篇内容中,我们从一个例子入手,大概了解了DOM树的概念,并讲解了Node类的继承关系和概念。这里,我们从结构图宏观的查看下它们之间的关系。

Document和Section

在上一篇中,我们知道:

Document是文档树的根节点,提供访问整个文档的入口
Section对象对应文档中的一节

也就是说,Document里包含一个或多个Section。

这里,我们从一张图,来看看这两者之间的关系。

这里写图片描述

这张图很清晰的描述了Document和Section在文档中扮演的角色。我们来罗列一下:

1.一个Document里包含一个或多个Section。
2.一个Section里包含一个Body node和0个或多个HeaderFooter nodes
3.Body和HeaderFooter node都包含0个或多个Block-level Nodes。
4.一个Document里可以包含0个或1个GlossaryDocument。

一般来说,一个word文档包含一个或多个Sections。一个Section可以定自己的页面大小,边距,文字方向排布,文本栏数,也可以定义headers 和footers。一个文档的Sections由section breaks分隔。

一个Section包含主要文本和头注、脚注。这些统一被称为”Stories”。在Aspose.words里,Section node包含Story node : Body & HeaderFooter。主要文本被管理在Body里, 头注和脚注被管理在HeaderFooter里。

任何文本story包含一个或多个paragraph和一个或多个table。这部分被称为Block-level nodes。

此外,一个Document可以包含一个glossary document。一个glossary document保存0个或多个building blocks。也就是说一个Document可以包含一个GlossaryDocument。一个GlossaryDocument包含0个或多个BuildingBlock,每个BuildingBlock包含0个或多个Section,并能管理这些Section做插入,拷贝和移除等动作。

Block-level Nodes

上面说到,任何文本story包含一个或多个paragraph和一个或多个table。这部分就是Block-level Nodes。

我们先贴张图直观的看下里面包含了什么。

这里写图片描述

我们来分析一下上面的结构图:

1.Block-level Nodes在一个DOM树里可以出现在很多节点里。这里罗列了八个(Body, HeaderFooter, Footnote, Comment, Shape, Cell, CustomXmlMarkup, StructuredDocumentTag)

2.Block-level Nodes里最重要的两个是:table和paragraph。

3.一个Table包含0个或多个Row-level Nodes。

4.一个Paragraph包含0个或多个Inline-level Nodes。

5.CustomXmlMarkup和StructuredDocumentTag还可以嵌套Block-level Nodes。

用通俗一点的话来说,Block-level Nodes就是文档里的一块内容,它可以包含表格和段落。表格里有一行一行的内容,段落里有内置的一行一行的内容。它还可以包含标签,标签里还可以嵌套包含一块内容。

这样讲是不是清晰了点,不过为了能更简单的查看代码,我们还是尽量用术语来定义,习惯了就好。如果实在不懂,可以查看上一篇关于Node类的定义说明。

Inline-level Nodes

我们从上面可以看到,一个Paragraph包含0个或多个Inline-level Nodes。这里,我们来看看Inline-level Nodes。

照例先上张图。

这里写图片描述

哇~这一层里有这么多成员!没事,我们慢慢来看。

我们继续总结一下上面的结构图:

1.Paragraph,Smarttag,CustomXmlMarkup,StructuredDocumentTag这几个节点都可以包含Inline-level节点。这里面,最经常包含Inline-level节点的是Paragraph。

2.Paragraph可以包含多个run节点,每个run节点的格式可以都不一样。

3.Paragraph可以包含书签,BookmarkStart和BookmarkEnd。

4.Paragraph可以包含注释,CommentRangeStart、CommentRangeEnd、Comment和Footnote。

5.Paragraph可以包含Word fields(这一块不熟,如果word用到过域应该会更容易理解这块),FieldStart、FieldSeparator、FieldEnd和FormField

6.Paragraph可以包含shapes, drawings, images等,通过Shape和GroupShape节点。

7.Paragraph可以包含标签,SmartTag, CustomXmlMarkup和StructuredDocumentTag

GroupShape可以包含Shape或者继续嵌套GroupShape;Shape,Footnode,Comment可以包含Block-level节点;

好吧,内嵌层的成员很多,部分成员可以继续嵌套内嵌层的成员,也有部分成员可以内嵌块层。关系还蛮复杂的~~

Table, Row and Cell

在块层里,我们有提到过,一个Table包含0个或多个Row-level Nodes。我们接下去看看这两者之间的关系。

照例上图先~

这里写图片描述

我们继续看图说话。

1.一个Table可以包含很多Row
2.一个Row可以包含很多Cell
3.Cell可以继续包含块层节点。尼玛,又能嵌套~
4.CustomXmlMarkup和StructuredDocumentTag这两个分别是Block-level、Row-level和Cell-level的成员。也就是说,可以在这几层不断嵌套。

Document树查看

Document Explorer

既然Aspose.words会把一个文档解析成一个DOM数,那么是否有工具可以清晰的查看某个word文档的DOM树结构呢?

我们可以自己写啊!通过解析,再添加log。这显然是个很不错的想法。不过,其实是有现成的。

我们可以去它的官网找DEMO,里面包含Document explorer。Aspose.Words for Java libs & examples。很遗憾的是,目前android端有用的例子只有可怜的一个:DocumentViewer。其功能是用来查看word文档。所以,我只能从其他平台的例子里去找了。发现.NET和java都有Document explorer,于是乎搭建环境,把java的例子跑起来看了下,效果还行。

这里写图片描述

Tree Nodes

我们再来看张UML图:

这里写图片描述

就像树形结构的特点,每个节点都处在一个树中,它会有一定的关系。

比方说图中的Node,它包含了CompositeNode和Inline,所以它相对于CompositeNode和Inline是父节点,而CompositeNode和Inline是它的子节点。CompositeNode和Inline有相同的父节点,所以它们是兄弟节点。记住,Document节点永远是一个文档DOM树的根节点。

兄弟节点之间是有先后关系的,就像上面Document explorer解析的一个文档,body下有很多paragraph,它们有先后顺序。我们再看张图:

这里写图片描述

上图也展示了兄弟节点之间的先后顺序关系。我们可以看到Body下有两个子节点:Paragraph和Table,它们的先后顺序通过数组游标确定。

只有继承自CompositeNode,才能做为父节点包含子节点;如果继承自Node,是无法包含子节点的。

接下去,我们再来看看Node节点的关系在代码里怎么体现。

Parent Node

查看一个节点的父节点,我们可以通过Node.ParentNode这个属性。如果一个Node刚创建出来,还没添加到dom树里;或者一个节点从DOM树里移除了,那么它是没有父节点的,Node.ParentNode此时为null。你可以通过在父节点调用Node.Remove来移除它的子节点。

根节点当然是没有父节点的。

如下代码展示怎么获得父节点:

// 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()));

Owner Document

这里需要强调一点的是,一个Node(节点)是永远要属于某个Document的,哪怕它是刚被创建出来还是已经被移除出DOM树。我们可以通过该节点的Node.Document查看它所属的Document。

我们通过一个例子来看看:

// 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));

上述代码,我们可以看到,在创建某个节点时,会马上传入Document对象,这样,它就保存了根节点。此时,它是没有父节点的。在后面,通过doc.getFirstSection().getBody().appendChild(para);才有了父节点。

Child Nodes

最有效的查找子节点的方式是通过CompositeNode的CompositeNode.FirstChild和CompositeNode.LastChild这两个属性,如果没有子节点,会返回null。

CompositeNode也提供了CompositeNode.ChildNodes的collection,可以方便我们遍历。

不过,我们需要注意的是CompositeNode.ChildNodes是在不断动态变化的,在每次添加或者移除时,就会更新它。关于这一块,我会在后面章节再展开讲解。

你可以通过CompositeNode.HasChildNodes这个属性,直接查看某个节点是否有子节点。

下面的例子,展示了遍历子节点的方法:

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());    }}
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());    }}

上一篇有说到NodeType,在这里使用是最合适的。

Sibling Nodes

关于兄弟节点,我先前有说过,它们是有先后顺序的。就像是家里兄弟姐妹一样,出生有先后。

那么怎么获取哥哥\姐姐或者弟弟\妹妹呢?通过Node.PreviousSibling和Node.NextSibling这两个属性。如果是最小的,那么它的Node.NextSibling就会是null;同理,如果是最大的,那么它的Node.PreviousSibling就会是null。

注意点,兄弟节点在Aspose内部是通过单链去维护的,所以Node.NextSibling比Node.PreviousSibling高效的多。

下面代码展示了如何遍历某个节点开始往下的所有的子节点

public void recurseAllNodes() throws Exception{    // Open a document.    Document doc = new Document(getMyDir() + "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 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);    }}

childNode.isComposite()这个是查看该节点是否是CompositeNode。

Typed Access to Children and Parent

可以看到,在上面的代码例子里,我们遍历的时候,获得某个节点,需要通过NodeType去判断该节点的类型,然后进行强转。

如果你不喜欢这种暴力编程方式,可以通过以下方式:

1.父节点公开了明确的子节点类型FirstXXX和LastXXX属性。比方说,Document里有Document.FirstSection和Document.LastSection。同样的,Table里也有Table.FirstRow和Table.LastRow。
2.当然,父节点也公开了子节点类型的collection,比方说,Document.Sections, Body.Paragraphs等等。
3.子节点也公开了父节点类型,比方说,Run.ParentParagraph和Paragraph.ParentSection.

如下代码展示这种方案:

// 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();}

好了,关于Aspose.words的DOM结构就讲到这里。下一篇Aspose.words编程指南之Working with Document深入讲解最核心的Node类—Document。

0 0
原创粉丝点击