閱讀本文大概需要 6 分鍾
日常開發軟件可能會遇到這類小眾需求,導出數據到 Word、Excel 以及 PDF文件,如果你使用 C++ 編程語言,那么可以選擇的方案不是很多,恰好最近剛好有這部分需求,整理下這段時間踩過的坑,方便后人
讀寫 Word
日常開發的軟件使用最多的應該是導出數據到 Word 文檔中,目前可以用的方案有這幾種

沒有十全十美的方案,任何方案都存在優點和缺點,下面來詳細看下這幾種方案的優缺點以及適用場景
XML 模板替換
原理:事先編輯好一份
Word模板,需要替換內容的
地方預留好位置,然后使用特殊字段進行標記,后面使用代碼進行全量替換即可完成
優點
- 代碼量相對較少、導出速度快
- 跨平台,支持多個系統,系統不安裝 office 也能導出;
- 支持圖片以及固定格式導出;
缺點
- 導出格式固定,可擴展性不強,如果需求變化導出格式變了,那么模板也要跟着改變;
- 一種格式對應一份模板,如果導出格式較多,需要准備的模板文件較多,這樣比較繁瑣;
- 需要
Word2003 以上版本;
舉個栗子
我們先編輯一份 Word 模板文檔,內容大概如下所示:

- 將該文檔另存為
Word XML文檔XML-Template.xml - 讀取文檔內容進行變量替換
QFile file("XML-Template.xml");
if (!file.open(QIODevice::ReadOnly))
{
qDebug() << "open xxml file fail. " << file.errorString();
return 0;
}
QByteArray baContent = file.readAll();
file.close();
QString strAllContent = QString::fromLocal8Bit(baContent);
strAllContent.replace("$VALUE0", "1");
strAllContent.replace("$VALUE1", QString::fromLocal8Bit("法外狂徒張三"));
strAllContent.replace("$VALUE2", QString::fromLocal8Bit("考試不合格"));
strAllContent.replace("$VALUE3", "2");
strAllContent.replace("$VALUE4", QString::fromLocal8Bit("李四"));
strAllContent.replace("$VALUE5", QString::fromLocal8Bit("合格"));
QFile newFile("export.doc");
if (!newFile.open(QIODevice::WriteOnly))
{
qDebug() << "file open fail." << newFile.errorString();;
return 0;
}
newFile.write(strAllContent.toLocal8Bit());
newFile.close();
- 保存替換后的內容,寫入文件

