C++(Qt) 和 Word 交互總結(jié)(二)
“閱讀本文大概需要 6 分鐘
之前有一篇文章介紹過 C++/Qt
操作 Word
的一些方法,雖然能滿足一部分使用場(chǎng)景,但是終究是在某些平臺(tái)上有限制,使用起來還是不方便,所以就有了這邊文章
我們知道操作 Word
其實(shí)還有一種方法,那就按照 OOXML
規(guī)范讀寫即可,OOXML
是微軟 2007之后推出的一套標(biāo)準(zhǔn),凡是符合這個(gè)標(biāo)準(zhǔn)生成的文檔都可以正常打開,遺憾的是這方面 C++
沒有可用的庫,一是因?yàn)楸旧?C++
人群少,二是是用 C++
實(shí)現(xiàn)工作量大,所以就只能選擇現(xiàn)有成熟的輪子
Python
有非常多的開源庫可以使用,其中有一個(gè)Python-docx
庫,完美實(shí)現(xiàn)了Word
讀寫,使用 C++
調(diào)用 Python
是非常方便的,所以可以間接來實(shí)現(xiàn) Word
的交互
支持功能:
支持自定義標(biāo)題,包括樣式、字體、對(duì)齊方式、標(biāo)題級(jí)別等;
支持插入任意行列表格,表格支持單獨(dú)設(shè)置某個(gè)單元格樣式,字體、顏色、是否加粗、水平、垂直對(duì)齊方式等;
支持合并任意單元格;
支持插入圖片,支持相對(duì)路徑和絕對(duì)路徑
下面看測(cè)試導(dǎo)出的效果:

