前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >VBA解析复合文档04——解析目录信息

VBA解析复合文档04——解析目录信息

作者头像
xyj
发布2020-08-10 09:56:56
5380
发布2020-08-10 09:56:56
举报
文章被收录于专栏:VBA 学习VBA 学习

有了FAT数组的信息,就可以继续解析目录信息和MiniFAT。

01

解析目录

目录信息存放的才是复合文档中的真正数据的一些信息,也只有通过目录信息才能正确读取到数据流:

代码语言:javascript
复制
Private Type CFDir
    EntryName(63) As Byte
    EntryNameLen As Integer
    ObjectType As Byte                    '1仓storage 2流 5根
    ColorFlag As Byte                     '0红色 1黑色
    LeftSiblingID As Long                 '-1表示叶子
    RightSiblingID As Long
    ChildID As Long
    CLSID(16 - 1) As Byte
    StateBits As Long
    CreationTime As Date
    ModifiedTime As Date
    StartingSectorID As Long               '目录入口所表示的第1个扇区编码
    StreamSize As Long                     '目录入口流尺寸,可判断是否是短扇区
    not_used As Long    '我是32位office,读不了64位整数!
    
    '这个不是结构体的字段
    StrDirName As String
    '在文件中的偏移位置
    lOffset As Long
End Type

'解析目录
Private Function parseDir() As String
    Dim l_sub_dir As Long
    Dim l_SID As Long
    Dim k As Long
    Dim i As Long
    Dim lOffset As Long
    
    l_SID = cf.Header.FirstDirSID

    k = 0
    Do
        lOffset = getOffsetBySID(l_SID)
        '设置读取的位置
        cf.r.SeekFile lOffset, OriginF
        
        ReDim Preserve cf.ArrDir(k + 4 - 1) As CFDir
        For i = 0 To 4 - 1
            cf.r.Read cf.ArrDir(k + i).EntryName
            cf.ArrDir(k + i).EntryNameLen = cf.r.ReadInteger()
            '名称长度为0就可以退出了
            If cf.ArrDir(k + i).EntryNameLen = 0 Then Exit Do
            
            cf.ArrDir(k + i).ObjectType = cf.r.ReadByte()                  '1仓storage 2流 5根
            cf.ArrDir(k + i).ColorFlag = cf.r.ReadByte()                  '0红色 1黑色
            cf.ArrDir(k + i).LeftSiblingID = cf.r.ReadLong()             '-1表示叶子
            cf.ArrDir(k + i).RightSiblingID = cf.r.ReadLong()
            cf.ArrDir(k + i).ChildID = cf.r.ReadLong()
            cf.r.Read cf.ArrDir(k + i).CLSID
            cf.ArrDir(k + i).StateBits = cf.r.ReadLong()
            cf.ArrDir(k + i).CreationTime = cf.r.ReadDate()
            cf.ArrDir(k + i).ModifiedTime = cf.r.ReadDate()
            cf.ArrDir(k + i).StartingSectorID = cf.r.ReadLong()             '目录入口所表示的第1个扇区编码
            cf.ArrDir(k + i).StreamSize = cf.r.ReadLong()           '目录入口流尺寸,可判断是否是短扇区
            cf.ArrDir(k + i).not_used = cf.r.ReadLong()
            
            
            If cf.ArrDir(k + i).EntryName(0) <= 5 Then
                cf.ArrDir(k + i).StrDirName = VBA.CStr(cf.ArrDir(k + i).EntryName(0))
                cf.ArrDir(k + i).EntryName(0) = VBA.Asc("]")
                cf.ArrDir(k + i).StrDirName = "[" & cf.ArrDir(k + i).StrDirName & VBA.Left$(cf.ArrDir(k + i).EntryName, cf.ArrDir(k + i).EntryNameLen \ 2 - 1)
                
            Else
                cf.ArrDir(k + i).StrDirName = VBA.Left$(cf.ArrDir(k + i).EntryName, cf.ArrDir(k + i).EntryNameLen \ 2 - 1) '-1包含结尾的0
            End If
            
            cf.ArrDir(k + i).lOffset = lOffset
            lOffset = lOffset + DIR_SIZE
            
        Next
    
        k = k + 4
        l_SID = cf.FAT(l_SID)
    Loop Until l_SID = End_Of_Chain_SID
    
    '去掉最后的一个空白
    ReDim Preserve cf.ArrDir(k + i - 1) As CFDir
    
    recordDir
    
    GetDirsName
End Function

'记录dir的完整名称到hash
Private Function recordDir() As String
    '记录目录名称的Hash
    Set cf.h = NewCHash(UBound(cf.ArrDir) + 1)
    
    RrecordDir cf.ArrDir(0), "", 0
End Function
Private Function RrecordDir(d As CFDir, preDir As String, dirIndex As Long)
    cf.h.Add preDir & d.StrDirName, dirIndex
    d.StrDirName = preDir & d.StrDirName
    
    If d.LeftSiblingID = -1 And d.RightSiblingID = -1 And d.ChildID = -1 Then
        Exit Function
    End If
    
    If d.LeftSiblingID <> -1 Then RrecordDir cf.ArrDir(d.LeftSiblingID), preDir, d.LeftSiblingID
    If d.RightSiblingID <> -1 Then RrecordDir cf.ArrDir(d.RightSiblingID), preDir, d.RightSiblingID
    If d.ChildID <> -1 Then RrecordDir cf.ArrDir(d.ChildID), d.StrDirName & Application.PathSeparator, d.ChildID
End Function

目录信息读取的时候,同时将目录名称的一些信息记录到了一个Hash类中。

02

解析MiniFAT

MiniFAT是不一定会存在的,这个主要是看目录信息中的文件StreamSize有没有小于Header结构中的MiniStreamSize,有的情况下才会出现MiniFAT。

代码语言:javascript
复制
'读取短扇区配置表(Allocator for mini stream  user-defined data)
'是一个SID数组
Private Function parseMiniFAT() As String
    Dim l_SID As Long
    Dim i As Long, j As Long

    If cf.Header.MiniFATSectorsCount = 0 Then Exit Function
    
    cf.lShortSectorSize = 2 ^ cf.Header.MiniSectorShift
    cf.ssNumPerSector = cf.lSectorSize / cf.lShortSectorSize
    
'    根目录的 stream_size 表示短流存放流的大小,每64个为一个short sector
    ReDim cf.MiniFAT(cf.ArrDir(0).StreamSize / cf.lShortSectorSize - 1) As Long

    l_SID = cf.Header.FirstMiniFATSID    '短流起始SID
    
    For i = 0 To UBound(cf.MiniFAT) Step cf.longNumPerSector
        '设置读取的位置
        cf.r.SeekFile getOffsetBySID(l_SID), OriginF
        
        For j = 0 To cf.longNumPerSector - 1
            cf.MiniFAT(i + j) = cf.r.ReadLong()
            
            If i + j = UBound(cf.MiniFAT) Then Exit For
        Next
        
        l_SID = cf.FAT(l_SID)
    Next

End Function

到此复合文档的结构就解析完成了,如果想要读取具体某个数据流,只需要根据FAT或者MiniFAT所构建的扇区链表,逐个扇区读取就可以。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 VBA 学习 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档