李懿 Excel学习 2016-07-05
微信扫一扫
关注该公众号
终于离破解只剩2步了,我们先来看倒数第二步。
Directory Entry是一个记录了复合文档中的所有内容的目录。它的数据结构是一个红黑树,我们不必管这是个啥玩意儿,反正在这篇教程中也用不着。Directory Entry的长度为128个字节,它记录的信息如下:
图 26 Directory Entry结构
Directory Entry Name:Directory
Entry的名称,总长度为64个字节,是一个双字节的UNICODE字符类型,可以用StrConv函数进行转换。
Name Length:记录了名称的长度。2字节。
Type:Entry的类型。1字节。
0代表非法,
1代表Storage,
2代表Stream,
5代表Root Entry(即第一个目录内容)。
Color:节点的颜色。1字节。
Left Sibling ID:左兄弟节点ID。4字节。
Right Sibling ID:右兄弟节点ID。4字节。
Child ID:子节点ID。4字节。
CLSID:没啥用,一般都是0。16字节。
State Bits:没啥用,一般都是0。4字节。
Creation Time:创建时间,这个挺复杂,不介绍如何解析了。8字节。
Modify Time:修改时间,这个挺复杂,不介绍如何解析了。8字节。
Start Sector Location:若是Stream类型,则表示数据扇区的起始ID。4字节。
Stream Size:Stream的大小。LONGLONG的类型,8个字节。其实只用4个。这个属性决定了数据存储在一般的扇区还是mini扇区。当大小大于或等于4096的时候,数据存放在普通扇区,其余的存放在mini扇区。
你应该注意到了有两种类型的Directory Entry,它们是Storage和Stream。这两者的关系就和文件夹以及文件一样。Storage是文件夹,把Stream合并归类,而Stream则是实际的文件了,所有的Stream都有相应的扇区存储数据。
经过前面那么多的代码的熏陶,相信你已经掌握了基本的方法了吧。我们再来总结一下:
声明一个自定义类型来表示Directory Entry数据
按照文件头的信息读取第一个Directory Entry扇区
按照FAT表按顺序读取剩下的Directory Entry扇区
我们来看下核心代码:
Sub ReadDir(FS As Integer, FAT() As Long, DirEntry() As CFBDirecroty, SectorID As Long, SectorSize As Long)
Dim i As Long
Dim Entry As CFBDirecroty
'若SectorID为结束ID,则停止读取
If SectorID = SECTORTYPE.ENDOFCHAIN Or SectorID = SECTORTYPE.FREESECT Then
Exit Sub
End If
'定位
Seek FS, GetFileOffset(SectorID, SectorSize)
For i = 1 To SectorSize / 128
'读取数据
Get FS, , Entry
'添加元素
AddDir2List DirEntry(), Entry
Next i
'读取下一个Sector
ReadDir FS, FAT(), DirEntry(), FAT(SectorID), SectorSize
End Sub
是不是很简单啊,思路完全一样。(此处省略Directory Entry的类型声明)
读完上述Directory Entry信息之后,起始我们发现有很多信息是没用的,而且Entry的名称也不太好读。
那些个方块实在不咋滴。
我们来改进下吧,再声明一个自定义类型,把需要的信息存放进去。
Public Type DirectoryEntryLocal
EntryName As String '名称
EntryType As EntryType '类型
LeftSiblingID As Long '左兄弟节点ID
RightSiblingID As Long '右兄弟节点ID
ChildID As Long '子节点ID
SectorStartID As Long '若是Stream,数据起始扇区ID
StreamSize As Long 'Stream的大小
End Type
再来改一下原来的代码
Sub ReadDirLocal(FS As Integer,FAT() As Long, DirEntry() As DirectoryEntryLocal, SectorID As Long,SectorSize As Long)
Dim i As Long
Dim Entry As CFBDirecroty
Dim EntryLocal As DirectoryEntryLocal
'若SectorID为结束ID,则停止读取
If SectorID = SECTORTYPE.ENDOFCHAIN Or SectorID = SECTORTYPE.FREESECT Then
Exit Sub
End If
'定位
Seek FS, GetFileOffset(SectorID, SectorSize)
For i = 1 To SectorSize / 128
'读取数据
Get FS, , Entry
If Entry.ENTRYTYPE <> ENTRYTYPE.dirNA Then
EntryLocal.EntryName = StrConv(Left(Entry.EntryName, Entry.EntryLength - 2), vbFromUnicode)
EntryLocal.ENTRYTYPE = Entry.ENTRYTYPE
EntryLocal.ChildID = Entry.ChildID
EntryLocal.LeftSiblingID = Entry.LeftSiblingID
EntryLocal.RightSiblingID = Entry.RightSiblingID
EntryLocal.SectorStartID = Entry.SectorStartID
EntryLocal.StreamSize = Entry.StreamSize(0)
'添加元素
AddDirLocal2List DirEntry(),EntryLocal
End If
Next i
'读取下一个Sector
ReadDirLocal FS, FAT(), DirEntry(), FAT(SectorID), SectorSize
End Sub
运行一下程序,看看结果
这样一看,是不是爽快多了啊,哈哈。感兴趣的可以看下每个DirLocal下的内容。
如果你一路都跟着我的节奏,并且自己写代码操练的话,相信你已经掌握了解析复合文档的方法了。接下去的事情基本都是重复的了,我们将在下一章简单介绍一下VBA部分。
这次有点福利,下面是我写了这么多的参考文档,也发给大家,都是微软官方公布的。后面的一个链接还有一个官方提供的工具,但是结果是错的,不知道是存心唬弄人还是咋的。但是我们都做对了,是不是很有成就感啊。哈哈。
参考文档:
Compound File Binary File Format:
https://msdn.microsoft.com/en-us/library/dd942138.aspx
Understanding the Excel .xls Binary File
Format(本文中提供了一个工具可以查看二进制复合文件,但是解析Directory的部分是错的)
https://msdn.microsoft.com/zh-cn/library/office/gg615597(v=office.14).aspx