DuckDB社区sheetreader插件打开某些xlsx文件失败原因分析

上文提到sheetreader插件打不开某些文件,今天遇到一个Duckdb excel插件生成的文件,就是这样,报错信息如下:

from sheetreader('ex100.xlsx')limit 10;
Invalid Error:
Failed to retrieve shared strings file

奇怪的是,用WPS软件打开此文件再保存,sheetreader插件就能打开,并输出内容了。

D from sheetreader('ex100d.xlsx')limit 10;
┌────────────┬───────────┬───────────┬──────────────┬────────────┬─────────────────┬───┬────────────┬──────────────┬───────────────┬───────────────────┬────────────┬──────────────────────┐
│ l_orderkey │ l_partkey │ l_suppkey │ l_linenumber │ l_quantity │ l_extendedprice │ … │ l_shipdate │ l_commitdate │ l_receiptdate │  l_shipinstruct   │ l_shipmode │      l_comment       │
│   double   │  double   │  double   │    double    │   double   │     double      │   │    datedatedate      │      varchar      │  varchar   │       varchar        │
├────────────┼───────────┼───────────┼──────────────┼────────────┼─────────────────┼───┼────────────┼──────────────┼───────────────┼───────────────────┼────────────┼──────────────────────┤
│        1.0155190.07706.01.017.021168.23 │ … │ 1996-03-13 │ 1996-02-12   │ 1996-03-22    │ DELIVER IN PERSON │ TRUCK      │ to beans x-ray car…  │
│        1.067310.07311.02.036.045983.16 │ … │ 1996-04-12 │ 1996-02-28   │ 1996-04-20    │ TAKE BACK RETURN  │ MAIL       │ according to the f…  │
│        1.063700.03701.03.08.013309.6 │ … │ 1996-01-29 │ 1996-03-05   │ 1996-01-31    │ TAKE BACK RETURN  │ REG AIR    │ ourts cajole above…  │

猜测几种理由都不对,想起来去源码看看,从github拉取了sheetreader-core,里面有个XlsxFile.cpp文件,里面包含上述报错信息:
这是调用的地方

