- 主题:在MS的xls中见到了史上最奇葩的设计
由于要parse老版本的*.xls文档,看了一下xls的格式,发现xls中存在一个极其奇葩的设计。
xls是Excel 97/95版本的,这种文件是二进制格式,带有一个文件头,文件头指向后面一系列的记录。
每个记录是T-L-V(Type-Length-Value)结构的,V里面会有很多结构,这个没啥问题。
由于每个记录有最大长度限制(Excel 97的记录的L最大值是8224字节,Excel 95是2080字节),
所以MS的人又搞了一个CONTINUE类型的记录,
一个超长的数据,会拆分为一个头部记录,外加一个或多个CONTINUE类型的记录。
这个拆分记录的设计,奇葩在什么地方?
一、在头部记录中,没有标志标明后面是否跟的有CONTINUE记录。
这样的话,拼凑这些记录的办法有:
1、一直向后预读,看是否存在CONTINUE记录,有则全部读进来拼成一块数据再解析。
流式解析的话,预读会降低效率,除非把预读的缓存着,而缓存后面的记录会增加复杂度。
但MS的人又搞了一个设计,导致你无法简单拼接记录。这个设计就是:
如果记录中的某个结构是字符串数据,这个结构将包含一个固定的头,用来表明字符串的长度、字符宽度等信息,固定头后面是变长的字符串数据。
如果要拆分记录时,正好是要从这个字符串结构这里拆开的话,它规定的拆分规则是:字符串的第一个字符一定要和其固定头保持在同一个记录中。
而且拆了字符串后,这个字符串处于下一个记录(CONTINUE记录)中的字符宽度是可以变的,比如8-bit的windows 1252编码的字符变为16-bit的utf16/GBK字符,或者反过来。虽然一般并不变。
也就是说被拆为两半的字符串,这两半的编码可以不一样!在CONTINUE记录的数据区的第一个字节,就是用来表明字符串的后一半的字符宽度的。但是就是因为这个字节的存在,导致无法简单拼接记录。因为如果不是从字符串结构的中间来拆分记录的话,不存在这个(重复的)字节。这个字节有时有,有时没有,那就难以简单处理。
2、先尝试解析前面的记录,直到发现必须要有CONTINUE记录才能满足解析要求,那么就记下中间状态,持续读取CONTINUE记录,根据中间状态继续解析,或者从头重试解析。
总的来说,因为这个奇葩设计,导致parser的复杂度大大增加。
--
FROM 125.35.123.*
是的,太烂了,有些是软盘时代的遗产(为了节省存储而大量使用奇葩的紧凑设计),
而且doc/ppt/xls这些老格式各自为阵,每个都不一样,明显是几个team单独搞的,没有统一规划
即便是docx/pptx/xlsx这些新格式,里面也有不统一的地方
【 在 Madlee 的大作中提到: 】
: 所以后来要用xlsx啊,如果不是开始设计的太烂为啥要换新格式
--
修改:z16166 FROM 125.35.123.*
FROM 125.35.123.*
python和java的不行,对性能有严格要求,类似杀软的实时防护扫描doc的要求,越快越好。
不过我上回遇到一个解开后xml有300多兆的xlsx,优化后还是要11秒,基本接近极限,只能从产品上想办法
【 在 hgoldfish 的大作中提到: 】
: 这种东东不是有现成的吗?
: Python 以前用过两个库,分别 xlrd, python-excel 能够分析 xls 和 xlsx
:
--
修改:z16166 FROM 125.35.123.*
FROM 125.35.123.*
只解析其中的子集。要是全部特性都解析,吐血也搞不完的。搞docx/pptx/xlsx还得花钱买OOXML的标准文档才能搞清全部细节
另外一个是RTF格式,这个格式明显是基于TeX搞的,必须要从头读到尾,不支持随机位置的读取。当然,早已经被MS抛弃了,只是提供兼容。
【 在 callmebbser 的大作中提到: 】
: 自己解析XLS文件? 够毅力!
: GitHub上有C++的XLS读写库,在Qt上使用过。没测试过性能。
:
--
FROM 125.35.123.*
哈哈,你这个例子在那个年代估计也确实合理
MS的ppt二进制格式里面,也会把用户每次修改的对象,单独存储,叫UserEditAtom
【 在 vabc3 的大作中提到: 】
: 看过一个例子:对于1M的文件,需要在第10字节插入一个字符,现在的机器就直接全读进来然后全写回去好了。
: 但是在软盘+低内存时代这个是无法接受的,产生了有不少技巧,比如文件头预留一部分固定长度描述后面有什么patch,然后把这个字符当作patch插入文件尾部。这样能节省很多io。
: 下次'另存为‘的时候,这些patch才有机会被合并成新文件。
--
FROM 125.35.123.*
别把file parser想那么难,有规范文档的,对着文档弄就完事了
除非是那种什么巨老的连格式文档都找不到的
当然,要想少出点漏洞,得搞一堆的测试用例和fuzzy
【 在 DoorWay 的大作中提到: 】
: 上千人年,作者对office的工作量估计。
: 但博客本身,也是吐槽文档格式的BIFF,增加了解析的难度。核心结论就是,这种格式根本没有考虑interchangeable ,就是为了快。
: 解析的难度还来自复杂漫长的历史,比如1900和1904那个例子。
: ...................
--
修改:z16166 FROM 125.35.123.*
FROM 125.35.123.*
就那个CONTINUE记录的具体设计来说,我觉得可以去掉那个表示字符宽度的字节,
本CONTINUE记录的字符宽度,和上个记录的字符宽度一致就行。
【 在 xwing 的大作中提到: 】
: 这不能抛开历史来评价,第一个大卖的Excel版本是 2,1987年发布。
: 上面有回复说到软盘,小内存…,这就是历史
: 微软还有一个广泛被人诟病的Windows,尤其是驱动子系统
: ...................
--
修改:z16166 FROM 125.35.123.*
FROM 125.35.123.*
一段字符串分为多段可以这么搞:
每段的前面都放一个uint16_t,其0至14 bit表示本段的字符长度,15 bit表示每个字符是单字节还是双字节的。
MS的人显然也能想到这个,而且分为两段时它其实类似这个(多出来的uint16_t中没有长度,只有一个bit表示每个字符是单字节还是双字节的。固定头中有总长度)
测试起来也比较麻烦,需要用Apache POI这种比较完善点的库生成对应的测试xls文件
【 在 adoal 的大作中提到: 】
: 可以分段优化节省空间,比如某一段只有ASCII就用ASCII了。
:
--
修改:z16166 FROM 125.35.123.*
FROM 125.35.123.*