可以看出來這種方式比較繁瑣,重點是編輯固定的模板格式,而且編輯好后保存成XML格式后還需要繼續調整,這種 XML 格式標簽很多,不小心就修改錯了,導致導出的文檔打不開
這種方式適合模板內容不太復雜,內容較少的情況下使用
COM 組件方式
原理:采用
Micro Soft公開的接口進行通訊,進行讀寫時會打開一個 `Word進程來交互
Qt 為我們提供了專門進行交互的類和接口,使用 Qt ActiveX框架就可以很好的完成交互工作
優點
- 實現簡單,快速上手;
缺點
- 導出寫入速度慢,因為相當於打開 word 文檔操作;
- 僅
Windows平台可用,其它平台失效; - 需要程序運行的電腦安裝 office word,否則調用失敗
舉個栗子
使用時需要引入對應的模塊,在 pro 文件引入模塊
QT *= axcontainer
打開文檔寫入內容
QAxObject *pWordWidget = new(std::nothrow) QAxObject;
bool bResult = pWordWidget->setControl("word.Application");
if (!bResult)
{
return false;
}
// 設置是否顯示
pWordWidget->setProperty("Visible", false);
QAxObject *pAllDocuments = pWordWidget->querySubObject("Documents");
if(nullptr == pAllDocuments)
{
return false;
}
// 新建一個空白文檔
pAllDocuments->dynamicCall("Add (void)");
// 獲取激活的文檔並使用
QAxObject *pActiveDocument = pAllDocuments->querySubObject("ActiveDocument");
if(nullptr == pActiveDocument)
{
return false;
}
// 插入字符串
QAxObject *pSelectObj = pWordWidget->querySubObject("Selection");
if (nullptr != pSelectObj)
{
pSelectObj->dynamicCall("TypeText(const QString&)", "公眾號:devstone");
}
……
可以看出來使用起來不難,對於新手友好一點,很多寫入操作方法比較繁瑣,需要自己重新封裝一套接口
-
這種方案比較適合那些排版比較復雜,圖片、文字、表格混排的場景下,而且內容都是動態變化的,可以很好的實現定制化
-
當然了它的缺點也不少,也有一些坑,有時候莫名其妙會失敗,還有就是比如你電腦安裝的
Word沒有激活,那么每次啟動會彈激活窗口 -
還有就是這種方式要求所有的路徑必須是本地化的,比如
D:\\Soft\test.png -
使用前最好讀取注冊表判斷當前電腦是否安裝了
Office Word,如果沒有安裝,直接讀取操作肯定會崩潰
這種方式同樣適用於寫入 Excel 文件,后面再說
HTML 方式
原理:這種方式得益於
Word支持 HTML格式導出渲染顯示,那么反向也可以支持,需要我們拼接HTML格式內容,然后寫入文件保存成.doc格式
優點
- 跨平台,不僅限於
Windows平台,代碼可擴展性比較好 - 導出速度快、代碼可擴展;
缺點
- 字符串拼接
HTML容易出錯,缺失標簽導出后無法顯示; - 插入的圖片是本地圖片文件的鏈接,導出的 word文檔拷貝到其它電腦圖片無法顯示
舉個栗子
QString HTML2Word::getHtmlContent()
{
QString strHtml = "";
strHtml += "<html>";
strHtml += "<head>";
strHtml += "<title>測試生成word文檔</title>";
strHtml += "<head>";
strHtml += "<body style=\"bgcolor:yellow\">";
strHtml += "<h1 style=\"background-color:red\">測試qt實現生成word文檔</h1>";
strHtml += "<hr>";
strHtml += "<p>這里是插入圖片<img src=\"D:\\title.jpg" alt=\"picture\" width=\"100\" height=\"100\"></p>";
strHtml += "</hr>";
strHtml += "</body>";
strHtml += "</html>";
return strHtml;
}
// 保存寫入文件
QFile file("D:/htmp2Word.doc");
if (!file.open(QIODevice::WriteOnly))
{
return false;
}
QTextStream out(&file);
out << getHtmlContent();
file.close();

這種方式難點在於 HTML格式拼接,任何缺失字段都會導致導出失敗,適合小眾需求下導出
圖片問題其實可以手動進行轉化,文檔導出成功后手動拷貝內容到新的文檔,這樣圖片就真正插入到文檔中,文檔發送給別人也不會丟失圖片了
還有一個坑就是:如果你使用 WPS 打開導出的文檔,默認顯示的是 web視圖,需要手動進行調整
某些電腦分辨率變化也會導致生成的文檔中字體等產生變化
第三方開源庫
可以使用的第三方庫幾乎沒有,網絡上找到的有這么幾個
- OpenOffice: 兼容性差,集成調用難度大
- LibOffice: 太龐大,不容易集成
- DuckX: 太小眾,只能簡單的使用
- docx:小眾庫
在讀寫 Word這部分,C++ 基本沒有可以使用的第三方庫,不像其他語言Java、C#、Python有很多可以選擇,這個痛苦也只有 C++ 程序員能夠理解了吧
所以怎么選擇還是看自己項目需求吧,沒有十全十美的方案
上面說了這么多,都是導出生成 Wrod,那么下面來看看有那些方式可以讀取顯示 Word內容
這種需求應該不會很多,而且顯示難度更大一些
使用 COM組件方式,即采用 QAxWidget框架顯示 office 文檔內容,本質上就是在我們編寫的 Qt 界面上嵌入 office 的軟件,這種方式其實和直接打開 Word查看沒有啥區別,效果、性能上不如直接打開更好一些
目前一般都會采用折中方案,把 Word 轉為 PDF 進行預覽加載顯示,我們知道 PDF 渲染庫比較多,生態相對來說要好一些,在選擇上就更廣泛些,如何使用后面部分有專門介紹 PDF章節
讀寫 Excel
目前有一個支持比較好的第三方庫可以使用,整體使用基本可以滿足日常使用

這款開源庫支持跨平台,Linux、Windows、Mac、IOS、Android,使用方式支持動態庫調用和源碼直接集成,非常方便
編譯支持 qmake和cmake,可以根據你自己的項目直接集成編譯,讀寫速度非常快
QXlsx::Document xlsx;
// 設置一些樣式
QXlsx::Format titleFormat;
titleFormat.setBorderStyle(QXlsx::Format::BorderThin); // 邊框樣式
titleFormat.setRowHeight(1,1,30); // 設置行高
titleFormat.setHorizontalAlignment(QXlsx::Format::AlignHCenter); // 設置對齊方式
// 插入文本
xlsx.write(1,1, "微信公眾號:devstone", titleFormat);
// 合並單元格
xlsx.mergeCells(QXlsx::CellRange(2,1,4,4), titleFormat);
// 導出保存
xlsx.saveAs("D:/xlsx_export.xlsx");
// 添加工作表
xlsx.addSheet("devstone");
可以看到上手非常容易、各個函數命名也貼近 Qt Api,是一款非常良心的開源軟件
PS:注意該軟件使用
MIT許可協議,這樣對於很多個人或者公司來說非常良心,意味着你可以無償使用、修改該項目,但是必須在你項目中也添加同樣的MIP許可
上面也提到了,還可以使用 COM 組件的方式讀寫 Excel,不過有了這款開源庫基本就可以告別 COM組件方式了
讀寫 PDF
PDF相關開源庫挺多的,給了 C++ 程序員莫大的幫助,目前可用的主要有這些

其中 mupdf和 poppler 屬於功能強大但是很難編譯的那種,需要有扎實的三方庫編譯能力,否則面對 n 個依賴庫會無從下手
不過可喜的是 Github 上有兩個開源庫可以供選擇
qpdf 庫
這個庫其實封裝了 pdf.js庫,使用 WebEngine來執行 JavaScript進而加載文件

- 直接從本地文件加載;
- 支持從內存數據直接加載渲染 PDF 內容;
這種方式對環境有特殊要求了,如果你的項目使用的 Qt 版本不支持 WebEngine,那么就無法使用
qtpdf 庫
這個庫是 Qt 官方親自操刀對第三方庫進行了封裝,暴露的 API 和 Qt 類似,使用起來非常舒服

代碼結構以及使用 Demo

小試牛刀
關於如何使用,官方已經給了我們非常詳細的步驟了,直接跟着下面幾步就 OK 了
git clone git://code.qt.io/qt-labs/qtpdf
cd qtpdf
git submodule update --init --recursive
qmake
make
cd examples/pdf/pdfviewer
qmake
make
./pdfviewer /path/to/my/file.pdf
可以看到使用了谷歌開源的 pdfium 三方庫,編譯時需要單獨更新下載這個庫,因為某些原因可能你無法下載,不過好在有人在 GitHub上同步了這個倉庫的鏡像,有條件還是建議直接下載最新穩定版的

可正常訪問的倉庫地址:https://github.com/PDFium/PDFium
相關類可以看這個文檔:https://developers.foxit.com/resources/pdf-sdk/c_api_reference_pdfium/modules.html
最后還要注意項目開源協議:pdfium引擎開始來自於福昕,一個中國本土的軟件公司,Google與其合作最終進行了開源,目前采用的是
BSD 3-Clause協議,這種協議允許開發者自由使用、修改源代碼,也可以修改后重新發布,允許閉源進行商業行為,不過需要你在發布的產品中包含原作者代碼中的BSD協議
總結
以上就是項目中常用的文檔處理方法總結,當然了肯定也還有其它方案可以實現,畢竟條條大路通羅馬,如果你還要不錯的方案和建議歡迎留言
PS: 以上方案和對應的源碼編譯、使用例子會統一上傳到 GitHub對應的倉庫,方便后人使用
取之互聯網、回報互聯網
原創不易,如果覺得對你有幫助,歡迎點贊、在看、轉發
推薦閱讀
