ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

QtXlsxWriter适配Qt6:从QRegExp到QStringDecoder的源码迁移实战

QtXlsxWriter适配Qt6:从QRegExp到QStringDecoder的源码迁移实战 简介面向 Qt6 与 C 开发者的 QtXlsx 读写 Excel 组件适配包基于 GitHub 上 dbzhang800/QtXlsxWriter 仓库 2020-03-19 版本源码针对 Qt6 接口变化完成兼容性修改并在 Deepin20 的 Qt6 环境下编译通过。资源共 187 个文件其中 67 个 cpp 源码、47 个 h 头文件、44 个 pro/qmake 工程配置另有 qdoc 文档、示例图片与编译配置等压缩包仅 561KB结构紧凑便于对照学习。已有 412 人浏览学习。通过比对源码修改点读者可掌握 Qt5 到 Qt6 移植中信号槽语法、模块划分等关键差异并直接获得一套可在 Qt6 下正常构建的 Excel 读写组件适合需要将旧版 QtXlsx 项目升级到 Qt6或希望从源码层面理解该组件实现原理的开发者。1. QtXlsxWriter 适配 Qt6 的源码包从停更老库到能跑起来的改动记录在做 Qt6 Excel C 开发组件选型时你多半会遇到一个尴尬Qt 官方没有像样的 Excel 读写库社区里呼声很高的 QtXlsxWriter 又停在 Qt5 时代。这个 zip 就是把 dbzhang800/QtXlsxWriter 在 2020-03-19 时间版本基础上为 Qt6 做源码级适配后的结果并且在 Deepin20 的 Qt6 环境里实际编译通过。它解决的是迁移过程中最让人头疼的两块QRegExp 被移除、QTextCodec 被移除所带来的连锁编译错误。适合正在从 Qt5 往 Qt6 迁移的桌面应用开发者也适合需要在 C 里直接生成或读取 xlsx 的工程。2. 先搞懂 Qt6 移除了什么从 API 变更反推迁移工作量2.1 Qt6 的核心库变化Qt6 把原本庞大的 QtCore 做了拆分很多 Qt5 里随手就用到的类到了 Qt6 要么被删掉要么被挪进单独的兼容库。对 QtXlsxWriter 这种老库来说最致命的两个类是 QRegExp 和 QTextCodec。QRegExp 在 Qt6 里被彻底移除官方给出的替代品是 QRegularExpression两者的接口和匹配语义差异很大。QTextCodec 则被移出 QtCore官方建议改用 QStringDecoder / QStringConverter但老代码里到处是QTextCodec::codecForName(UTF-8)或.toUnicode()替换思路需要转一圈。除了这两个大头还有一批“软不兼容”。比如 Qt6 把 QXmlStreamReader / QXmlStreamWriter 对 XML 命名空间的处理方式改了解析严格程度更高QDateTime 对时间规格的处理变了部分构造函数和枚举被标记为过时Qt Charts 独立成单独模块编译时必须额外指定QT charts否则 xlsxchart.cpp 一编译就报找不到 QChart。另外 Qt6 强制要求编译器支持 C17老库的 .pro 里如果还写着CONFIG c11连 Qt 头文件都过不去。2.2 用 grep 圈定修改范围拿到源码第一步别急着打开 IDE先在终端里把 Qt5 时代遗留的“定时炸弹”扫一遍。我在适配这份源码时第一件事就是在 QtXlsxWriter 的 src 目录下执行grep -rE QRegExp|QTextCodec|QDesktopWidget|QFontMetrics.*width|QString::SkipEmptyParts src/执行结果会直接告诉你哪些文件依赖了 Qt6 不再提供的接口。比如 src/xlsx/xlsxutils.cpp 里会有 QTextCodec 的使用src/xlsx/xlsxworksheet.cpp 里几乎必然出现 QRegExp。这一步的意义是把“改哪里”变成明确的清单而不是等编译报错了再一个个文件去翻。Qt6 的编译器错误信息通常会先报在头文件包含上比如No such file or directory #include QRegExp这时候一个文件里可能只有一个地方用了这个类但错误信息会让人误以为整个文件都要重写。在正式修改之前我习惯先把一次完整编译的日志存下来/usr/lib/qt6/bin/qmake ../ make -j$(nproc) 21 | tee build.log grep -nE error:|No such file|undefined reference build.log这里建议把 qmake 写成全路径而不是直接用 PATH 里的 qmake。很多迁移第一天翻车的人都是因为系统里同时有 Qt5 和 Qt6终端里敲qmake调到了老的 Qt5 版本导致后续所有报错都不是 Qt6 真正的问题。全路径调用后编译日志里的错误顺序才可信第一次错误通常出现在 xlsxutils.cpp 或 xlsxdocument.cpp往后才是 worksheet、styles 这些文件。2.3 先改工具层还是先改业务层QtXlsxWriter 的源码分层其实很清晰xlsxdocument.cpp 是对外门面负责打开和保存文件xlsxworkbook.cpp、xlsxworksheet.cpp 负责工作簿和单元格模型xlsxstyles.cpp、xlsxformat.cpp、xlsxconditionalformatting.cpp 负责样式xlsxchart.cpp、xlsxdrawinganchor.cpp 负责图表和绘图锚点。修改顺序有个原则先工具层再业务层最后才是门面。因为编码转换、正则匹配这些工具函数一旦改完上层文件的编译错误才会真正暴露出来不会被同一类错误反复打断。在 Dbzhang800 的原始工程里xlsxutils.cpp 承担了大量编码转换和字符串解析工作它虽然在这次发布包的清单里没有出现但如果你是从原库整体 checkout 再套用适配补丁这个文件也需要做一次同样手法的修改。我带的这个发布包里改动集中在 xlsxworksheet.cpp、xlsxstyles.cpp、xlsxformat.cpp、xlsxdocument.cpp、xlsxconditionalformatting.cpp、xlsxworkbook.cpp、xlsxchart.cpp、xlsxdrawinganchor.cpp 这几个文件外加两个构建配置文件。它们和原库的关系是“覆盖式替换”不是“新增文件”。也就是说你先把原库完整拉下来再用这个 zip 里的同名文件覆盖改完后整体编译一次。3. 逐文件适配编码、正则、模块三关怎么过3.1 编码层迁移QTextCodec 换成 QStringDecoderQt6 移除 QTextCodec 后最直接的替换方案是 QStringConverter 体系的 QStringDecoder。QtXlsxWriter 里负责编码探测的代码通常是先拿到一段字节判断它是不是合法 UTF-8再决定用 UTF-8 还是 Latin-1 解码。Qt5 时代的常见写法是这样// 这段是 Qt5 的常见写法Qt6 下编译不过 #include QTextCodec QTextCodec *codec QTextCodec::codecForName(UTF-8); QTextCodec::ConverterState state; QString str codec-toUnicode(ba.constData(), ba.size(), state); bool isUtf8 (state.invalidChars 0);Qt6 下我改成 QStringDecoder。#include QStringDecoder QStringDecoder decoder(QStringConverter::Utf8); QString str decoder.decode(ba); bool isUtf8 !decoder.hasError();这两段代码逻辑上等价Qt5 通过 ConverterState 的 invalidChars 计数判断 UTF-8 解码是否遇到非法字节Qt6 的 QStringDecoder 则在 decode 过程中记录错误状态hasError() 返回 true 就说明这段字节不是合法的 UTF-8 序列。有一个细节要特别注意QStringDecoder 的 decode 接口接收的是 QByteArray如果你手里的数据是const char *加长度需要先包成 QByteArray 再传进去否则会走 QByteArray 的隐式转换遇到二进制内容时容易丢数据。在 xlsxdocument.cpp 里凡是加载 sharedStrings.xml 或者其他文本型资源时用到的编码入口我都统一换成了这个模式。修改完之后记得检查整个工程里是否还有对 QTextCodec 的间接引用比如某个头文件 include 了 QTextCodec虽然原文件没直接用它Qt6 下也会因为头文件不存在而报错。3.2 正则层迁移QRegExp 换成 QRegularExpressionxlsxworksheet.cpp 里的正则使用量最大主要用来解析单元格地址比如把 “$B$12” 这类字符串拆成列号和行号。Qt5 的 QRegExp 写法是这样的。QRegExp rx(^\\$?([A-Z]{1,3})\\$?(\\d)$); if (rx.indexIn(cellRef) ! -1) { QString colStr rx.cap(1); QString rowStr rx.cap(2); }改成 QRegularExpression 之后匹配流程从 indexIn 变成了 match捕获组也从 cap(n) 变成了 captured(n)。这是 Qt6 强制推的 API不能像其他兼容问题那样打个补丁糊弄过去。#include QRegularExpression QRegularExpression rx(^\\$?([A-Z]{1,3})\\$?(\\d)$, QRegularExpression::CaseInsensitiveOption); QRegularExpressionMatch match rx.match(cellRef); if (match.hasMatch()) { QString colStr match.captured(1); QString rowStr match.captured(2); }替换时有个容易忽略的差异QRegExp 默认是大小写不敏感的不对QRegExp 默认大小写敏感但 QRegularExpression 的默认行为同样是大小写敏感区别在于 QRegExp 的部分重载和匹配模式没有那么严格。实际踩坑点在于QRegularExpression 对转义符的处理比 QRegExp 严格在 C 字符串里写正则模式时\$和\\$的区别会直接影响匹配结果。建议所有正则都写成原样字符串也就是用R()包裹这样源码里看到的和真正传给正则引擎的是一致的。比如上面那个匹配单元格的正则用原生字符串可以写成R(^\$?([A-Z]{1,3})\$?(\d)$)少一层转义模糊排查起来也直观。xlsxstyles.cpp 和 xlsxformat.cpp 里的正则相对少一些主要集中在数字格式和主题色解析。这些文件里 QRegExp 的替换手法完全一样把rx.indexIn(...) ! -1改成match.hasMatch()把rx.cap(n)改成match.captured(n)。注意 QRegularExpressionMatch 是一次匹配的快照不能像 QRegExp 那样反复调用 cap 取值应该先把 match 存到局部变量里再取捕获组。凡是循环里多次用同一正则的地方把 QRegularExpression 对象提升到循环外构造避免每轮迭代都重新编译正则表达式性能差别在大量单元格写入时会很明显。3.3 类型与模块调整条件格式、图表和绘图锚点xlsxconditionalformatting.cpp 涉及的改动主要是 QVariant 和 QColor 的兼容。Qt6 对 QColor 的某些构造函数做了调整比如不推荐从 int 直接构造 ARGB 颜色值建议显式用QColor::fromRgba。条件格式里大量用到颜色枚举和规则比较替换时要把 QColor 相关的隐式转换全部改成显式调用否则编译器会给出 deprecated 警告而 Qt6 在开启QT_DISABLE_DEPRECATED_BEFORE宏之后会把这类警告直接升格为错误。xlsxchart.cpp 是另一个重头。Qt6 把 Charts 模块从 QtCore 系列中拆出来单独作为 QtCharts 模块维护。编译这个文件之前先确认你的 Qt6 安装包是否带 Charts 模块。官方安装包在组件选择里需要勾选 Qt ChartsDebian 系发行版则要安装 qt6-charts-dev 这类包。代码层面xlsxchart.cpp 里的头文件包含要改成#include QtCharts/QChart #include QtCharts/QChartView #include QtCharts/QLineSeries同时在 .pro 文件里追加QT charts。如果只是用 QtXlsxWriter 做纯数据写入图表模块不是必选项可以先把 xlsxchart.cpp 从 SOURCES 里摘掉等图表需求真的出现再编译这部分。这个取舍能让第一次编译的报错数量少一半。xlsxdrawinganchor.cpp 里主要是坐标解析和图片锚点定位它依赖 QXmlStreamReader 解析 DrawingML 里的 anchor 节点。Qt6 对 QXmlStreamReader 的 namespaceUri 处理更严格如果 XML 里没有声明正确的命名空间isStartElement()的判定会跟 Qt5 行为不一致。遇到这类问题不要硬改字符串匹配先检查 XML 头部的命名空间声明在解析前把命名空间预声明好。这里也容易出现 QRegExp 用在坐标字符串解析的情况比如解析x123这样的属性替换方式跟 3.2 节一致。4. 在 Deepin20 的 Qt6 环境编译落地qmake 配置与命令4.1 安装包怎么选Qt6 的安装路径决定后续所有坑其实最开始我也在“qt6最新版安装包选哪些”这个问题上绕了一下。Deepin20 是基于 Debian 的桌面 Linux默认 apt 源里的 Qt6 包有时不完整或者版本偏旧。如果你准备把 QtXlsxWriter 改完直接商用我建议不要从 apt 仓库拼凑 Qt6 的零散模块而是用 Qt 官方安装包装一套完整的 gcc_64 套件。组件选择上基础编译库只需要 Qt 6 Base 和对应开发工具也就是 qmake、moc、rcc 这些如果后面要编译 xlsxchart.cpp再补上 Charts 模块。不要图省事把所有组件全选几百个模块装完后 PATH 环境变量会被 Qt Creator 自带的工具链干扰qmake指向错了后边每一步都白做。装好后确认一下 qmake 版本最好直接用全路径检查。/opt/Qt/6.x.x/gcc_64/bin/qmake --version输出里应该明确指出这是 Qt6 的 qmake而 Qt Creator 底下自带的 qmake 或者老项目里配置的 Qt5 qmake 都要从 PATH 里临时移除。检查和安装包版本绑定环境干净是后面能顺利编译的前提这一步不值得为了省时间跳过。4.2 .pro 和 .qmake.conf 的调整这个发布包里带了 .qmake.conf它是 qmake 在进入子项目目录时读取的配置文件适合放一些影响所有子项目的公共设置。Qt6 强制要求 C17所以 .qmake.conf 里至少要加一行CONFIG c17如果是从原库迁移原来的 .pro 文件里可能还在用CONFIG c11记得把它删掉否则会出现一类很隐蔽的错误Qt6 的头文件语法本身没问题但编译器因为你强制了 c11 标准在处理某些标准库头文件时提前报错。这类错误不会指向 QtXlsxWriter 自己的源码而是指向 /opt/Qt/6.x.x 下的头文件排查起来非常费时间。另一个容易忽略的点是静态库和动态库的选择。QtXlsxWriter 原工程生成的是共享库Qt6 下如果你希望最终程序能一键拷贝到别的机器上跑建议在 .pro 里追加CONFIG staticlib这样构建产物是 .a 文件链接进你的主程序里运行时不依赖额外的 QtXlsx 动态库。代价是编译时间变长但部署时省掉了LD_LIBRARY_PATH的折磨。两种模式我都试过动态库适合开发和调试静态库适合交付按你当前项目的发布方式选没有绝对的对错。4.3 编译安装命令示例源码目录正确、配置改完之后编译命令很简单。关键点是每次重新编译前先make clean或者直接新建一个独立构建目录避免 Qt5 时代的 object 文件残留。unzip QtXlsxWriter-qt6.zip -d xlsx-qt6-src cd xlsx-qt6-src mkdir -p build-qt6 cd build-qt6 /opt/Qt/6.x.x/gcc_64/bin/qmake ../ make -j$(nproc) sudo make installmake -j$(nproc)会把本机 CPU 核心数自动传给 make编译速度拉满但在内存较小的开发机上我建议保守一点直接make -j4因为 QtXlsxWriter 编译时每个 cpp 文件都要处理 Qt 头文件内存占用比想象中高。make install 会默认把库和头文件安装到 Qt 目录或者 /usr/local 下如果不想污染系统目录可以在 qmake 时指定安装前缀/opt/Qt/6.x.x/gcc_64/bin/qmake ../ make -j4 make install INSTALL_ROOT/opt/xlsx-qt6这里 INSTALL_ROOT 指定的目录会在 make install 时自动创建路径最好和你的业务项目放在一起主工程引用时直接-I/opt/xlsx-qt6/include和-L/opt/xlsx-qt6/lib环境变量都不用改。5. 避坑记录从 Qt5 迁到 Qt6 中我踩过的坑5.1 QRegExp 头文件直接消失现象编译 xlsxworksheet.cpp 时第一行就报错#include QRegExp提示 No such file or directory紧接着后面几十个错误全是围绕 QRegExp 类型不存在的。原因Qt6 彻底移除了 QRegExp没有做任何兼容头文件。原始工程里很多 cpp 通过其他头文件间接包含 QRegExp比如某个公共头文件里写了#include QtCore/QRegExp一旦这个公共头文件被修改错误才会暴露。解决全项目 grep QRegExp把所有用到的地方一次性替换成 QRegularExpression。替换时不只是换类型名indexIn、cap、pos这些老接口也都要同步换。我建议先替换 xlsxutils.cpp 这类工具层再往上替换业务层每换一个文件就编译一次把错误范围控制在单个文件内。5.2 QTextCodec 符号找不到但代码里明明 include 了现象代码里明明写了#include QTextCodec编译时却报QTextCodec is not a member of Qt或者链接时出现 undefined reference。原因Qt6 把 QTextCodec 移到了 Qt5Compat 模块单独保留了一个 QtCore5Compat 库。如果你的项目没有显式链接 Qt5Compat就算头文件路径能找到类声明也无法使用。QtXlsxWriter 这种老库大概率不会主动引入 Qt5Compat所以这条路走不通。解决不要试图链接 Qt5Compat 来省事。Qt5Compat 的存在意义是给大规模老项目过渡用的对 QtXlsxWriter 这种库直接用 QStringDecoder 替换编码逻辑更干净性能更好也不依赖一个额外的兼容库。记住替换后要检查hasError()的状态判断逻辑上承接 Qt5 的 invalidChars 计数。5.3 qmake 版本混用导致整个工程解析失败现象执行 qmake 时没有任何报错但生成的 Makefile 里写的是 Qt5 的路径编译时疯狂的找不到 Qt6 模块。原因系统里有多个 Qt 版本PATH 里的 qmake 顺序不对。我当时在 Deepin20 上装了 Qt5 的项目依赖后来又装了 Qt6终端的 qmake 被 Qt5 抢走了。解决所有 qmake 调用都写全路径。Qt6 官方安装包的 qmake 位于 /opt/Qt/6.x.x/gcc_64/bin/qmakeapt 方式安装的通常叫 qmake6。在 .pro 文件里也可以通过isEmpty(QMAKE_QMAKE)这类判断来校验当前的 qmake 版本不过最简单的方式还是编译前敲一下qmake --version并确认输出如果显示的是 5.x就停下来纠正路径别让后续的 make 在错误的 Makefile 上浪费时间。5.4 图表相关文件编译报错找不到 QChart现象编译 xlsxchart.cpp 时QChart、QChartView这些类全部提示未知类型代码没有任何拼写错误。原因Qt6 中 Qt Charts 不再是 Qt 核心库的一部分它变成了一个独立的模块需要单独安装包和 .pro 里的模块声明。官方安装包如果没有勾选 Charts 组件或者 .pro 里没写QT charts就会出现这个问题。解决先确认安装包里是否带 Charts。官方安装器重跑一遍勾选 Qt Charts 模块不重新安装的话就用系统包管理器安装 qt6-charts-dev。代码层面在 .pro 里加QT charts。如果你确定用不到图表功能直接把 xlsxchart.cpp 从 SOURCES 里去掉更省事这不算阉割功能只是把不使用的部分摘出去对数据写入和读取没有影响。5.5 C17 标准没有开启编译器错误指到 Qt 头文件内部现象编译时没有报你的代码有问题而是报 /opt/Qt/6.x.x/include 下的某个头文件内部语法错误比如std::optional相关的问题。原因Qt6 的头文件本身使用了 C17 标准库特性如果你的 .pro 里仍然写着CONFIG c11编译器就会在引入标准库头文件时直接崩掉。这类错误很难联想到自己的工程配置因为错误定位不在你的源码上。解决检查 .pro 和 .qmake.conf确保 C17 或更高标准被启用。追加CONFIG c17然后先重新 qmake再进行 make让 Makefile 带上新的编译参数。如果你用的是 CMake对应的是target_compile_features(tgt PUBLIC cxx_std_17)或set(CMAKE_CXX_STANDARD 17)。6. 用最小 Demo 验证移植成果再做两件延伸的事验证一个库能不能用别急着写业务代码先用 30 行内的小程序把写入、保存、读取走一遍。这个 Demo 不依赖任何业务模块只验证核心路径。#include xlsxdocument.h #include QDateTime #include QDebug int main(int argc, char *argv[]) { QXlsx::Document doc; doc.write(A1, hello qt6); doc.write(A2, 42); doc.write(A3, QDateTime::currentDateTime().toString(Qt::ISODate)); bool ok doc.saveAs(demo.xlsx); if (!ok) { qCritical(save failed); return 1; } QXlsx::Document reader(demo.xlsx); QVariant v reader.read(A1); qDebug() v.toString(); return 0; }编译这个 Demo 的 .pro 文件也很简单QT core gui CONFIG c17 INCLUDEPATH /opt/xlsx-qt6/include /path/to/QtXlsxWriter-src/src/xlsx LIBS -L/opt/xlsx-qt6/lib -lQtXlsx SOURCES main.cpp写成功后用 Excel 或 WPS 打开 demo.xlsx检查单元格内容再把 Demo 里的读取逻辑跑一遍确认写入和读取是自洽的。验证到这里这份源码包在你的发行版和 Qt6 版本上就算正式落地了。接下来我把这套源码的用法往外延伸了一步。第一次移植成功后我把同样的替换手法用在了另一个停止维护的 Qt5 库上流程完全复用先 grep QRegExp 和 QTextCodec然后按工具层、业务层、门面的顺序逐个替换最后用最小 Demo 收口验证。从那以后每当我拿到一个停在 Qt5 时代的老库第一反应不是去翻业务代码而是先跑一遍grep -R QRegExp\\|QTextCodec src/确认没有这两类硬骨头再谈集成。文本、样式、图表逐个模块加回来每加一个模块就编译一次把出问题的范围限制在刚加入的部分排查时间能省下一大半。希望这份适配过程和踩坑记录能帮到正在跟 Qt6 迁移较劲的人也希望你少走几个我走过的弯路。本文还有配套的精品资源点击获取
返回列表