上文提到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 │ │ date │ date │ date │ varchar │ varchar │ varchar │
├────────────┼───────────┼───────────┼──────────────┼────────────┼─────────────────┼───┼────────────┼──────────────┼───────────────┼───────────────────┼────────────┼──────────────────────┤
│ 1.0 │ 155190.0 │ 7706.0 │ 1.0 │ 17.0 │ 21168.23 │ … │ 1996-03-13 │ 1996-02-12 │ 1996-03-22 │ DELIVER IN PERSON │ TRUCK │ to beans x-ray car… │
│ 1.0 │ 67310.0 │ 7311.0 │ 2.0 │ 36.0 │ 45983.16 │ … │ 1996-04-12 │ 1996-02-28 │ 1996-04-20 │ TAKE BACK RETURN │ MAIL │ according to the f… │
│ 1.0 │ 63700.0 │ 3701.0 │ 3.0 │ 8.0 │ 13309.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) 中的一个关键组件,其主要用途是:
- 字符串共享机制:存储工作表中所有唯一的字符串值
- 减少文件大小:相同的字符串只在文件中存储一次,多个单元格引用同一个字符串索引
- 提高处理效率:避免重复存储相同的字符串内容
文件位置
在解压后的 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"
表示保留空白字符
- 可能包含属性如
高级格式特性
-
富文本格式:字符串可以包含格式化的部分
<si> <r> <rPr>...</rPr> <t>格式化的</t> </r> <r> <t>文本</t> </r> </si>
-
特殊字符处理:XML 特殊字符如
<
,>
,&
会被转义 -
空白处理:
xml:space="preserve"
属性用于保留前导和尾随空格
工作流程
- 单元格中存储的是字符串的索引而非实际值
- 解析时,通过索引在 SharedStrings.xml 中查找实际字符串
- 单元格的 XML 表示类似:
<c r="A1" t="s"> <!-- t="s" 表示共享字符串 --> <v>0</v> <!-- 引用 sharedStrings.xml 中的第一个字符串 --> </c>
实际应用
当解析 XLSX 文件时,通常需要:
- 首先解析 sharedStrings.xml 建立字符串索引表
- 然后解析工作表时,将单元格中的索引映射到实际字符串
这种设计使得处理大量重复文本的电子表格时,文件大小可以显著减小。
明白了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 │ │ date │ date │ date │ varchar │ varchar │ varchar │
├────────────┼───────────┼───────────┼──────────────┼────────────┼─────────────────┼───┼────────────┼──────────────┼───────────────┼───────────────────┼────────────┼──────────────────────┤
│ 1 │ 155190 │ 7706 │ 1 │ 17 │ 21168.23 │ … │ 1996-03-13 │ 1996-02-12 │ 1996-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规范,需要完整的共享字符串机制才能正确解析文件内容。