本章将介绍代码如何利用ZLTextPlainModel类来分别处理.xhtml文件中的文本信息与标签信息。 html
本章涉及的核心类是ZLTextPlainModel类、ZLTextWritablePlainModel类、CachedCharStorage类、XHTMLTagAction接口实现类 前端
.xhtml文件中包含着两种信息:文本信息与标签信息。咱们须要先正确解析出标签信息表明的结构,才能正确得将文本信息显示在屏幕上。 linux
举个例子:(这个例子是三体1中的文本) c++
咱们须要让程序知道这里有四种标签(h1标签、h2标签、b标签、p标签),每种标签表明了不一样的格式。程序必须正确显示出不一样标签的格式,才能让用户看到正常的文本信息。 程序员
在正式开始介绍对.xhtml文件中的文本信息与标签信息的处理流程以前,咱们有必要先来介绍下流程中涉及的三个核心类:ZLTextWritablePlainModel类、CachedCharStorage类、XHTMLTagAction接口实现类 sql
ZLTextWritablePlainModel类是ZLTextPlainModel类的子类,这个类中有三个int数组与一个CachedCharStorage类。 数据库
myStartEntryIndices属性指向的int数组记录了每一个段落具体在CachedCharStorage类内部的哪个char数组里面; 数组
myStartEntryOffsets属性指向的int数组记录了每一个段落从CachedCharStorage类内部char数组的哪一个位置开始; 服务器
myParagraphLengths属性指向的int数组记录每一个段落在CachedCharStorage类内部char数组中占据多少长度; 数据结构
最后,myStorage属性指向的CachedCharStorage类内部的char数组则是实际存储文本信息与标签信息的地方
PS:FBReader程序中一组p标签就表明一个段落(Paragraph)。
这个类中有两个重要的属性:myArray属性、myBlockSize属性
myArray属性指向一个由char数组组成的ArrayList(char数组都设定为软引用WeakReference,保证了虚拟机会回收这些char数组,不会占用过多的内存)。这些char数组里面的元素就表明这.xhtml的文本信息与标签信息。
myBlockSize属性指向一个int。char数组的长度最长不会超过这个长度(65536),一旦超过这个长度,代码就会新建一个char数组,同时就的数组会被持久化以便之后再用。
epub文件中有不少标签,不一样的标签表明不一样的不一样的结构,因此FBReader也为不一样的标签提供了不一样的处理类。这些处理类都是XHTMLTagAction接口的实现类。
标签通常都是成对出现的,XHTMLTagAction接口中的两个方法分别就对应了
具体哪些类对应哪些标签,是由XHTMLReader类中fillTagTable方法定义的。
介绍完三个核心类,咱们就能够正式开始介绍对.xhtml文件中的文本信息与标签信息的处理流程了。
咱们先以处理一个标签对(包含起始标签和结束标签)的流程为例。在利用for循环迭代标签对转换成的char数组的过程当中ZLXMLParser类的doIt方法会对如下的节点调用XHTMLReader类进行操做
起始标签右边的“<”:
记录char数组的偏移量,调用ZLXMLReader接口characterDataHandlerFinal方法(XHTMLReader类并未实现该方法,故能够忽略)
起始标签右边的“>”:
记录char数组的偏移量,取出两次偏移量当中的内容,获得当前标签的标签名。
ZLXMLParser类的processStartTag方法 -> XHTMLReader类的startElementHandler方法 -> XHTMLTagParagraphWithControlAction类的doAtStart方法
结束标签左边的“<”:
记录char数组的偏移量,取出两次偏移量当中的内容,获得标签当中的文本信息
XHTMLReader类的characterDataHandler方法 -> BookReader类的addData方法
将标签当中的文本信息存储到BookReader类的myTextBuffer属性
结束标签右边的“>”:
ZLXMLParser类的processEndTag方法 -> XHTMLReader类的endElementHandler方法 -> 标签名对应XHTMLTagAction接口实现类的doAtEnd方法
将BookReader类中的myTextBuffer属性
下面咱们再以《三体1》中的一段文本做为例子来详细介绍下这个流程:
记录char数组的偏移量
记录char数组的偏移量,取出两次偏移量当中的内容,获得当前标签的标签名。
ZLXMLParser类的processStartTag方法 -> XHTMLReader类的startElementHandler方法 -> XHTMLTagParagraphWithControlAction类的doAtStart方法
doAtStart方法
doAtStart方法会调用了两个方法BookReader类的pushKind方法与beginParagraph方法
BookReader类的pushKind方法:
这个方法会在myKindStack属性中添加FBTextKind.H1(31),而其实myKindStack属性中已经有FBTextKind.REGULAR(0),这个属性是在OEBBookReader类的readBook方法中设置的。
BookReader类的beginParagraph方法
这个方法调用了ZLTextWritablePlainModel类的createParagraph方法,而后用for循环迭代myKindStack属性并调用ZLTextWritablePlainModel类的addControl方法
createParagraph方法更新了ZLTextWritablePlainModel类中的三个属性,之后会依靠这三个属性在CachedCharStorage类的char数组中快速定位某一个段落
addControl方法往CachedCharStorage类中的char数组加入了两个能够表明标签的常量
PS:每次调用addControl方法都会加入ZLTextParagraph.Entry.CONTROL(3)这个常量,这个常量是一种标示。相似的标示还有常量ZLTextParagraph.Entry.TEXT(1),咱们会在下一章用到这两种变量。具体这两个标示是如何发挥做用的,请参考第十章中的内容。
记录char数组的偏移量,取出两次偏移量当中的内容,获得标签当中的文本信息
XHTMLReader类的characterDataHandler方法,将标签当中的文本信息存储到myTextBuffer属性
ZLXMLParser类的processEndTag方法 -> XHTMLReader类的endElementHandler方法 -> XHTMLTagParagraphWithControlAction类的doAtEnd方法
doAtEnd方法会调用ZLTextWritablePlainModel类的addText方法,在CachedCharStorage类中的char数组中加入三种信息:
1、常量ZLTextParagraph.Entry.TEXT(1),这是一种标示,相似常量ZLTextParagraph.Entry.CONTROL(3)
2、标签间文本信息的长度
3、标签间的实际文本信息
H2标签、P标签与H1标签基本上是同样,惟一的区别在于在起始标签右边的“>”触发的addControl方法中加入不一样的常量,而这个变量实际上是在FBTextKind接口中定义的。
H2标签会加入FBTextKind.REGULAR(0)以及FBTextKind.H2(32),P标签则只会加入FBTextKind.REGULAR(0)。每次加入这些常量的时候都会同时加入做为标示的常量ZLTextParagraph.Entry.TEXT(1)。
B标签与其余三个标签不一样,这个标签会触发两次addControl方法,只是两次的参数不一样。
这里须要补充下CachedCharStorage类中新增char数组的流程,咱们在介绍CachedCharStorage类的时候,曾经讲过:“BookModel类中的myBlockSize属性指向一个int。char数组的长度最长不会超过这个int(65536),一旦超过这个长度,代码就会新建一个char数组,同时就的数组会被持久化以便之后再用。”
新增char数组的工做由CachedCharStorage的createNewBlock方法完成
将旧的char数组持久化的工做是在CachedCharStorage类的freezeLastBlock方法完成的。
在sd卡上的Books/.FBReader这个文件夹里面,咱们能够找到这些持久化了的文件。
这个位置是在BookModel的构建函数中用过Paths类得到的。
其实咱们能够尝试把持久化char数组的方法改成utf8的编码,而后在把获得的文件后缀名改为.txt
打开这个txt文件,咱们就能够看到下面这样的数据
对比原先的xml文件,那些奇怪的符号就表明了标签信息
好,至此为止,咱们将.xhtml文件中的文本信息与标签信息存储到了ZLTextPlainModel中。而要想让程序最终用中正确的格式显示文本信息,还须要配合以后第八章(定位指定段落)以及第九章(显示.html文件)的内容才能让用户看到格式正确的文本。
最后,插几句题外话,写一些本身的思考:
FBReader使用char数组的形式来存储xml文件内容与结构信息,而后依靠记录每一个段落在char数组中具体位置的int数组快速获取指定段落在char数组中的部分。选择数组是ok的,在数组中定位到某一个部分,速度仍是比较快的,可是同时,也由于使用了数组这个数据结构,程序也是相对来讲比较占用内存的。通常来讲,电子书都是从一整段数据中按顺序取出一部分数据显示在屏幕上,这种业务需求其实用树的数据结构也是很是合适的。而说到树的数据结构,其实Android手机中正好有一个现成的sqlite数据库能够提供这种树的数据结构。咱们能够试想一下,若是改用sqlite数据库来存储和检索xml文件的内容与结构信息的话,相比数组会有怎样的好处。我想最起码会有三个好处:第1、节省手机的内存,sqlite数据库作好索引以后,无需像数组那样把数据所有装进内存后才能检索。这样一来,程序就节省了内存;2、方便跨平台的开发,sqlite支持linux、IOS以及HTML5,若是使用sqlite来做为存储和检索xml文件的方式,那么Android程序员、IOS程序员以及pc前端程序员只须要根据约定的sql语句就能完成开发,而没必要独立开发三种语言,或者用c或c++另外再开发一个底层库;3、方便与服务器端对接,当须要对客户端用户的阅读记录进行收集与分析的时候,若是客户端与服务端使用同一种或相近的sql结构的话,那么对接的难度就会下降不少。