原來,這才是 HTML+CSS 匯出 Word 最佳方式!

jeremy1127發表於2019-11-14

對於任何一個線上富文字寫作產品,匯出功能是必不可少的,其中匯出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-jshtml2docx

問題也很明顯,

  • 不支援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 協議》,轉載必須註明作者和本文連結

相關文章