阅读本文大概需要 6 分钟
之前有一篇文章介绍过 C++/Qt
操作 Word
的一些方法,虽然能满足一部分使用场景,但是终究是在某些平台上有限制,使用起来还是不方便,所以就有了这边文章
我们知道操作 Word
其实还有一种方法,那就按照 OOXML
规范读写即可,OOXML
是微软 2007之后推出的一套标准,凡是符合这个标准生成的文档都可以正常打开,遗憾的是这方面 C++
没有可用的库,一是因为本身 C++
人群少,二是是用 C++
实现工作量大,所以就只能选择现有成熟的轮子
Python
有非常多的开源库可以使用,其中有一个Python-docx
库,完美实现了Word
读写,使用 C++
调用 Python
是非常方便的,所以可以间接来实现 Word
的交互
支持功能:
-
支持自定义标题,包括样式、字体、对齐方式、标题级别等;
-
支持插入任意行列表格,表格支持单独设置某个单元格样式,字体、颜色、是否加粗、水平、垂直对齐方式等;
-
支持合并任意单元格;
-
支持插入图片,支持相对路径和绝对路径
下面看测试导出的效果:
原理介绍
我们知道 C/C++/Qt
都是编译型语言,也是是说不能直接从源码运行,而Python
是解释型语言,不需要经过编译成二进制代码可以直接从源码运行,在运行 Python
的时候首先经过 Python
解释器解释,你可以理解成翻译的意思,解释成字节码,然后在一条一条字节码指令开始执行
Python
提供了一些C
库,我们可以在C/C++
程序中包含对应头文件、库文件,进而调用函数方法来实现某个功能
调用 Python
主要流程如下:
- 初始化
Python
上下文环境(解释器环境) - 导入对应的模块
- 获取对应函数对象,参数转换,调用函数
- 解析返回值,结束调用
- 释放
python
解释器
C++
根据实际业务生成对应的JSON
字符串,然后调用Python
传递给对应函数,在Python
函数中解析JSON
字符串然后生成Word
内容
环境配置
下载并安装好Python
相关库,确保本地环境没有问题,记得安装好Python-docx
库。拷贝Python
相关依赖库到你的项目目录,不如下面这样
QtPythonDocx
| 3rdparty
│ └─Python310
│ ├─include
│ │ ├─cpython
│ │ └─internal
│ └─libs
├─bin
│ ├─Python310
│ │ ├─DLLs
│ │ └─Lib
| |─script
│ │ wordOperate.py
关于一些版本事项、以及中间会遇到那些坑,文末有注意事项统一介绍
调用 Python
库
为了做到简洁、通用,我们编写一个脚本调用类,该类和具体的业务无关,只负责传入不同模块、函数、参数调用对应的Python
函数并能够返回对应的结果,这样后续的调用者就使用的时候和使用普通函数没有区别
为了实现这个目的,目前有几个知识点需要解决:
- 由于
Python
数据类型和C++
不一样,如果要通用那么就需要进行转换,怎么做到C++
一个参数类型匹配Python
多个类型? - 返回值处理,我们的业务函数返回值可能多种多样,怎么兼容?
- 编码转换,
Python
中支持UTF-8
,我们程序处理中数据可能包含多种类型,怎么转换
解决了上述问题,基本也就是完成了本次要写的脚本加载类
脚本调用类实现
首先看下类型问题,其实我们这里需要一个万能类型来作为函数入参,那么有这个类型么?有,如果你的编译器支持 C++17
,那么可以用std::variant
std::variant<int, double, std::string> inputArg
由于作者本人对 Qt
比较熟一点,所以本次程序中使用了大量的Qt
内置数据类型,原理是相通的
KPythonRunScript
类的实现,核心函数如下所示
bool callFun(const char *funcName,
const QVariantList &args = QVariantList(),
QVariant &returnValue = QVariant(QVariant::Invalid));
funcName
: python 脚本中对应的函数名字args
: 函数入参,根据实际脚本中函数参数个数而定returnValue
: 返回值,如果脚本函数有返回值初始化的时候赋予对应类型
实际Python
脚本中函数的入参个数是不确定的,为了兼容多个调用场景,所以采用了数组作为实际的入参,数组每个元素采用QVariant
类型,这样就能根据实际传入的类型来判断,在调用Python
的时候应该转换为什么类型
返回值类型也一样,初始化调用时确定好本次调用的返回值类型,这样在Python
脚本调用完成后才能把返回值转为我们C++
实际的返回值
类型转换:
for(int index = 0; index < args.size(); index++)
{
QVariant arg = args[index];
switch (arg.type())
{
case QVariant::String:
{
QByteArray baContent = arg.toString().toLocal8Bit();
PyTuple_SetItem(pArgsObj, index, Py_BuildValue("s", baContent.constData()));
}
break;
case QVariant::Int: PyTuple_SetItem(pArgsObj, index, Py_BuildValue("i", arg.toInt())); break;
case QVariant::Double: PyTuple_SetItem(pArgsObj, index, Py_BuildValue("d", arg.toDouble())); break;
case QVariant::LongLong: PyTuple_SetItem(pArgsObj, index, Py_BuildValue("l", arg.toLongLong())); break;
case QVariant::Char: PyTuple_SetItem(pArgsObj, index, Py_BuildValue("b", arg.toChar().toLatin1())); break;
case QVariant::Invalid: PyTuple_SetItem(pArgsObj, index, Py_BuildValue("()")); break;
default: break;
}
}
这里目前适配了上述几种类型,如果后续不满足继续扩展其它类型即可
Python
脚本对应的函数
def generateWord(strContent):
#...
return True
详细调用
在上述实现的类的基础上,调用其实就变的很简单了,就和我们调用本地某个函数一样,非常轻松
KPythonRunScript *pRunScript = KPythonRunScript::instance("wordOperate");
QVariant returnValue = true;
QVariantList args = {""};
bool bResult = pRunScript->callFun("generateWord", args, returnValue);
qDebug() << "run generateWord result:" << bResult << returnValue;
if(!bResult)
{
qWarning() << "write word fail.....";
return;
}
可能你注意到程序中使用了单例,为什么使用单例?这是因为单个进程Python
解释器相关内容初始化一次即可,后续随意调用不用再次初始化,实际验证中也证实了,多次初始化会有一些异常问题(虽然每次用完已经释放了,再次初始化还是会有问题)
这样就实现了一个简单的调用过程,具体Python
文件中的内容可以看我开源的工程目录中的内容,其实就是把各种操作Word
方法封装成函数了,扩展了常用的字段
QtPythonDocx/bin/script/wordOperate.py
JSON
格式说明
由于 Word
内容较多,调用时兼容很多写入场景,因此目前设计使用 JSON
格式来交互,基本覆盖大部分使用场景,而且支持各种自定义,完全满足日常使用,下面是各个字段的说明
全局配置
- savePath: 定义了生成的
Word
文档路径,确保该路径有写入权限,否则可能会失败 - openFile: 导入成功后是否打开该文档
- line_spacing: 行间距,默认给 1.5倍
- header: 页眉文本,不需要页眉直接给空即可
- footer: 页脚文本
- content:[] 这里是
Word
内容部分,采用数组存储,由于数组有有序的,因此严格按照你的内容顺序依次传入即可 - fontSize: 全局字体大小
- fontName: 全局字体名字,设置后后续每个正文、标题、表格等可以不用设置,全局统一
正文
下面是正文内容部分说明
- type: 标识是那种类型,0:标题,1: 普通文本,2:图片,3:表格,其它类型后续扩展自定义
- text: 如果是文本或者标题给定内容
- level: 级别,目前只有标题类型生效
- bold: 是否加粗
- italic: 是否倾斜
- strike: 是否删除线
- alignment: 对齐方式,主要有这么几种:left, right,center
- color: 对应文本的颜色
- height: 行高
插入表格
如果是表格,那么有这些扩展字段
- columns: 列数
- rows: 行数
- height: 行高,所有行设置一样的行高,也可以自定义每行的行高
- mergeCells: 要合并的单元格数组,比如合并 (0,0)和(0,1)单元格,那么内容如下
{"begin": [0,0], "end": [0,1]}
- tableCell: 单元格内容,依次填充每个单元格内容即可,每个单元格内容和普通文本类似,下面是一个示例
tableCell": [
{"text": "我是第一个单元格,加粗,倾斜,红色", "style": "", "bold": true, "italic": true,"color": "#ff0000","alignment": "center"},
{"text": "00和01合并了,02会覆盖01的值,加粗变红,左对齐", "style": "", "bold": true, "italic": false,"color": "#ff0000","alignment": "left"},
{"text": "03", "style": "", "bold": false, "italic": false,"color": "#000000","alignment": "center"},
{"text": "04", "style": "", "bold": false, "italic": false,"color": "#000000","alignment": "center"},
{"text": "05", "style": "", "bold": false, "italic": false,"color": "#000000","alignment": "center"},
{"text": "06", "style": "", "bold": false, "italic": false,"color": "#000000","alignment": "center"},
{"text": "07", "style": "", "bold": false, "italic": false,"color": "#000000","alignment": "center"},
{"text": "08", "style": "", "bold": false, "italic": false,"color": "#000000","alignment": "center"}
]
插入图片
图片字段和其它文本字段类似,额外添加图片路径属性即可
- picture: "./test.png"
注意图片路径支持相对路径和绝对路径,根据自己实际需要传递即可
总结
本次通过Python
的方式可以很好的支持很多之前出现的异常问题,足以满足我们遇到的各种业务需要导出生成Word
难题,而且导出速度非常快,实际测试生成 10
页左右文档耗时不到 2
秒,测试了多台电脑,实际效果都非常理想
注意事项
Python
版本选择问题,确保你的程序最终要运行的平台,如果要最低要求是Windows7
,那么建议选择Python3.8
版本即可,如果无所谓那么选择最新稳定版本即可;Python
注意选择和你程序使用同一个位数,程序编译器使用的是64
位,那就下载64
位,32
位同理 ;
推荐阅读