Excel檔轉PDF產生的問題與解決方法
開發情境與問題描述
最近在開發的時候遇到一個匯出Excel的問題,原本使用的前端套件是將Html Table的內容直接轉成Html的Excel檔案,並沒有經過後端(backend)處理,但是這個套件在IE(又是)卻發生了嚴重的編譯問題,導致整個網站都沒辦法開啟,公司內部決議的解決方法是將匯出Excel的方法改為由後端產出Excel檔案。
後端產生Excel套件:EPPlus
如何產生Excel檔案的相關程式碼網路上都有,這邊就不贅述了,產生Excel表單還算簡單,至少有很多免費的套件可以使用,至於Excel轉PDF的話就遇到一堆問題,以下正片開始。
解決方案一:FreeSpire.XLS
首先我Google了一下,關鍵字epplus excel to pdf
,找到了以下文章: 使用 epplus 產生檔案後轉成 PDF 文章內容中提到,可以用FreeSpire.XLS這個套件來將Excel轉為PDF檔,因為看到Free
這個字眼,就直覺覺得是免費的套件,所以直接到Nuget搜尋並安裝,照著文章的寫法完成之後,很順利的產生了PDF檔,檢查了一下看似沒甚麼問題,這個功能就簽入到版控,打完收工準備驗收。 FreeSpire.XLS
結果隔天同事到客戶端驗收的時候,赫然發現最下面會出現需要Buy it now!!
的文字(WTF)。 (開發端不會產生,所以開發時沒有察覺)
緊急求救Google大神後發現,許多Excel轉PDF的套件均需要付費才能使用,免費的都會有一些限制,不是只給你一頁,就是多了一堆要你付費的文字,果然天下沒有白吃的午餐,只好找其他的方案了。
解決方案二:Microsoft.Office.Interop.Excel
因為是Excel檔,自然就找上了微軟的Excel轉PDF解法,也就是這個套件,只要Server端也安裝Office,並且專案加入這個dll的參考,就能夠使用它內建的匯出PDF的功能,相當的簡單好用。
// Excel 檔案位置
string sourcexlsx = @"D:\Downloads\003-圖表-ok.xlsx";
// PDF 儲存位置
string targetpdf = @"D:\Downloads\003-圖表-ok.pdf";
//建立 Excel application instance
Microsoft.Office.Interop.Excel.Application appExcel = new Microsoft.Office.Interop.Excel.Application();
//開啟 Excel 檔案
var xlsxDocument = appExcel.Workbooks.Open(sourcexlsx);
//匯出為 pdf
xlsxDocument.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, targetpdf);
//關閉 Excel 檔
xlsxDocument.Close();
//結束 Excel
appExcel.Quit();
但是,等到完成,而且都要發布到Server的時候,才被告知:因為Microsoft Office要買授權才可以安裝,客戶不確定有沒有買,而且客戶是公家機關,之後也不會用MS Office而是會推OpenOffice,所以這個套件基本上又不能用了。
Microsoft.Office.Interop.Excel 使用 C# 將 Excel 檔(.xlsx .xls) 轉換為 PDF
解決方案三:OpenOffice的LibreOffice(.exe)
就在無計可施的時候,突然想到,xlsx檔用OpenOffice也可以編輯,於是又一次請教了Google大神,關鍵字openoffice excel to pdf c#
,找到黑暗大的文章: LibreOffice docx 轉 pdf 評估筆記
文章內容是分享用LibreOffice將docx轉PDF檔的成果分享,讓我發現LibreOffice有跟MS Office一樣匯出PDF的功能(之後發現OpenOffice本身也有一樣轉PDF的功能),一樣只要在Server機器安裝就可以匯出,而且重點是免費!!!
(歡呼)
再搭配LibreOfficeLibrary這個套件,把參數設定一下,輕輕鬆鬆就可以透過LibreOffice將Excel轉成PDF,在開發端
測試完全沒問題,真是可喜可賀,問題就此解決,打完收工(?)
var documentConverter = new DocumentConverter();
documentConverter.ConvertToPdf(excelFilePath, pdfFilePath);
但就在開開心心的把LibreOffice安裝到客戶的Server上,並且把程式發布好,開啟站台測試時,一切瞬間風雲變色,原本0.1秒就可以順利看到PDF下載完成的訊息,放到正式機後跑了5分鐘還在轉圈圈,打開工作管理員查看執行中的程序,LibreOffice.exe這支執行檔一直在執行,我點幾次匯出就執行幾支程序,完全沒有結束的時候。
先就幾個方面進行問題排除:
- LibreOfficeLibrary套件問題 A. 也許是套件本身的問題,所以將套件匯出PDF的原始碼直接複製到專案。結果一樣,在開發端可以,發布上去就
失敗
LibreOfficeLibrary匯出Pdf的部分 B. LibreOffice匯出PDF是下Command Line來執行,所以參考網路上的Command語法來執行。結果同上也是失敗
//執行LibreOffice匯出PDF的Command Line語法
public static void ConvertToPdfProcess(string filePath)
{
//指定應用程式路徑
string target = @"C:\Program Files\LibreOffice\program\soffice.exe";
var pInfo = new ProcessStartInfo(target);
pInfo.Arguments = $"-headless -convert-to pdf \"{filePath}\" -outdir \"{Path.GetDirectoryName(filePath)}\" ";
using (var p = new Process())
{
p.StartInfo = pInfo;
p.Start();
}
}
- 資料夾或檔案權限問題 所以先把匯出檔案目錄以及LibreOffice.exe的權限先開放為Everyone。結果還是
失敗
解決方案四:LibreOffice SDK
就這樣又卡關了,接著就像遊魂一樣,漫無目的地在Google亂逛亂搜尋LibreOffice Export PDF Fail、LibreOffice Stuck(?)、LibreOffice 執行權限...,完全沒有找到解法,發呆了好一陣子,回去看LibreOffice的官方資源,想起了LibreOffice有提供SDK,也許SDK可以突破盲點(雖然不知道盲點在哪),順利匯出PDF。
首先下載SDK並安裝,將sdk安裝路徑(C:\Program Files\LibreOffice\sdk\cli)下的五個dll加入到專案參考。
並在專案中加入以下程式碼:
/// <summary>
/// 匯出PDF
/// </summary>
/// <param name="inputFile">來源檔案路徑</param>
/// <param name="outputFile">匯出檔案路徑</param>
public static void ConvertToPdfSdk(string inputFile, string outputFile)
{
if (ConvertExtensionToFilterType(Path.GetExtension(inputFile)) == null)
throw new InvalidProgramException("Unknown file type for OpenOffice. File = " + inputFile);
//Get a ComponentContext
var xLocalContext =
Bootstrap.bootstrap();
//Get MultiServiceFactory
var xRemoteFactory =
(XMultiServiceFactory)
xLocalContext.getServiceManager();
//Get a CompontLoader
var aLoader =
(XComponentLoader)xRemoteFactory.createInstance("com.sun.star.frame.Desktop");
//Load the sourcefile
XComponent xComponent = null;
try
{
xComponent = InitDocument(aLoader,
PathConverter(inputFile), "_blank");
//Wait for loading
while (xComponent == null)
{
Thread.Sleep(1000);
}
// save/export the document
SaveDocument(xComponent, inputFile, PathConverter(outputFile));
}
finally
{
if (xComponent != null) xComponent.dispose();
}
}
/// <summary>
/// 文件初始化
/// </summary>
/// <param name="aLoader"></param>
/// <param name="file">來源檔案路徑</param>
/// <param name="target">目標檔案路徑</param>
/// <returns></returns>
private static XComponent InitDocument(XComponentLoader aLoader, string file, string target)
{
var openProps = new PropertyValue[1];
openProps[0] = new PropertyValue { Name = "Hidden", Value = new Any(true) };
var xComponent = aLoader.loadComponentFromURL(
file, target, 0,
openProps);
return xComponent;
}
/// <summary>
/// 儲存檔案
/// </summary>
/// <param name="xComponent">套件</param>
/// <param name="sourceFile">來源檔案路徑</param>
/// <param name="destinationFile">目標檔案路徑</param>
private static void SaveDocument(XComponent xComponent, string sourceFile, string destinationFile)
{
var propertyValues = new PropertyValue[2];
// Setting the flag for overwriting
propertyValues[1] = new PropertyValue { Name = "Overwrite", Value = new Any(true) };
//// Setting the filter name
propertyValues[0] = new PropertyValue
{
Name = "FilterName",
Value = new Any(ConvertExtensionToFilterType(Path.GetExtension(sourceFile)))
};
((XStorable)xComponent).storeToURL(destinationFile, propertyValues);
}
/// <summary>
/// 檔案路徑字串格式
/// </summary>
/// <param name="file">檔案路徑</param>
/// <returns></returns>
private static string PathConverter(string file)
{
if (string.IsNullOrEmpty(file))
throw new NullReferenceException("Null or empty path passed to OpenOffice");
return String.Format("file:///{0}", file.Replace(@"\", "/"));
}
/// <summary>
/// 對應檔案類型
/// </summary>
/// <param name="extension">副檔名</param>
/// <returns></returns>
public static string ConvertExtensionToFilterType(string extension)
{
switch (extension)
{
case ".doc":
case ".docx":
case ".txt":
case ".rtf":
case ".html":
case ".htm":
case ".xml":
case ".odt":
case ".wps":
case ".wpd":
return "writer_pdf_Export";
case ".xls":
case ".xlsb":
case ".xlsx":
case ".ods":
return "calc_pdf_Export";
case ".ppt":
case ".pptx":
case ".odp":
return "impress_pdf_Export";
default:
return null;
}
}
來源參考:HOW TO: Convert office documents to PDF using Open Office/LibreOffice in C#
設定完畢之後,專案發行時,會發生無法載入檔案或組件 'cli_cppuhelper' 或其相依性的其中之一。 試圖載入格式錯誤的程式。
原因是因為sdk是x64,所以需要將專案的目標平台改為x64
設定後還會有一樣的錯誤,原因是專案的IIS Express也一樣要改為x64平台
發布到IIS的時候,該站台的應用程式集區→進階設定→載入使用者設定檔,這邊一定要設定為True
。
到此,設定告一個段落,經過測試,開發端可以順利轉換,安裝在客戶的站台一樣可以順利執行!!!(歡呼~~~!!!),這回真的是可喜可賀可口可樂,以上就是達成Excel轉PDF功能的經過,過程雖然不算艱辛但也一波三折。
總結
目前還不知道LibreOffice.exe究竟出了甚麼問題導致無法順利執行,但是基本上這幾個作法在開發端都可以順利執行,如果各位看官有機會需要做到Excel匯出PDF的功能的話,不妨也從比較簡單的方式試試看,如果有MS Office的授權的話可以直接用MS Excel套件轉PDF,其次就是嘗試解決方案三OpenOffice的LibreOffice(.exe)
,說不定簡單的方式就可行了,而若是有遇到跟我一樣的情況的話,歡迎各位參考我的作法,也希望能夠幫到大家,最後感謝各位的收看。
參考來源整理
EPPlus套件
使用 epplus 產生檔案後轉成 PDF
Microsoft.Office.Interop.Excel
使用 C# 將 Excel 檔(.xlsx .xls) 轉換為 PDF
LibreOffice docx 轉 pdf 評估筆記
LibreOfficeLibrary
HOW TO: Convert office documents to PDF using Open Office/LibreOffice in C#