對於任何一個線上富文字寫作產品,匯出功能是必不可少的,其中匯出word是其中最常見的選項,但是很多產品匯出的word,比如說Jira,格式就有不同程度的失真.
仔細分析下來,解決這個思路的主要有兩種:
- 硬轉:使用OpenXml的SDK,逐一匹配html標籤和樣式css,相當於用OpenXml實現了一個html+css的渲染層。
- 偷懶:想辦法把html+css的內容當成word的一部分,相當於用web檢視開啟。
當時,我花了一定的時間,在兩個方向上分別做了調研。
硬轉的思路,直覺上工作量就極大,而且很容易出Bug,畢竟那麼多組合情況需要考慮。值得參考的專案是html2openxml,1W多行只能實現html的轉換,還不包括css。
不是沒想過,限制輸出html的排版,比如用固定的id代表固定的部分,css樣式也只支援特定的幾種,但後來討論下來,覺得這種方法,需要前後端確定好規則,而在當時快速迭代的時期,這無疑會增加開發成本。
而偷懶這條路,能夠搜尋到專案,都是借用了word中altchunks的特性,比如:html-docx-js、html2docx。
問題也很明顯,
- 不支援float之類的樣式,對圖片的支援也需要自己寫程式碼轉base64,然後嵌進標籤裡。這些雖然麻煩些,但是還是可以tweak。
- 最麻煩的是,這種格式的word,相容性很差,在微信中、mac上打不開。
於是,我一度卡在這裡很久,直到我發現……
轉機來的也很突然,就是我發現用word開啟一個帶html+css的檔案後,再另存成word,不僅格式得到了保留,圖片也在,而且相容性也還不錯!真是
踏破鐵鞋無覓處,得來全不費工夫。
問題來了,怎麼把這一步程式化呢?
這下終於可以用上過去.net的開發經驗了,C#是可以通過com元件的方式操作word,但前提是執行程式的機器上必須裝word才行。
核心程式碼,也是非常的簡單:
首先引用Microsoft.Office.Interop.Word,呼叫的程式碼如下:
var word = new Application {Visible = false};
var doc = word.Documents.Open(htmlFilePath, Format: WdOpenFormat.wdOpenFormatWebPages,
ReadOnly: false);
doc.SaveAs2000(wordFilePath, WdSaveFormat.wdFormatDocumentDefault,
ReadOnlyRecommended: false);
doc.Close();
word.quit()
是不是很簡單呢?
而這裡唯一還需要處理的就是圖片,如果不做處理,圖片還是以連結的方式儲存的。
一方面,每次開啟的時候需要重複下載,好處是word檔案比較小,但缺點是受限於網速,可能會出現圖片載入不出來的情況;
另一方面時,如果圖片沒有指定寬度和高度,當圖片的尺寸大於文件的尺寸時,顯示的效果就很差。
估化的思路很簡單,即強行把圖片的尺寸拉伸到一頁可以顯示下,同時將圖片連結轉成內嵌的圖片。
foreach (InlineShape s in doc.InlineShapes)
{
var inlineShape = s;
if (inlineShape.Type != WdInlineShapeType.wdInlineShapePicture &&
inlineShape.Type != WdInlineShapeType.wdInlineShapeLinkedPicture)
{
continue;
}
if (inlineShape.Type == WdInlineShapeType.wdInlineShapeLinkedPicture)
{
inlineShape.LinkFormat.SavePictureWithDocument = true;
inlineShape.LinkFormat.BreakLink();
}
if (inlineShape.Width > this._documentWidth)
{
inlineShape.LockAspectRatio = MsoTriState.msoTrue;
inlineShape.Width = this._documentWidth;
}
if (inlineShape.Height > this._documentHeight)
{
inlineShape.LockAspectRatio = MsoTriState.msoTrue;
inlineShape.Height = this._documentHeight;
}
}
到此,核心的轉換程式碼就已經完成。(需要提醒各位看官的是,這種方法是不支援float和flex的css樣式的。)
請注意:上述程式碼是基於.net framework寫的,執行的機器上還必須有word。
如果要對外提供服務的話,考慮到跨語言跨平臺,最好的方式即提供http介面。
而這對一個不熟悉.net的人,那要學習的東西還是不少的。
樓主我就好人做到底,在上述的核心程式碼上用WCF的webHttpBinding實現了一個的http服務,同時使用log4net實現了日誌功能。
最終的程式的workflow是
- 使用者傳送html+css內容到http服務
- 先把html+css存成檔案
- 讀取html檔案,然後另存為word檔案
- 返回下載連結
- 使用nginx對映目錄中的檔案,實現下載功能
所有程式碼開源在HtmlToWord,如果它對你有用,歡迎點個star.
本作品採用《CC 協議》,轉載必須註明作者和本文連結