我們知道 C/C++/Qt
都是編譯型語言,也是是說不能直接從源碼運(yùn)行,而Python
是解釋型語言,不需要經(jīng)過編譯成二進(jìn)制代碼可以直接從源碼運(yùn)行,在運(yùn)行 Python
的時(shí)候首先經(jīng)過 Python
解釋器解釋,你可以理解成翻譯的意思,解釋成字節(jié)碼,然后在一條一條字節(jié)碼指令開始執(zhí)行
Python
提供了一些C
庫,我們可以在C/C++
程序中包含對(duì)應(yīng)頭文件、庫文件,進(jìn)而調(diào)用函數(shù)方法來實(shí)現(xiàn)某個(gè)功能
調(diào)用 Python
主要流程如下:
初始化
Python
上下文環(huán)境(解釋器環(huán)境)導(dǎo)入對(duì)應(yīng)的模塊
獲取對(duì)應(yīng)函數(shù)對(duì)象,參數(shù)轉(zhuǎn)換,調(diào)用函數(shù)
解析返回值,結(jié)束調(diào)用
釋放
python
解釋器
C++
根據(jù)實(shí)際業(yè)務(wù)生成對(duì)應(yīng)的JSON
字符串,然后調(diào)用Python
傳遞給對(duì)應(yīng)函數(shù),在Python
函數(shù)中解析JSON
字符串然后生成Word
內(nèi)容
“整個(gè)腳本實(shí)現(xiàn)庫以及對(duì)應(yīng) Example 都已經(jīng)開源,感興趣的朋友直接訪問即可
https://github.com/kevinlq/QtPythonDocx
環(huán)境配置
下載并安裝好Python
相關(guān)庫,確保本地環(huán)境沒有問題,記得安裝好Python-docx
庫??截?code>Python相關(guān)依賴庫到你的項(xiàng)目目錄,不如下面這樣
QtPythonDocx
|??3rdparty
│??└─Python310
│??????├─include
│??????│??├─cpython
│??????│??└─internal
│??????└─libs
├─bin
│??├─Python310
│??│??├─DLLs
│??│??└─Lib
|??|─script
│??│??wordOperate.py
“關(guān)于一些版本事項(xiàng)、以及中間會(huì)遇到那些坑,文末有注意事項(xiàng)統(tǒng)一介紹
調(diào)用 Python
庫
為了做到簡(jiǎn)潔、通用,我們編寫一個(gè)腳本調(diào)用類,該類和具體的業(yè)務(wù)無關(guān),只負(fù)責(zé)傳入不同模塊、函數(shù)、參數(shù)調(diào)用對(duì)應(yīng)的Python
函數(shù)并能夠返回對(duì)應(yīng)的結(jié)果,這樣后續(xù)的調(diào)用者就使用的時(shí)候和使用普通函數(shù)沒有區(qū)別
為了實(shí)現(xiàn)這個(gè)目的,目前有幾個(gè)知識(shí)點(diǎn)需要解決:
由于
Python
數(shù)據(jù)類型和C++
不一樣,如果要通用那么就需要進(jìn)行轉(zhuǎn)換,怎么做到C++
一個(gè)參數(shù)類型匹配Python
多個(gè)類型?返回值處理,我們的業(yè)務(wù)函數(shù)返回值可能多種多樣,怎么兼容?
編碼轉(zhuǎn)換,
Python
中支持UTF-8
,我們程序處理中數(shù)據(jù)可能包含多種類型,怎么轉(zhuǎn)換
解決了上述問題,基本也就是完成了本次要寫的腳本加載類
腳本調(diào)用類實(shí)現(xiàn)
首先看下類型問題,其實(shí)我們這里需要一個(gè)萬能類型來作為函數(shù)入?yún)ⅲ敲从羞@個(gè)類型么?有,如果你的編譯器支持 C++17
,那么可以用std::variant
std::variant<int,?double,?std::string>?inputArg
由于作者本人對(duì) Qt
比較熟一點(diǎn),所以本次程序中使用了大量的Qt
內(nèi)置數(shù)據(jù)類型,原理是相通的
KPythonRunScript
類的實(shí)現(xiàn),核心函數(shù)如下所示
bool?callFun(const?char?*funcName,
?????????????????const?QVariantList?&args?=?QVariantList(),
?????????????????QVariant?&returnValue?=?QVariant(QVariant::Invalid));
funcName
: python 腳本中對(duì)應(yīng)的函數(shù)名字args
: 函數(shù)入?yún)ⅲ鶕?jù)實(shí)際腳本中函數(shù)參數(shù)個(gè)數(shù)而定returnValue
: 返回值,如果腳本函數(shù)有返回值初始化的時(shí)候賦予對(duì)應(yīng)類型
實(shí)際Python
腳本中函數(shù)的入?yún)€(gè)數(shù)是不確定的,為了兼容多個(gè)調(diào)用場(chǎng)景,所以采用了數(shù)組作為實(shí)際的入?yún)?,?shù)組每個(gè)元素采用QVariant
類型,這樣就能根據(jù)實(shí)際傳入的類型來判斷,在調(diào)用Python
的時(shí)候應(yīng)該轉(zhuǎn)換為什么類型
返回值類型也一樣,初始化調(diào)用時(shí)確定好本次調(diào)用的返回值類型,這樣在Python
腳本調(diào)用完成后才能把返回值轉(zhuǎn)為我們C++
實(shí)際的返回值
類型轉(zhuǎn)換:
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;
????}
}
這里目前適配了上述幾種類型,如果后續(xù)不滿足繼續(xù)擴(kuò)展其它類型即可
Python
腳本對(duì)應(yīng)的函數(shù)
def?generateWord(strContent):
????#...
????return?True
詳細(xì)調(diào)用
在上述實(shí)現(xiàn)的類的基礎(chǔ)上,調(diào)用其實(shí)就變的很簡(jiǎn)單了,就和我們調(diào)用本地某個(gè)函數(shù)一樣,非常輕松
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;
}
可能你注意到程序中使用了單例,為什么使用單例?這是因?yàn)閱蝹€(gè)進(jìn)程Python
解釋器相關(guān)內(nèi)容初始化一次即可,后續(xù)隨意調(diào)用不用再次初始化,實(shí)際驗(yàn)證中也證實(shí)了,多次初始化會(huì)有一些異常問題(雖然每次用完已經(jīng)釋放了,再次初始化還是會(huì)有問題)
這樣就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的調(diào)用過程,具體Python
文件中的內(nèi)容可以看我開源的工程目錄中的內(nèi)容,其實(shí)就是把各種操作Word
方法封裝成函數(shù)了,擴(kuò)展了常用的字段
QtPythonDocx/bin/script/wordOperate.py
JSON
格式說明
由于 Word
內(nèi)容較多,調(diào)用時(shí)兼容很多寫入場(chǎng)景,因此目前設(shè)計(jì)使用 JSON
格式來交互,基本覆蓋大部分使用場(chǎng)景,而且支持各種自定義,完全滿足日常使用,下面是各個(gè)字段的說明
全局配置
savePath: 定義了生成的
Word
文檔路徑,確保該路徑有寫入權(quán)限,否則可能會(huì)失敗openFile: 導(dǎo)入成功后是否打開該文檔
line_spacing: 行間距,默認(rèn)給 1.5倍
header: 頁眉文本,不需要頁眉直接給空即可
footer: 頁腳文本
content:[] 這里是
Word
內(nèi)容部分,采用數(shù)組存儲(chǔ),由于數(shù)組有有序的,因此嚴(yán)格按照你的內(nèi)容順序依次傳入即可fontSize: 全局字體大小
fontName: 全局字體名字,設(shè)置后后續(xù)每個(gè)正文、標(biāo)題、表格等可以不用設(shè)置,全局統(tǒng)一
正文
下面是正文內(nèi)容部分說明
type: 標(biāo)識(shí)是那種類型,0:標(biāo)題,1: 普通文本,2:圖片,3:表格,其它類型后續(xù)擴(kuò)展自定義
text: 如果是文本或者標(biāo)題給定內(nèi)容
level: 級(jí)別,目前只有標(biāo)題類型生效
bold: 是否加粗
italic: 是否傾斜
strike: 是否刪除線
alignment: 對(duì)齊方式,主要有這么幾種:left, right,center
color: 對(duì)應(yīng)文本的顏色
height: 行高
插入表格
如果是表格,那么有這些擴(kuò)展字段
columns: 列數(shù)
rows: 行數(shù)
height: 行高,所有行設(shè)置一樣的行高,也可以自定義每行的行高
mergeCells: 要合并的單元格數(shù)組,比如合并 (0,0)和(0,1)單元格,那么內(nèi)容如下
{"begin":?[0,0],?"end":?[0,1]}
tableCell: 單元格內(nèi)容,依次填充每個(gè)單元格內(nèi)容即可,每個(gè)單元格內(nèi)容和普通文本類似,下面是一個(gè)示例
tableCell":?[
????????????????{"text":?"我是第一個(gè)單元格,加粗,傾斜,紅色",?"style":?"",?"bold":?true,?"italic":?true,"color":?"#ff0000","alignment":?"center"},
????????????????{"text":?"00和01合并了,02會(huì)覆蓋01的值,加粗變紅,左對(duì)齊",?"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"
注意圖片路徑支持相對(duì)路徑和絕對(duì)路徑,根據(jù)自己實(shí)際需要傳遞即可
總結(jié)
本次通過Python
的方式可以很好的支持很多之前出現(xiàn)的異常問題,足以滿足我們遇到的各種業(yè)務(wù)需要導(dǎo)出生成Word
難題,而且導(dǎo)出速度非???,實(shí)際測(cè)試生成 10
頁左右文檔耗時(shí)不到 2
秒,測(cè)試了多臺(tái)電腦,實(shí)際效果都非常理想
注意事項(xiàng)
Python
版本選擇問題,確保你的程序最終要運(yùn)行的平臺(tái),如果要最低要求是Windows7
,那么建議選擇Python3.8
版本即可,如果無所謂那么選擇最新穩(wěn)定版本即可;Python
注意選擇和你程序使用同一個(gè)位數(shù),程序編譯器使用的是64
位,那就下載64
位,32
位同理 ;
推薦閱讀