void XlsxFile::parseSharedStringsInterleaved() {
    mz_zip_archive* file = mFileSharedStrings == nullptr ? mFile : mFileSharedStrings;
    const int relIndex = fileIndex(file, mPathSharedStrings.c_str());
    if (relIndex < 0) {
        stringCount.store(-1);
        throw std::runtime_error("Failed to retrieve shared strings file");
    }

同时找到了报错的函数,它在解压缩阶段就报错。

int fileIndex(mz_zip_archive* archive, const char* fileName) {
    // tries to retrieve the file index
    // matches even if preceding '/' does not
    mz_zip_archive_file_stat fileStat;
    const int offset = fileName[0] == '/' ? 1 : 0;
    for (int i = 0; i < (int)mz_zip_reader_get_num_files(archive); ++i) {
        if (mz_zip_reader_file_stat(archive, i, &fileStat)) {
            if (strcmp(fileStat.m_filename + (fileStat.m_filename[0] == '/' ? 1 : 0), fileName + offset) == 0) {
                return i;
            }
        }
    }
    return -1;
}

因为xlxs文件本质上是zip格式,

file ex100.xlsx
ex100.xlsx: Microsoft Excel 2007+

file ex100d.xlsx.zip
ex100d.xlsx.zip: Microsoft Excel 2007+

所以把它重命名后用解压缩工具解压缩,比较保存前后两版文件,发现区别在于WPS保存后的文件多了一个SharedStrings.xml文件,里面保存了一些字符串,从报错信息看,这些字符串是共享的。
用文本编辑器打开,部分内容如下

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="516" uniqueCount="132"><si><t>l_orderkey</t></si><si><t>l_partkey</t></si><si><t>l_suppkey</t></si><si><t>l_linenumber</t></si><si><t>l_quantity</t></si><si><t>l_extendedprice</t></si><si><t>l_discount</t></si><si><t>l_tax</t></si><si><t>l_returnflag</t></si><si><t>l_linestatus</t></si><si><t>l_shipdate</t></si><si><t>l_commitdate</t></si><si><t>l_receiptdate</t></si><si><t>l_shipinstruct</t></si><si><t>l_shipmode</t></si><si><t>l_comment</t></si><si><t>N</t></si><si><t>O</t></si><si><t>DELIVER IN PERSON</t></si><si><t>TRUCK</t></si><si><t>to beans x-ray carefull</t></si><si><t>TAKE BACK RETURN</t></si>

可见,里面存了标题行和一些数据行的字符串。
经询问DeepSeek,它的解释如下:

SharedStrings.xml 在 XLSX 文件中的用途和格式

用途

SharedStrings.xml 是 Office Open XML (OOXML) 格式的 Excel 文件 (.xlsx) 中的一个关键组件,其主要用途是:

  1. 字符串共享机制:存储工作表中所有唯一的字符串值
  2. 减少文件大小:相同的字符串只在文件中存储一次,多个单元格引用同一个字符串索引
  3. 提高处理效率:避免重复存储相同的字符串内容

文件位置

在解压后的 XLSX 文件中,SharedStrings.xml 位于:

xl/sharedStrings.xml

基本格式

SharedStrings.xml 的基本结构如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="总字符串出现次数" uniqueCount="唯一字符串数量">
    <si>
        <t>字符串内容1</t>
    </si>
    <si>
        <t>字符串内容2</t>
    </si>
    ...
</sst>

关键元素说明

  • <sst>: 根元素,包含所有共享字符串

    • count: 所有工作表中这些字符串被引用的总次数
    • uniqueCount: 唯一字符串的数量
  • <si> (String Item): 每个唯一字符串的容器

  • <t> (Text): 实际的字符串内容

    • 可能包含属性如 xml:space="preserve" 表示保留空白字符

高级格式特性

  1. 富文本格式:字符串可以包含格式化的部分

    <si>
        <r>
            <rPr>...</rPr>
            <t>格式化的</t>
        </r>
        <r>
            <t>文本</t>
        </r>
    </si>
    
  2. 特殊字符处理:XML 特殊字符如 <, >, & 会被转义

  3. 空白处理xml:space="preserve" 属性用于保留前导和尾随空格

工作流程

  1. 单元格中存储的是字符串的索引而非实际值
  2. 解析时,通过索引在 SharedStrings.xml 中查找实际字符串
  3. 单元格的 XML 表示类似:
    <c r="A1" t="s"> <!-- t="s" 表示共享字符串 -->
        <v>0</v> <!-- 引用 sharedStrings.xml 中的第一个字符串 -->
    </c>
    

实际应用

当解析 XLSX 文件时,通常需要:

  1. 首先解析 sharedStrings.xml 建立字符串索引表
  2. 然后解析工作表时,将单元格中的索引映射到实际字符串

这种设计使得处理大量重复文本的电子表格时,文件大小可以显著减小。

明白了sharedStrings.xml的用途,那就简单了,如果不需要共享字符串,就建立一个空白的文件放在里面。sheetreader插件找到该文件应该就不会报错了,而实际的字符串都保存在sheet1.xml中,也不影响解析。WPS一开始就能打开无sharedStrings.xml的xlsx文件就证明了这一点。
我把保存了空白sharedStrings.xml文件的xlxs文件用WPS果然能够打开,用rusty_sheet插件也能打开,

D load rusty_sheet;
D from read_sheet('a/a.xlsx')limit 10;
┌────────────┬───────────┬───────────┬──────────────┬────────────┬─────────────────┬───┬────────────┬──────────────┬───────────────┬───────────────────┬────────────┬──────────────────────┐
│ l_orderkey │ l_partkey │ l_suppkey │ l_linenumber │ l_quantity │ l_extendedprice │ … │ l_shipdate │ l_commitdate │ l_receiptdate │  l_shipinstruct   │ l_shipmode │      l_comment       │
│   int64    │   int64   │   int64   │    int64     │   int64    │     double      │   │    datedatedatevarcharvarcharvarchar        │
├────────────┼───────────┼───────────┼──────────────┼────────────┼─────────────────┼───┼────────────┼──────────────┼───────────────┼───────────────────┼────────────┼──────────────────────┤
│          1155190770611721168.23 │ … │ 1996-03-131996-02-121996-03-22    │ DELIVER IN PERSON │ TRUCK      │ to beans x-ray car…  │

而用sheetreader仍然不行,

from sheetreader('a/a.xlsx')limit 10;

Binder Error:
Inline & dynamic String types not supported yet

这是一个新的错误提示,查到它位于sheetreader-duckdb/src/sheetreader_extension.cpp文件中。

//! Converts the cell types from sheetreader-core to DuckDB types (column_types)
//! and it also sets the column names (uses generic names)
inline bool ConvertCellTypes(vector<LogicalType> &column_types, vector<string> &column_names,
                             vector<CellType> &cell_types) {
	idx_t current_column_index = 0;
	//! Indicates if the first row contains only string values
	bool first_row_all_string = true;

	for (auto &col_type : cell_types) {
		switch (col_type) {
		case CellType::T_STRING_REF:
			column_types.push_back(LogicalType::VARCHAR);
			column_names.push_back("String" + std::to_string(current_column_index));
			break;
		case CellType::T_STRING:
		case CellType::T_STRING_INLINE:
			// TODO
			throw BinderException("Inline & dynamic String types not supported yet");
			break;

很明显,代码中TODO字样表明它还处理不了内联字符串,看来这个软件的确完成度不高,就不再测试了。

摘要:DuckDB的sheetreader插件在读取某些Excel文件时遇到"Failed to retrieve shared strings file"错误。经分析,原始文件缺少SharedStrings.xml文件,该文件是XLSX格式的核心组件,用于存储工作表中所有唯一的字符串值。当用WPS重新保存文件后,系统自动生成了包含共享字符串的SharedStrings.xml文件(位于xl/目录下),使得插件能正常读取数据。这表明sheetreader插件严格依赖Excel的OOXML规范,需要完整的共享字符串机制才能正确解析文件内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值