利用DeepSeek辅助WPS电子表格ET格式分析

前面研究了电子表格的xlsx、xlsbODS格式,接下来研究WPS电子表格ET格式,这个格式网上的资料很少,WPS官方说 .et 格式是兼容 Xlsx 和 Xls 的。

然而,把它后缀改为xlsx,rust_sheet插件还是无法识别,报错

sql 复制代码
Binder Error:
Read spreadsheet failed: Invalid xlsx file format: File not found 'xl/_rels/workbook.xml.rels'

而把后缀改为xls,rust_sheet插件就能识别,怎么解释这样神奇的现象?

sql 复制代码
mv HelloWorld.et HelloWorld.et.xls

./duckdb132

D load rusty_sheet;
D from read_sheet('HelloWorld.et.xls');
┌─────────────┐
│ Hello World │
│   varchar   │
├─────────────┤
│   0 rows    │
└─────────────┘

这要从xls格式的来源说起,以下是DeepSeek的详细说明

Microsoft Excel 97-2003 (XLS) 文件格式详解

Excel 97-2003 使用的 XLS 格式(也称为 BIFF8 格式)是 Microsoft 的二进制文件格式,在 Excel 2007 之前一直是标准格式。以下是该格式的全面介绍:

一、基本结构

XLS 文件是基于 复合文件二进制格式(Compound File Binary Format,简称 CFBF)的结构化存储文件,也称为 OLE2 或 DOCFILE 格式。

1. 存储结构特点

  • 类似小型文件系统:包含"存储"(storages)和"流"(streams)
  • 扇区分配:数据被分成512字节的扇区
  • FAT表:文件分配表记录扇区链接关系
  • 目录结构:包含文件内各部分的层次关系

二、核心组成部分

1. 主要存储流

复制代码
\Workbook       // 主工作簿数据
\SummaryInformation  // 文档摘要信息
\DocumentSummaryInformation // 文档统计信息

2. Workbook 流结构

Workbook 流由一系列 BIFF记录(Binary Interchange File Format)组成:

记录类型 说明
BOF 工作簿/工作表开始标记
EOF 结束标记
SST 共享字符串表
BoundSheet 工作表信息
DIMENSIONS 工作表尺寸
ROW 行信息
CELL 单元格数据
FORMULA 公式
FONT 字体定义
XF 单元格格式
STYLE 样式定义

三、关键记录详解

1. 单元格数据存储

  • 数字:直接以8字节双精度浮点存储
  • 字符串:引用SST表中的索引
  • 布尔值:0(FALSE)或1(TRUE)
  • 错误值:特定错误代码
  • 空白单元格:不存储,通过ROW记录推断

2. 共享字符串表(SST)

cpp 复制代码
struct {
    uint16_t numStrings;      // 字符串总数
    uint16_t numUniqueStrings;// 唯一字符串数
    XLUnicodeString strings[];// 字符串数组
}

3. 公式存储

cpp 复制代码
struct {
    uint16_t row;
    uint16_t col;
    uint16_t xfIndex;
    uint64_t value;  // 缓存的计算结果
    byte formula[];  // 公式的RPN表示
}

四、格式限制

项目 限制
行数 65,536 (2^16)
列数 256 (2^8)
工作表数 受内存限制
单元格样式 4,000种左右
字符串长度 32,767字符

五、与新版XLSX对比

特性 XLS XLSX
格式类型 二进制 XML压缩包
文件大小 相对较大 较小
恢复能力
最大行数 65,536 1,048,576
扩展性 有限 良好
加密支持

六、编程处理

1. 使用Python读取

python 复制代码
import xlrd

# 打开XLS文件
workbook = xlrd.open_workbook("example.xls")

# 获取第一个工作表
sheet = workbook.sheet_by_index(0)

# 读取单元格数据
print(sheet.cell_value(0, 0))  # 第一行第一列

2. 使用Python写入

python 复制代码
import xlwt

# 创建工作簿
workbook = xlwt.Workbook()
sheet = workbook.add_sheet("Sheet1")

# 写入数据
sheet.write(0, 0, "Hello World")

# 保存文件
workbook.save("output.xls")

它的结构可以使用专业工具检查:

python 复制代码
from olefile import OleFileIO

try:
    ole = OleFileIO("test.xls")
    print("有效的OLE结构:", ole.listdir())
except:
    print("不是完整的OLE文件")

我们用上述xlwt脚本生成一个HelloWorld.xls,用xlrt和olefile分别去读。

