閱讀本文大概需要 6 分鍾
日常開發軟件可能會遇到這類小眾需求,導出數據到 Word
、Excel
以及 PDF
文件,如果你使用 C++
編程語言,那么可以選擇的方案不是很多,恰好最近剛好有這部分需求,整理下這段時間踩過的坑,方便后人
讀寫 Word
日常開發的軟件使用最多的應該是導出數據到 Word
文檔中,目前可以用的方案有這幾種
沒有十全十美的方案,任何方案都存在優點和缺點,下面來詳細看下這幾種方案的優缺點以及適用場景
XML 模板替換
原理:事先編輯好一份
Word
模板,需要替換內容的
地方預留好位置,然后使用特殊字段進行標記,后面使用代碼進行全量替換即可完成
優點
- 代碼量相對較少、導出速度快
- 跨平台,支持多個系統,系統不安裝 office 也能導出;
- 支持圖片以及固定格式導出;
缺點
- 導出格式固定,可擴展性不強,如果需求變化導出格式變了,那么模板也要跟着改變;
- 一種格式對應一份模板,如果導出格式較多,需要准備的模板文件較多,這樣比較繁瑣;
- 需要
Word
2003 以上版本;
舉個栗子
我們先編輯一份 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
對應的倉庫,方便后人使用
取之互聯網、回報互聯網
原創不易,如果覺得對你有幫助,歡迎點贊、在看、轉發
推薦閱讀