资讯详情

QT5 Linux下读写2003 Excel:Excelformat源码修改与中文路径实践

📅 2026/10/29 0:39:45 | 华诺云谱 👁 阅读
QT5 Linux下读写2003 Excel:Excelformat源码修改与中文路径实践
简介在Linux环境下用Qt5读写Excel 2003xls文件时中文路径是许多开发者的痛点日文、韩文等非英文路径也同理根源往往出在wcstombs和mbstowcs两个转换函数对宽字符长度的错误计算上。这份ExcelFormat第三方库的修改版源码瞄准此问题在源文件cpp中create和open函数内增加条件编译宏“NON_ENGLISH_PATH_FILENAME_SUPPORT”将文件名长度按双倍字节计算后传入转换函数从而成功支持中文、日文、韩文路径且不影响原有表格字体、边框、对齐等样式设置能力。压缩包总大小2.61MB包含21个文件以C源码、Qt工程文件、Makefile、编译中间文件为主并附带了演示用Demo工程包含主窗口界面和可运行样例便于直接编译体验。资源已在统信UOS系统、aarch64和mips64架构上用gcc编译验证通过对国产Linux发行版上的Qt项目集成Excel读写功能具有直接参考价值。目前已有392人学习下载适合在Linux下开发办公类应用或数据导入导出功能的Qt工程师参考移植。1. 在QT5 for Linux里处理2003的excel文件为什么绕不开Excelformat源码修改版当你需要在一个 QT5 for Linux 的桌面或服务端程序里支持中文路径读写 2003 的 excel 文件也就是 .xls时Qt 自带的类库基本帮不上忙QAxObject 依赖 Windows COMQtXlsx 只支持 xlsx剩下的只有第三方库。而 Excelformat 这个老牌 C 库因为代码量小、能读能写、许可证宽松成了接入 .xls 最常被选的对象但它在 Linux 下的中文路径、wchar_t 宽度和文件流处理上有一堆历史遗留问题直接编译基本过不去直接跑中文路径必翻车。本文把我改过的这套源码修改版的思路、改动位置、CMake 集成方式和验证清单完整写出来适合正在接政务、工业、运维报表这类还依赖 .xls 场景的 Qt 工程师照着复现。2. 把Excelformat源码先拆开看2003的excel格式和两条读写链路2.1 为什么最终选了Excelformat而不是libxls、libxl或者QXlsx在 Linux 下做一个 .xls 的读写模块常见候选有四类libxls、libxl、QXlsx 和 Excelformat。libxls 本身读 .xls 做得不错但写能力基本为零你仍然要再搭一个写 .xls 的组件libxl 读写都成熟可它是闭源商业库授权费按人头算很多项目卡在采购流程上QXlsx 只处理 xlsx2003 的 .xls 完全不沾边。Excelformat 是唯一一个开源、C 编写、读写 .xls 两条腿都有的老库SourceForge 上还留在 2016 年前后的版本代码老但五脏俱全正因为老它给了我们改源码的空间。实际工程里我见过有人同时挂三个库libxls 读、Excelformat 写、再加一层自己写的格式转换最后维护成本极高。我的习惯是只留 Excelformat把读的能力补到能用为止这样 CMake 里只多一个静态库头文件也干净。做这个选择前先量化一下自己的需求如果只需要把界面表格导出一个 2003 的 excel 文件Excelformat 是性价比最高的如果还要解析用户上传的、来源五花八门的 .xls那必须先在读的路径上做充分测试这个库的读取器对一些不规范的 BIFF8 记录处理得很脆。2.2 2003的excel文件到底是什么CFB容器加BIFF8记录改这套源码之前至少要弄明白 .xls 在磁盘上长什么样。一个标准的 Excel 97-2003 文件是两层结构外层是 OLE 复合文档容器CFB里面是一个叫做 Workbook 的流内层才是真正存数据的 BIFF8 记录流。CFB 负责把多个流组织在一个文件里所以你可以用file命令看到它标记为 Composite Document FileBIFF8 记录则是连续排列的二进制块每条记录以两个字节的 opcode 开头跟着两个字节的长度字段和长度对应的数据。BIFF8 记录类型很多但对读写最关键的就那么几个BOF/EOF 标记工作簿边界、SST 存共享字符串表、LABELSST 引用 SST 里的字符串索引、NUMBER 存浮点数、FORMULA 存公式结果。Excelformat 源码里的 ExcelFormat 部分就是在做这些记录的编码和解码BasicExcel 部分做的是 CFB 层和对外 API。理解这个结构对排查很重要中文乱码和路径无关时问题多半出在 SST 或者 LABELSST 的索引对不上文件打不开时往往在 CFB 层的目录扇区解析上。2.3 从源码里找读写两条链路的入口先画清楚再动手我拿到一套老库源码第一步不是急着改而是把它的调用链在纸上画出来。以 Excelformat 最常见的接口为例写文件的调用链是BasicExcel::New创建空工作簿Worksheet(idx)拿到指定工作表Cell(row, col)拿单元格指针然后调SetString/SetWString/SetDouble写入内容最后SaveAs(filename)把内存里组好的整个 BIFF8 记录块交给 CFB 封装并落盘。读文件则是Load(filename)先解析 CFB 目录取出 Workbook 流再逐条跳读 BIFF8 记录遇到 SST 就加载共享字符串遇到 LABELSST 就按索引取字符串最后把值填回给Cell(row, col)由外部读取。中文路径的问题恰恰卡在两条链路的端口上Load和SaveAs的 filename 参数在 Windows 版源码里是按窄字符或 TCHAR 处理的移植到 Linux 后它默认把入参当作 ASCII中文路径一旦传进来文件流打不开或者打开了但 CFB 内部记录定位出错。真正要修改的不是 BIFF8 解析逻辑而是文件打开层、路径编码层和 wchar_t 适配层。3. 源码修改版的核心改动文件层、编码层、编译兼容三件事3.1 改动主线只动 IO 与编码不碰 BIFF8 解析这套源码修改版能否稳定的关键是控制改动面。BIFF8 记录解析逻辑是经过大量测试的乱动会引入玄学问题而文件打开、路径转换和 wchar_t 处理这三层在 Linux 下是确定会出问题的改动收益最高。我先明确三条主线第一条把所有文件打开入口统一收口到 Qt 的 QFile / QSaveFile让路径编码交给 Qt 的本地编码机制第二条文件流强制二进制模式堵住老库在读写时可能使用文本模式的隐患第三条写一层 wchar_t 与 QString 的双向转换屏蔽 Linux 和 Windows 在 wchar_t 位宽上的差异。定位改动点时在源码里搜几个关键词能快速收敛文件名参数出现的地方、fopen/_wfopen/ifstream打开文件的位置、MultiByteToWideChar这类 Windows API 调用点、以及所有直接按字节长度操作 wchar_t 的代码。老库在这几处通常写得比较随意我见过的一个版本甚至把 wchar_t* 用strlen求长度在 Linux 的 4 字节 wchar_t 下直接多读一倍数据。3.2 修改点ALoad入口把文件交给QFile路径编码走本地规则第一处必须改的是BasicExcel::Load的文件打开方式。常见的老代码是用fopen(filename, rb)或者ifstream(filename, ios::binary)直接打开这在 Linux 的 UTF-8 路径下其实大多能通但一旦 locale 被设置成 C/POSIX或者文件名里带空格和中文混排就会偶发失败。我一般会让 Load 先走一遍QFile::decodeName把外部传入的字节路径还原成 QString再用 QFile 读成 QByteArray最后把这段内存数据交给原来的 BIFF8 解析流程。// BasicExcel.cpp 中 Load 入口的修改版 bool BasicExcel::Load(const char* path) { if (!path || !*path) { return false; } #ifdef Q_OS_LINUX // Linux 下路径的字节编码就是 UTF-8QFile::decodeName 按本地 8 位编码还原 const QString nativePath QFile::decodeName(path); if (nativePath.isEmpty()) { return false; } QFile file(nativePath); if (!file.exists() || !file.open(QIODevice::ReadOnly)) { return false; } const QByteArray raw file.readAll(); file.close(); if (raw.isEmpty()) { return false; } // 后续所有 BIFF8 解析逻辑不变只把“文件流读取”替换成“内存块读取” return readFromMemory(reinterpret_castconst unsigned char*(raw.constData()), static_castsize_t(raw.size())); #else // Windows / macOS 沿用原实现 // ... #endif }这段代码里有两个参数值得注意。file.readAll()会把整个 .xls 一次性读进内存对于 2003 格式单文件常见几 MB 的量级完全没问题但你如果被要求处理上百 MB 的超大 Excel则需要改成read()分块喂给解析层。readFromMemory是我在原库基础上抽出来的一个静态函数内部不做任何格式转换只是把原来从 ifstream 里read字节的逻辑替换成了从指针读。这样 Load 的改动对后续解析完全透明。3.3 修改点BSaveAs换成QSaveFile顺带解决半截文件问题写文件的入口同样要收口。Linux 下如果沿用老库的ofstream(filename, ios::out)一旦漏写ios::binary遇到 0x0A 字节就可能被做换行转换整个 BIFF8 结构被破坏更稳妥的做法是换成QSaveFile它先把数据写到临时文件最后commit()时原子替换目标文件写一半断电也不会留下一个损坏的 .xls。// BasicExcel.cpp 中 SaveAs 入口的修改版 bool BasicExcel::SaveAs(const char* path) { if (!path || !*path) { return false; } #ifdef Q_OS_LINUX const QString nativePath QFile::decodeName(path); QSaveFile outFile(nativePath); if (!outFile.open(QIODevice::WriteOnly)) { return false; } // m_data 是内存里已经组好的整块 CFB BIFF8 数据 qint64 written outFile.write(m_data.constData(), m_data.size()); if (written ! static_castqint64(m_data.size())) { outFile.cancelWriting(); return false; } return outFile.commit(); #else // Windows / macOS 沿用原实现 // ... #endif }这里有个容易踩的细节QSaveFile::open()只是创建临时文件如果目标文件的父目录不存在open 会直接失败。调用方需要先保证目录存在我一般会在save()之前用QDir().mkpath(QFileInfo(path).absolutePath())兜底。另外commit()失败时不能直接返回成功务必在write返回值不等于数据长度时调用cancelWriting()否则残留的临时文件会一直堆在目录里。3.4 修改点Cwchar_t位宽差异的适配这是中文乱码的真正来源在 Linux 上wchar_t 是 4 字节在 Windows 上wchar_t 是 2 字节。Excelformat 内部从 BIFF8 读取字符串时会把 UTF-16LE 的字节序列转换成 wchar_t 数组交给上层写出时反向转换。如果不做适配代码里所有len * sizeof(wchar_t)的计算在 Linux 下都会出错轻则读出的字符串后面带一堆空字符重则越界崩溃。我的做法是封装两个静态函数把转换集中到一处外部统一使用 QString源码里其他位置不再直接碰 wchar_t。对于读取方向把 wchar_t 数组按 UTF-32 语义交给QString::fromUtf32就行。因为 Linux 的 wchar_t 宽度正好是 32 位这既不需要逐字符转码也不会丢数据。对于写入方向先把 QString 转成 UTF-32 的数组再逐个放进 wchar_t。注意这里不能用text.toStdWString()直接返回因为标准库的 wstring 在 Linux 上底层就是 32 位字符但它不保证与 Excelformat 内部期望的 UTF-16LE 长度一致转换必须先统一到 UTF-32 再落到 wchar_t 中。// WinCompat.h统一 wchar_t 与 QString 的双向转换 #include QString #include QVector #include string static QString FromBiffWString(const wchar_t* src, size_t len) { if (!src || len 0) { return QString(); } #ifdef Q_OS_LINUX // Linux: wchar_t 是 32 位按 UTF-32 构建 QString return QString::fromUtf32(reinterpret_castconst uint*(src), int(len)); #else return QString::fromWCharArray(src, int(len)); #endif } static std::wstring ToBiffWString(const QString text) { if (text.isEmpty()) { return std::wstring(); } #ifdef Q_OS_LINUX const QVectoruint ucs4 text.toUcs4(); std::wstring result; result.resize(ucs4.size()); for (int i 0; i ucs4.size(); i) { result[i] static_castwchar_t(ucs4[i]); } return result; #else return text.toStdWString(); #endif }这个适配层改了之后只需要保证所有单元格字符串的读写都走这两个函数中文内容就不会再乱码。我之前遇到过一种情况路径用 QString 处理好了文件也打开了但单元格里写进去的中文在 LibreOffice 里看是乱码查到最后是调用SetString而不是SetWString导致文本被按单字节 Latin-1 处理。所以调用侧也要遵守一条规则中文必须走 WString 系列接口不能用 SetString。3.5 编译兼容Linux下要补的宏和头文件老库在 Windows 上编译没问题的代码在 Linux 下会先死在一批 Windows CRT 函数上。最常见的是strcmpi、_tcslen、_tcsicmp这些Linux 的 libc 没有这个命名。我一般会加一个PlatformCompat.h在#ifdef Q_OS_LINUX分支里做宏映射而不是去逐行改源码。// PlatformCompat.h #ifndef PLATFORM_COMPAT_H #define PLATFORM_COMPAT_H #include strings.h #include string.h #ifdef Q_OS_LINUX #define _tcslen strlen #define _tcsicmp strcasecmp #define _tcsncpy strncpy #define _tcsnicmp strncasecmp #define strcmpi strcasecmp #define stricmp strcasecmp #endif #endif同时在编译 Excelformat 库的 CMake 配置里我建议加上-fno-strict-aliasing因为这个库老到用了大量reinterpret_cast和类型双关高优化等级下容易被编译器优化出怪问题。把这层兼容头文件 include 进 BasicExcel.cpp 和 ExcelFormat.cpp 的最前面比在 IDE 里改全局定义要可移植得多。4. 在QT5工程里集成修改版CMake构建和两个封装类4.1 把修改版编译成独立静态库避免污染主工程集成时我不会把 Excelformat 的 .cpp 直接丢进主工程而是单独编一个静态库。这样有几个优点老库的警告噪音不会刷屏主工程后续机器迁移时只需要带上这个 .a 文件编译参数可以单独控制不影响 Qt 业务代码的编译标准。CMake 里的一段配置如下。cmake_minimum_required(VERSION 3.16) project(xls2003_demo LANGUAGES CXX) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets) # 修改版 Excelformat 静态库 add_library(excelformat_mod STATIC third_party/excelformat/BasicExcel.cpp third_party/excelformat/ExcelFormat.cpp third_party/excelformat/PlatformCompat.cpp ) target_include_directories(excelformat_mod PUBLIC third_party/excelformat ) target_compile_definitions(excelformat_mod PRIVATE NO_OLDNAMES1 ) target_compile_options(excelformat_mod PRIVATE -fno-strict-aliasing -w ) # 主程序 add_executable(demo main.cpp Xls2003Writer.cpp ) target_link_libraries(demo PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets excelformat_mod )几个参数值得解释。NO_OLDNAMES1是 Windows 系代码常见的开关让老库不要依赖微软风格的旧命名在 Linux 下可以减少一些头文件冲突-fno-strict-aliasing前面说过了是给老代码的保守选项-w是关掉警告因为老库的警告实在太多留着会影响真正错误的排查。如果你有洁癖可以去掉-w但要做好被几百条 warning 刷屏的心理准备。4.2 封装Xls2003Writer对外只暴露四个方法直接在主工程里调 BasicExcel 的 API 会很痛苦尤其是老库的命名和返回值不够直观。我习惯做一层薄封装让它只暴露四个方法创建文件、设置工作表名、写单元格、保存。其他细节全部收进实现里。// Xls2003Writer.h #pragma once #include QString class BasicExcel; class Xls2003Writer { public: Xls2003Writer(); ~Xls2003Writer(); bool create(const QString filePath, int sheetCount 1); void setSheetName(int sheetIndex, const QString sheetName); void setCell(int sheetIndex, int row, int col, const QString value); bool save(); private: BasicExcel* m_excel; QString m_filePath; };实现时有一条需要特别留意改工作表名之前要先确认当前工作表的 Status 满足库的要求不同小版本的枚举名不一样但基本都要先解除锁定再改名。否则 SetSheetName 会被静默忽略生成的文件里工作表仍叫 Sheet1用户在 Excel 里手动改能成功但程序改不生效。我在代码里统一先调一个unlockSheet的内部方法再设置名称。// Xls2003Writer.cpp 关键实现 bool Xls2003Writer::create(const QString filePath, int sheetCount) { if (m_excel) { delete m_excel; m_excel nullptr; } m_excel new BasicExcel(); m_excel-New(sheetCount); m_filePath filePath; return true; } void Xls2003Writer::setCell(int sheetIndex, int row, int col, const QString value) { BasicExcel::Worksheet* ws m_excel-Worksheet(sheetIndex); if (!ws) return; // 注意老库的接口在不同版本里叫 Cell(row,col) 或 GetCell(row,col) // 编译报错时把下面的调用换成对应版本即可 BasicExcel::Cell* cell ws-Cell(row, col); if (!cell) return; const std::wstring wstr ToBiffWString(value); cell-SetWString(wstr.c_str(), static_castint(wstr.size())); } bool Xls2003Writer::save() { if (!m_excel) return false; return m_excel-SaveAs(m_filePath.toStdString().c_str()); }这里最关键的参数是SetWString的第二个参数。它接收的是字符个数不是字节数在 Linux 下如果用wcslen * sizeof(wchar_t)传进去长度会翻四倍后续的 BIFF8 记录长度字段就会全部错位生成的文件打不开。ToBiffWString返回的 wstring 用wstr.size()取的是字符个数正好符合要求。4.3 封装Xls2003Reader读取单元格值并转回QString读取方向也要做一层封装核心是把 BasicExcel 的 Cell 类型翻译成 QString。读取时同样只走 WString 接口不要用 GetString因为那套接口在 Linux 的 wchar_t 下不可靠。// Xls2003Reader.cpp 关键实现 QString Xls2003Reader::cellText(int sheetIndex, int row, int col) { BasicExcel::Worksheet* ws m_excel-Worksheet(sheetIndex); if (!ws) return QString(); BasicExcel::Cell* cell ws-Cell(row, col); if (!cell) return QString(); if (cell-Type() BasicExcel::Cell::WSTRING) { const wchar_t* text cell-GetWString(); int len cell-GetWStringLength(); return FromBiffWString(text, static_castsize_t(len)); } if (cell-Type() BasicExcel::Cell::STRING) { const char* text cell-GetString(); int len cell-GetStringLength(); return QString::fromUtf8(text, len); } if (cell-Type() BasicExcel::Cell::DOUBLE) { return QString::number(cell-GetDouble(), g, 15); } return QString(); }判断单元格类型再取值这一步不能省。老库的枚举在不同版本里叫法略有差异但基本都有 STRING、WSTRING、DOUBLE 这几个分支。数字类型用g, 15格式化是为了避免把 123456789012345 这种 15 位以上数字变成科学计数法或丢精度如果你对精度要求更高可以改成f, 2之类的固定小数位。4.4 一个带中文路径的最小调用示例把上面两段串起来就是一个可以直接跑的示例。这个例子完整复现了标题里的需求路径是中文目录加中文文件名工作表名是中文单元格内容也是中文。// main.cpp #include QCoreApplication #include QDir #include QFileInfo #include Xls2003Writer.h int main(int argc, char* argv[]) { QCoreApplication app(argc, argv); const QString outPath QStringLiteral(/home/张三/报表/2024年度-设备台账.xls); QDir().mkpath(QFileInfo(outPath).absolutePath()); Xls2003Writer writer; if (!writer.create(outPath, 2)) { return 1; } writer.setSheetName(0, QStringLiteral(设备清单)); writer.setCell(0, 0, 0, QStringLiteral(设备编号)); writer.setCell(0, 0, 1, QStringLiteral(设备名称)); writer.setCell(0, 1, 0, QStringLiteral(E-1001)); writer.setCell(0, 1, 1, QStringLiteral(循环水泵)); if (!writer.save()) { return 2; } return 0; }这个示例里的mkpath是在 SaveAs 之前主动创建目录因为我前面说过 QSaveFile 不会自动创建父目录如果这个动作忘了做中文路径的报错往往不是“路径不存在”而是 open 返回 false排查起来很迷惑。跑通这段之后再往 Writer 里加样式、合并单元格、列宽这些能力时方向就清晰了。5. 中文路径读写2003的excel常见故障五个坑的定位与修复5.1 定位工具先行编译过但打不开先用strace看文件层在逐个列坑之前先说一个通用排查手段。中文路径问题有时发生在库解析之前程序和文件系统之间就断了这时靠打日志效率很低直接上 strace 看系统调用最干净。命令很简单strace -e traceopenat,read,close -o open_trace.log ./demo然后进日志里搜中文路径看 openat 的返回码。如果返回 ENOENT 或 EACCES说明路径本身或权限有问题问题不在 Excelformat如果 openat 成功但后续 read 返回长度为 0再回来查代码里的文件打开参数。用这个命令把问题定位到“文件层之上”还是“文件层之下”能省下大量试错时间。这也是我在 Linux 上做任何文件兼容问题时第一个要跑的命令。5.2 编译报错strcmpi未定义老库的Windows函数残留现象在 Linux 下编译 BasicExcel.cpp 或 ExcelFormat.cpp 时报strcmpi或_tcsicmp未定义有时还伴随__int64未声明。原因这套源码最初是按 Windows CRT 写的Linux 的 libc 不提供这些非标准命名。strcmpi在 Windows 里是大小写不敏感字符串比较Linux 等价的函数是strcasecmp。解决不要全局去改源码里的每个调用点用我在第 3 章写的 PlatformCompat.h 统一映射在 include 老库源码前先 include 它。如果你不想额外加文件也可以在 CMake 里直接定义宏映射但可读性差很多。改完再编译这类报错会一次性清掉。5.3 路径编码中文目录能建文件但程序自己的Load打不开现象同样的路径在文件管理器里能看到文件file 中文目录/报表.xls也能正常识别但程序里Load(/home/张三/报表/2024年度-设备台账.xls)返回 false。原因程序内部在某处提前把 UTF-8 的路径字符串用QString::fromLocal8Bit转了一次在 locale 为 C/POSIX 的服务器环境里fromLocal8Bit 会按 ASCII 解析中文导致字节被截断传给文件层的路径已经不是原来的 UTF-8 字节流。开发者本机 locale 通常是 UTF-8 所以测不出来一部署到 docker 容器就原形毕露。解决所有从外部传入的路径统一用QFile::decodeName和QFile::encodeName做本地编码转换不要手动选择 fromLocal8Bit 还是 fromUtf8。我在这套修改版里所有文件入口都改成了这个组合部署到不同环境的差异就消失了。5.4 单元格乱码路径通了但中文内容在Excel里是乱码现象文件能生成用 LibreOffice 打开路径正常但单元格里的中文显示成类似“䏿–‡”的乱码。原因写入时调用了SetString而不是SetWString。老库的 SetString 会把 char* 按当前 locale 或直接按字节塞进 BIFF8 的窄字符串记录而 2003 格式内部对 Unicode 文本用的是 UTF-16LE中文的 UTF-8 字节被逐字节当 Latin-1 处理后就变成了乱码。读取方向同理GetString 出来的内容不能直接 QString::fromUtf8。解决单元格写入统一走ToBiffWString加SetWString读取统一走GetWString加FromBiffWString。这一步做完中文路径和中文内容是两套独立机制别再混在一起排查。5.5 保存出来的xls打不开文本模式或长度计算错误现象保存成功返回 true文件大小也不是 0但用 Excel 打开提示“文件格式和扩展名不匹配”用 LibreOffice 打开直接是乱码。原因常见两处。第一处是文件流没按二进制模式打开老库的ofstream忘了ios::binary在 Linux 下写入时把 0x0A 当换行转换第二处是SetWString的长度参数按字节数传了BIFF8 记录里的字符串长度字段和实际数据对不上整个记录流错位。解决写入统一走 QSaveFile它默认就是二进制长度参数则按字符个数传我在这套修改版里已经把这两个点都封死了你在自己的项目里改时记住这两条就是最容易做错的位置。6. 验收这套源码修改版三条交叉检验路径和一份复测清单验证一个 .xls 读写模块单靠“程序没崩”完全不够必须做独立于 Excelformat 的交叉检查。我会按三条路径来验收。先用file命令确认文件的 CFB 容器标识再用 LibreOffice 无头模式转换 PDF 验证可读性最后用 Python 的 xlrd 读取单元格并和写入值做比对。# 第一条确认文件是合法的 OLE 复合文档 file /home/张三/报表/2024年度-设备台账.xls # 预期输出包含 Composite Document File V2 Document # 第二条用 LibreOffice 无头模式转换 PDF退出码为 0 说明能正常解析 soffice --headless --convert-to pdf --outdir out /home/张三/报表/2024年度-设备台账.xls echo $?第三条用 Python 的 xlrd 读取注意装 1.2.0 这个最后的支持 .xls 的版本。读的时候按工作表索引拿值和 Qt 侧写入的二维表逐项比对这一条能直接发现 BIFF8 记录索引错位问题。import xlrd wb xlrd.open_workbook(/home/张三/报表/2024年度-设备台账.xls) ws wb.sheet_by_index(0) for r in range(ws.nrows): for c in range(ws.ncols): print(r, c, ws.cell_value(r, c))三条都过了之后我会跑一遍复测清单。必测项包括路径含中文、文件名含空格、工作表名含中文和特殊符号、单个工作表写入到 65535 行、单元格内容超过 255 个字符、文件名不带扩展名。2003 格式的行数上限是 65536所以写入前最好在 Writer 里做一个行数检查超过直接返回错误否则库内部可能写出一个自己都读不回来的文件。把这套修改版固化成静态库放进工程后我遇到的大部分问题都集中在第一次编译和第一次中文写入之后的迭代基本不会再碰这些底层坑。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