bash 复制代码
python3 xlrd2.py

Hello World

python3 ole2.py
有效的OLE结构: [['Workbook']]

然后用WPS打开HelloWorld.xls,再另存为HelloWorld.et,用xlrt和olefile分别去读。

bash 复制代码
python3 xlrd2.py

Hello World

python3 ole2.py
有效的OLE结构: [['\x05DocumentSummaryInformation'], ['\x05SummaryInformation'], ['ETExtData'], ['Workbook']]

可见,et格式是有效的OLE结构,但比标准xls格式多了3个额外的段,不影响读取。

那么它怎么又能兼容xlsx格式呢?问题就出在多出来的段上。

我们知道xlsx实际是zip格式,那ET也是zip格式吗,不完全是。

我用unzip工具去列出它包含的文件,结果是这样的。

bash 复制代码
unzip -l HelloWorld.et
存档文件:  HelloWorld.et
warning [HelloWorld.et]:  10223 extra bytes at beginning or within zipfile
  (attempting to process anyway)
  Length      Date    Time    Name
---------  ---------- -----   ----
      540  2012-07-02 09:52   [Content_Types].xml
        0  2012-07-02 09:52   _rels/
      310  2012-07-02 09:52   _rels/.rels
        0  2012-07-02 09:52   theme/
        0  2012-07-02 09:52   theme/theme/
        0  2012-07-02 09:52   theme/theme/_rels/
      283  2012-07-02 09:52   theme/theme/_rels/themeManager.xml.rels
     6432  2012-07-02 09:52   theme/theme/theme1.xml
      138  2012-07-02 09:52   theme/theme/themeManager.xml
---------                     -------
     7703                     9 files

unzip发现这个文件除了zip格式应该有的部分,还有10223个多余的字节,而且还能继续解压,但多余的字节解压不了,也就取不到真正的数据。

需要特别说明,WPS保存的xls格式并非标准xls格式,仅仅是改个后缀。它利用了OLE结构允许多个段,而读取xls格式的程序只去读'Workbook'段这个特点实现了"兼容"。本质上就是带有xls段和zip信息的混合格式。

再将它与xlsx格式比较,用WPS打开HelloWorld.et,再另存为HelloWorld.xlsx,用unzip工具去列出:

bash 复制代码
unzip -l HelloWorld.xlsx
存档文件:  HelloWorld.xlsx
  Length      Date    Time    Name
---------  ---------- -----   ----
     1293  2012-07-02 09:52   [Content_Types].xml
        0  2012-07-02 09:52   _rels/
      735  2012-07-02 09:52   _rels/.rels
        0  2012-07-02 09:52   docProps/
      564  2012-07-02 09:52   docProps/app.xml
      573  2012-07-02 09:52   docProps/core.xml
      526  2012-07-02 09:52   docProps/custom.xml
        0  2012-07-02 09:52   xl/
        0  2012-07-02 09:52   xl/_rels/
      698  2012-07-02 09:52   xl/_rels/workbook.xml.rels
      187  2012-07-02 09:52   xl/sharedStrings.xml
    20006  2012-07-02 09:52   xl/styles.xml
        0  2012-07-02 09:52   xl/theme/
     6432  2012-07-02 09:52   xl/theme/theme1.xml
      477  2012-07-02 09:52   xl/workbook.xml
        0  2012-07-02 09:52   xl/worksheets/
     1309  2012-07-02 09:52   xl/worksheets/sheet1.xml
---------                     -------
    32800                     17 files

可见,这个文件没有多余字节,真正的数据保存在sharedStrings.xml和xl/worksheets/sheet1.xml中,这在上文已经分析过了。

为什么WPS能这么干呢?因为zip格式是允许文件中间有多余部分的,以下是DeepSeek的详细解释。

ZIP 文件格式详解及安全修改指南

ZIP 文件格式基本结构

ZIP 文件是一种常见的归档文件格式,其基本结构如下:

1. 核心组成部分

复制代码
[Local File Header 1]
[File Data 1]
[Local File Header 2]
[File Data 2]
...
[Central Directory]
[End of Central Directory Record]

