利用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扩展存储大容量额外数据

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

相关推荐
数科云3 小时前
AI提示词(Prompt)入门:什么是Prompt?为什么要写好Prompt?
人工智能·aigc·ai写作·ai工具集·最新ai资讯
Devlive 开源社区3 小时前
技术日报|Claude Code超级能力库superpowers登顶日增1538星,自主AI循环ralph爆火登榜第二
人工智能
软件供应链安全指南3 小时前
灵脉 IAST 5.4 升级:双轮驱动 AI 漏洞治理与业务逻辑漏洞精准检测
人工智能·安全
lanmengyiyu3 小时前
单塔和双塔的区别和共同点
人工智能·双塔模型·网络结构·单塔模型
微光闪现3 小时前
AI识别宠物焦虑、紧张和晕车行为,是否已经具备实际可行性?
大数据·人工智能·宠物
技术小黑屋_4 小时前
用好Few-shot Prompting,AI 准确率提升100%
人工智能
中草药z4 小时前
【嵌入模型】概念、应用与两大 AI 开源社区(Hugging Face / 魔塔)
人工智能·算法·机器学习·数据集·向量·嵌入模型
web3.08889994 小时前
微店商品详情API实用
python·json·时序数据库
知乎的哥廷根数学学派4 小时前
基于数据驱动的自适应正交小波基优化算法(Python)
开发语言·网络·人工智能·pytorch·python·深度学习·算法
DisonTangor4 小时前
GLM-Image:面向密集知识与高保真图像生成的自回归模型
人工智能·ai作画·数据挖掘·回归·aigc