65.9K
CodeProject 正在变化。 阅读更多。
Home

计算 XLSB 文件中记录的大小

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (5投票s)

2013年11月7日

CPOL

8分钟阅读

viewsIcon

20156

了解XLSB文件格式中字符串记录的记录结构。

引言

本文是对Microsoft XLSB文件格式中有关读取字符串数据的未充分记录功能的回复。本文阐明了用户如何理解如何编写代码来读取XLSB字符串数据。本文的目的是向读者展示如何计算XLSB文件格式中记录类型的记录大小。

背景

这是一个非常高级的主题。它要求用户已经编写了大量可以处理XLSB文件格式的代码。XLSB文件格式实际上是一个ZIP存档,其中包含许多具有多种信息类型的文件。对于本文,我们只关注工作表文件信息。

使用代码

本文不使用代码甚至伪代码。它只是阐明了字符串记录的数据结构。

关注点

本文适用于所有曾经编写过Excel文件格式阅读器或转换器的人。目前,我已编写或参与过XLS 95、XLS 97-2003、XLSX和XLSB。我对这些格式了如指掌,并认为自己是文件格式许多方面的专家,尽管我并不是该软件本身的用户或专家。在我的日常工作中,我必须支持Excel文件格式。

数据结构

XLSB文件格式中的记录是一种智能数据结构。它允许未来的记录类型和对旧记录类型的支持。我发现记录格式非常高级且设计巧妙。“每个二进制记录都是一个变长字节序列。二进制记录由三个组件组成:记录类型、记录大小以及特定于该记录类型的记录数据。”——摘自XLSB文档。

  • 记录类型是变长的,最多2个字节,并以7位数字组合。
  • 记录大小也是变长的,最多4个字节,并以7位数字组合。
  • 记录数据紧随其后,其长度(以字节为单位)就是记录大小。

读取数据时,可以跳过所有不感兴趣的数据记录类型,并解析感兴趣的记录。字符串数据有多种格式。它可以存在于字符串表中,也可以作为独立记录存在。由于字符串数据本质上是变长的,因此能够精确计算记录大小以正确读取记录数据至关重要。在文章末尾给出了一个公式,可用于计算任何记录类型的任何记录长度。本文将结合字符串数据来探讨这一点。

文档对如何处理短字符串的描述和示例很差,它们给出的公式是 B2 * 128 + B1。B1 是第一个字节,但只使用前7位加上第二个字节乘以128,但也只使用数据的前7位。使用此公式,我们只能读取总长度为 127 * 128 + 127 的字符串。这相当于 16,383 字节的数据。由于字符串存储为 Unicode 数据,因此此长度实际上减半,字符串限制为 8,191 个 Unicode 字符。这对于大多数数据单元格来说可能已足够。

如果字符串比这长怎么办?如果我们按照文档操作,我们会想要更多的示例和文档。然而,我们却一无所获……我们该怎么办?在我的案例中,我不得不调试一个崩溃的应用程序,因为它试图读取比此大小更大的字符串。幸运的是,我能够逆向工程这个过程,并将其整理成一篇很好的文章,供未来希望读取 Excel 数据的程序员使用。

一个字符串有许多不同的记录类型,但归根结底都是基本的字符串表示。对于这个例子,我们正在查看记录类型62。这个记录正式名称为BRTCELLSTRING,它是Excel在应用程序之间传输剪贴板数据时使用的记录类型之一。该记录包含单元格和字符串信息。此记录由文件格式的规范2.4.2822.5.92.5.128给出。在每个图像中,最左边的“0”表示第一个位,最右边的“1”表示数据结构的第32位。组件按逻辑顺序和大小堆叠。一些位未使用并标记为保留位。

679614/BrtCellRString.jpg

679614/Cell.jpg

679614/RichStr.jpg 

让我们看看崩溃的应用程序和它试图读取的字符串。我们知道字符串的格式。参见上面BrtCellRString。第一部分是一个单元格。参见上面Cell。下一部分是值。参见上面RichStr

现在我们需要读取RichStr (2.5.128) 结构中的数据dwSizeStrRun,它告诉我们字符串的长度。理论上,仅仅使用字符串长度元素就应该可以逆向工程出整个记录大小,因为其余数据是固定长度的。

再次查看结构,我们看到单元格总是8字节,其中4字节用于列,4字节用于样式。值是可变长度的,其中1字节用于信息,4字节用于长度dwSizeStrRun,然后是字符串数据。

现在我们可以读取字符串长度并计算记录大小,并理解记录大小字节的含义。

在我分析的崩溃中,dwSizeStrRun包含值12402。利用记录知识并知道此字符串不是语音字符串,我们可以计算出记录大小应告诉我们的值。

从知道字符串是Unicode字符串开始,我们必须将此数量加倍以获得字节长度。因此,12402 * 2 = 24804。现在我们在此基础上加上13,以计入包含dwSizeStrRun(4字节)、A和B(1字节)、iStyleRef和A(4字节)以及(4字节)的字段的大小。我们得到一个预期的记录大小为24817字节。

现在,在我的样本数据中,整个文件的记录大小由3个字节241、193和1给出。我们怎么知道我们只有3个字节来确定记录大小呢?我们知道这一点,因为记录大小的任何大于或等于128的字节都意味着后面还有另一个字节。根据这个逻辑,我们最多读取4个字节。在这个例子中,第三个字节的值是1,所以我们停止。

字节241、193和1到底是如何转换为记录大小24817的?如果你知道如何做,那就很容易了……

  • 步骤1 - 文档告诉我们,只有前7位对于值很重要,所以对于大于或等于128的值,我们减去128。这给我们带来了值113、65和1
  • 步骤2 - 我们被告知第一个字节是最低有效字节,最后一个字节是最高有效字节,所以我们反转顺序。这给了我们1、65和113的序列。
  • 步骤3 - 我们被告知这些是7位数字,所以让我们按上面序列的顺序查看这些位

1 = 0000001, 65 = 1000001, 113 = 1110001.

合并后它们是000000110000011110001

现在让我们看看24817的二进制值。

 24817 = 110000011110001

让我们比较我们的两个二进制数:110000011110001 == 110000011110001

瞧!它们是相同的,所以现在我们知道如何操作记录大小字节以匹配字符串长度。这些记录大小字节,一旦操作,就形成一个BCD值。BCD是二进制编码的十进制。让我们把这些信息变成一个公式。该公式必须忽略第8位,并且必须将(最多)4个字节连接在一起以构成记录长度。第8位是128,所有其他相关位组合起来的值是127。我们可以使用“与”逻辑来屏蔽高位,只关注相关位。我们可以使用“位移”逻辑来定位我们的二进制编码十进制值位。我们可以使用“或”逻辑将所有内容重新组合在一起。

这就是我们的公式:

记录大小 = ((B4 & 127) << 21) | ((B3 & 127) << 14) | ((B2 & 127) << 7) | (B1 & 127) 

我怀疑您在逆向工程文件格式以填补我工作中所需的文档空白时,是否会获得与我相同的满足感。但是,我希望这个公式能真正帮助到某人,我写这篇文章的目的是记录XLSB文件格式中一个未充分记录但极其关键的功能。

干杯! - 安迪

历史 

  • 2013年11月7日 - 文章由我撰写。
  • 2013年11月8日 - 编辑,增加更多文本并修正公式,因为我漏了一个括号。
© . All rights reserved.