2. 关键结构说明

  • Local File Header:每个文件的本地头,包含文件名、压缩方法等元数据

    • 签名:0x04034b50 (PK\x03\x04)
    • 结构:30字节固定部分 + 文件名 + 额外字段
  • File Data:实际压缩后的文件内容

  • Central Directory:中央目录,包含所有文件的索引信息

    • 签名:0x02014b50 (PK\x01\x02)
    • 包含所有文件的完整路径、压缩信息、偏移量等
  • End of Central Directory Record:目录结束标记

    • 签名:0x06054b50 (PK\x05\x06)
    • 包含中央目录的位置和文件总数

在ZIP中添加内容而不影响解压的方法

方法1:利用ZIP注释字段(推荐)

python 复制代码
import zipfile

def add_to_zip_comment(zip_path, extra_data, output_path):
    with zipfile.ZipFile(zip_path, 'a') as zf:
        # 获取原注释
        orig_comment = zf.comment
        # 追加新数据
        new_comment = orig_comment + extra_data.encode('utf-8')
        # 创建新ZIP文件
        with zipfile.ZipFile(output_path, 'w') as new_zip:
            # 复制所有原文件
            for item in zf.infolist():
                with zf.open(item.filename) as f:
                    new_zip.writestr(item, f.read())
            # 设置新注释
            new_zip.comment = new_comment

# 使用示例
add_to_zip_comment('example.zip', '这是我的额外数据', 'modified.zip')

优点

  • 完全符合ZIP规范
  • 所有解压工具都能正常处理
  • 不会影响文件校验和

方法2:在Central Directory后添加数据(兼容性较好)

  1. 找到End of Central Directory Record的起始位置
  2. 在其前面插入额外数据
  3. 更新End of Central Directory Record中的偏移量
python 复制代码
def append_after_central_dir(original_zip, extra_data, output_zip):
    with open(original_zip, 'rb') as f:
        data = f.read()
    
    # 查找End of Central Directory Record
    eocd_pos = data.rfind(b'PK\x05\x06')
    
    if eocd_pos == -1:
        raise ValueError("无效的ZIP文件")
    
    # 在EOCD前插入数据
    new_data = data[:eocd_pos] + extra_data.encode() + data[eocd_pos:]
    
    with open(output_zip, 'wb') as f:
        f.write(new_data)

方法3:利用Extra Field字段(针对特定文件)

python 复制代码
def add_to_extra_field(zip_path, output_path):
    with zipfile.ZipFile(zip_path, 'a') as zf:
        # 获取第一个文件的信息
        file_list = zf.infolist()
        if not file_list:
            raise ValueError("ZIP文件为空")
        
        # 修改extra字段
        file_info = file_list[0]
        file_info.extra = b'MyExtraData\x00\x01' + file_info.extra
        
        # 创建新ZIP文件
        with zipfile.ZipFile(output_path, 'w') as new_zip:
            for item in file_list:
                with zf.open(item.filename) as f:
                    content = f.read()
                new_zip.writestr(item, content)

注意事项

  1. 签名冲突:确保添加的数据不包含PK\x03\x04、PK\x01\x02等ZIP魔术数字

  2. 文件校验:修改后建议验证ZIP文件完整性

    python 复制代码
    zipfile.is_zipfile('modified.zip')  # 验证ZIP有效性
  3. 大小限制

    • ZIP注释最大65535字节
    • Extra Field字段通常限制为65535字节
  4. 兼容性测试:建议在目标解压环境中测试修改后的文件

高级技巧:隐藏数据

对于需要隐蔽存储的场景,可以:

  1. 将数据加密后存入注释
  2. 使用StegZIP等工具在最低有效位(LSB)隐藏信息
  3. 利用ZIP64扩展存储大容量额外数据

以上方法都能确保标准解压工具正常解压原始内容,同时携带额外信息。

相关推荐
咖啡Beans1 天前
Python工具DrissionPage推荐
后端·python
Nicole-----1 天前
PySpark数据输入
python
机器之心1 天前
字节Seedream 4.0将全量开放!抢先评测来了,我们摸索出AI生图一大波「邪修」玩法
人工智能·openai
扑克中的黑桃A1 天前
Python学习的自我理解和想法(27)
python
空白到白1 天前
机器学习-集成学习
人工智能·机器学习·集成学习
水凌风里1 天前
4.4 机器学习 - 集成学习
人工智能·机器学习·集成学习
雲_kumo1 天前
集成学习:从理论到实践的全面解析
人工智能·机器学习·集成学习
用户5191495848451 天前
30条顶级APT与蓝队攻防单行命令:网络战场终极对决
人工智能·aigc
双向331 天前
AI 辅助文档生成:从接口注释到自动化 API 文档上线
人工智能