Java實(shí)現(xiàn)markdown格式內(nèi)容轉(zhuǎn)換為word
前言
最近有個需求,就是需要在項(xiàng)目中,初始添加的信息后,將詳情頁面導(dǎo)出為word文檔,下載下來可以繼續(xù)編輯。一開始考慮word的樣式,字體等問題,使用開源的Apache POI,在保存為word內(nèi)容時候,樣式基本上會丟失,導(dǎo)致導(dǎo)出后的文檔樣式和在頁面上看的不太一樣,會出現(xiàn)錯位等情況,希望使用商業(yè)版的word處理工具,比如Aspose Words或者Spire.Doc for Java(Spire.Doc for Java 中文教程)非常專業(yè),功能非常強(qiáng)大。但是付費(fèi)商業(yè)方案被否了,后來推薦直接導(dǎo)出為PDF格式,這樣后端基本上也不用處理,導(dǎo)出PDF后,使用Adobe Acrobat在轉(zhuǎn)換成Word即可,最后給了一個方案就是頁面保存為markdown格式內(nèi)容到數(shù)據(jù)庫,然后下載時候,后端將markdown轉(zhuǎn)換為word即可。
實(shí)現(xiàn)步驟
首先添加java處理的相關(guān)依賴:
<!-- excel工具 練習(xí)的項(xiàng)目自身的依賴--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.2</version> </dependency> <!-- 新添加的依賴--> <!-- markdown格式轉(zhuǎn)換為html --> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> <version>0.21.0</version> </dependency> <!-- poi-tl和poi-tl-plugin-markdown是處理markdown格式轉(zhuǎn)換為word格式,處理只處理markdown轉(zhuǎn)換為html,只需要commonnark依賴即可--> <dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl</artifactId> <version>1.10.1</version> </dependency> <dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl-plugin-markdown</artifactId> <version>1.0.3</version> </dependency>
編寫工具類
package com.xiaomifeng1010.common.utils; import com.deepoove.poi.XWPFTemplate; import com.deepoove.poi.config.Configure; import com.deepoove.poi.data.style.*; import com.deepoove.poi.plugin.markdown.MarkdownRenderData; import com.deepoove.poi.plugin.markdown.MarkdownRenderPolicy; import com.deepoove.poi.plugin.markdown.MarkdownStyle; import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; import org.apache.poi.xwpf.usermodel.XWPFTable; import org.apache.poi.xwpf.usermodel.XWPFTableCell; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.springframework.core.io.ClassPathResource; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; /** * @author xiaomifeng1010 * @version 1.0 * @date: 2024-08-24 17:23 * @Description */ @UtilityClass @Slf4j public class MarkdownUtil { /** * markdown轉(zhuǎn)html * * @param markdownContent * @return */ public String markdownToHtml(String markdownContent) { Parser parser = Parser.builder().build(); Node document = parser.parse(markdownContent); HtmlRenderer renderer = HtmlRenderer.builder().build(); String htmlContent = renderer.render(document); log.info(htmlContent); return htmlContent; } /** * 將markdown格式內(nèi)容轉(zhuǎn)換為word并保存在本地 * * @param markdownContent * @param outputFileName */ public void toDoc(String markdownContent, String outputFileName) { log.info("markdownContent:{}", markdownContent); MarkdownRenderData code = new MarkdownRenderData(); code.setMarkdown(markdownContent); MarkdownStyle style = MarkdownStyle.newStyle(); style = setMarkdownStyle(style); code.setStyle(style); // markdown樣式處理與word模板中的標(biāo)簽{{md}}綁定 Map<String, Object> data = new HashMap<>(); data.put("md", code); Configure config = Configure.builder().bind("md", new MarkdownRenderPolicy()).build(); try { // 獲取classpath String path = MarkdownUtil.class.getClassLoader().getResource("").getPath(); log.info("classpath:{}", path); //由于部署到linux上后,程序是從jar包中去讀取resources下的文件的,所以需要使用流的方式讀取,所以獲取流,而不是直接使用文件路徑 // 所以可以這樣獲取 InputStream resourceAsStream = MarkdownUtil.class.getClassLoader().getResourceAsStream(""); // 建議使用spring的工具類來獲取,如下 ClassPathResource resource = new ClassPathResource("markdown" + File.separator + "markdown_template.docx"); InputStream resourceAsStream = resource.getInputStream(); XWPFTemplate.compile(resourceAsStream, config) .render(data) .writeToFile(path + "out_markdown_" + outputFileName + ".docx"); } catch (IOException e) { log.error("保存為word出錯"); } } /** * 將markdown轉(zhuǎn)換為word文檔并下載 * * @param markdownContent * @param response * @param fileName */ public void convertAndDownloadWordDocument(String markdownContent, HttpServletResponse response, String fileName) { log.info("markdownContent:{}", markdownContent); MarkdownRenderData code = new MarkdownRenderData(); code.setMarkdown(markdownContent); MarkdownStyle style = MarkdownStyle.newStyle(); style = setMarkdownStyle(style); code.setStyle(style); // markdown樣式處理與word模板中的標(biāo)簽{{md}}綁定 Map<String, Object> data = new HashMap<>(); data.put("md", code); Configure configure = Configure.builder().bind("md", new MarkdownRenderPolicy()).build(); try { fileName=URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()); //由于部署到linux上后,程序是從jar包中去讀取resources下的文件的,所以需要使用流的方式讀取,所以獲取流,而不是直接使用文件路徑 // 所以可以這樣獲取 InputStream resourceAsStream = MarkdownUtil.class.getClassLoader().getResourceAsStream(""); // 建議使用spring的工具類來獲取,如下 ClassPathResource resource = new ClassPathResource("markdown" + File.separator + "markdown_template.docx"); InputStream resourceAsStream = resource.getInputStream(); response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8") + ".docx"); // contentType不設(shè)置也是也可以的,可以正常解析到 response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=utf-8"); XWPFTemplate template = XWPFTemplate.compile(resourceAsStream, configure) .render(data); template.writeAndClose(response.getOutputStream()); } catch (IOException e) { log.error("下載word文檔失敗:{}", e.getMessage()); } } /** * 設(shè)置轉(zhuǎn)換為word文檔時的基本樣式 * @param style * @return */ public MarkdownStyle setMarkdownStyle(MarkdownStyle style) { // 一定設(shè)置為false,不然生成的word文檔中各元素前邊都會加上有層級效果的一串?dāng)?shù)字, // 比如一級標(biāo)題 前邊出現(xiàn)1 二級標(biāo)題出現(xiàn)1.1 三級標(biāo)題出現(xiàn)1.1.1這樣的數(shù)字 style.setShowHeaderNumber(false); // 修改默認(rèn)的表格樣式 // table header style(表格頭部,通常為表格頂部第一行,用于設(shè)置列標(biāo)題) RowStyle headerStyle = new RowStyle(); CellStyle cellStyle = new CellStyle(); // 設(shè)置表格頭部的背景色為灰色 cellStyle.setBackgroundColor("cccccc"); Style textStyle = new Style(); // 設(shè)置表格頭部的文字顏色為黑色 textStyle.setColor("000000"); // 頭部文字加粗 textStyle.setBold(true); // 設(shè)置表格頭部文字大小為12 textStyle.setFontSize(12); // 設(shè)置表格頭部文字垂直居中 cellStyle.setVertAlign(XWPFTableCell.XWPFVertAlign.CENTER); cellStyle.setDefaultParagraphStyle(ParagraphStyle.builder().withDefaultTextStyle(textStyle).build()); headerStyle.setDefaultCellStyle(cellStyle); style.setTableHeaderStyle(headerStyle); // table border style(表格邊框樣式) BorderStyle borderStyle = new BorderStyle(); // 設(shè)置表格邊框顏色為黑色 borderStyle.setColor("000000"); // 設(shè)置表格邊框?qū)挾葹?px borderStyle.setSize(3); // 設(shè)置表格邊框樣式為實(shí)線 borderStyle.setType(XWPFTable.XWPFBorderType.SINGLE); style.setTableBorderStyle(borderStyle); // 設(shè)置普通的引用文本樣式 ParagraphStyle quoteStyle = new ParagraphStyle(); // 設(shè)置段落樣式 quoteStyle.setSpacingBeforeLines(0.5d); quoteStyle.setSpacingAfterLines(0.5d); // 設(shè)置段落的文本樣式 Style quoteTextStyle = new Style(); quoteTextStyle.setColor("000000"); quoteTextStyle.setFontSize(8); quoteTextStyle.setItalic(true); quoteStyle.setDefaultTextStyle(quoteTextStyle); style.setQuoteStyle(quoteStyle); return style; } public static void main(String[] args) { String markdownContent = "# 一級標(biāo)題\n" + "## 二級標(biāo)題\n" + "### 三級標(biāo)題\n" + "#### 四級標(biāo)題\n" + "##### 五級標(biāo)題\n" + "###### 六級標(biāo)題\n" + "## 段落\n" + "這是一段普通的段落。\n" + "## 列表\n" + "### 無序列表\n" + "- 項(xiàng)目1\n" + "- 項(xiàng)目2\n" + "- 項(xiàng)目3\n" + "### 有序列表\n" + "1. 項(xiàng)目1\n" + "2. 項(xiàng)目2\n" + "3. 項(xiàng)目3\n" + "## 鏈接\n" + "[百度](https://www.baidu.com)\n" + "## 圖片\n" + "\n" + "## 表格\n" + "| 表頭1 | 表頭2 | 表頭3 |\n" + "|-------|-------|-------|\n" + "| 單元格1 | 單元格2 | 單元格3 |\n" + "| 單元格4 | 單元格5 | 單元格6 |"; toDoc(markdownContent, "test23"); } }
這個代碼是初步版本,導(dǎo)出的文檔直接保存在了本地,初步測試成功。如果運(yùn)行main方法測試報錯,比如這樣:
提示找不到xx方法,是因?yàn)樾绿砑拥膒oi-tl依賴的版本問題,因?yàn)轫?xiàng)目是使用的ruoyi腳手架的版本比較古老,項(xiàng)目本身依賴的poi版本比較舊是4.X版本,所以將poi-tl版本降低一點(diǎn)就行了,比如使用1.10.1版本,然后再次運(yùn)行就成功了
轉(zhuǎn)換markdown到word過程中,首先需要一個word模板,實(shí)際上還是用markdown內(nèi)容經(jīng)過markdown渲染器渲染后的內(nèi)容去填充word模版中的占位符{{md}}
word模版可以從poi-tl項(xiàng)目的官方github倉庫下載:https://github.com/Sayi/poi-tl/tree/master/poi-tl-plugin-markdown/src/test/resources
將這個markdown文件夾放在本項(xiàng)目的resources目錄下就可以了
里邊有5個文件,其中4個md文件主要是為了測試使用的,word文件是用于渲染的模版
內(nèi)容是這樣的:
然后執(zhí)行剛才工具類中main方法成功后,在項(xiàng)目target下就會生成一個新的渲染markdown內(nèi)容填充后的word文檔
內(nèi)容為這樣:
生成的效果還不錯。
如果你直接用md文件測試,而不是使用一段md格式的字符串,測試方法可以參考官方的測試方法:
package com.deepoove.poi.plugin.markdown; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import com.deepoove.poi.XWPFTemplate; import com.deepoove.poi.config.Configure; public class MarkdownTest { public static void testMarkdown(String name) throws Exception { MarkdownRenderData code = new MarkdownRenderData(); byte[] bytes = Files.readAllBytes(Paths.get("src/test/resources/markdown/" + name + ".md")); String mkdn = new String(bytes); code.setMarkdown(mkdn); MarkdownStyle style = MarkdownStyle.newStyle(); style.setShowHeaderNumber(true); code.setStyle(style); Map<String, Object> data = new HashMap<>(); data.put("md", code); Configure config = Configure.builder().bind("md", new MarkdownRenderPolicy()).build(); XWPFTemplate.compile("src/test/resources/markdown/markdown_template.docx", config) .render(data) .writeToFile("target/out_markdown_" + name + ".docx"); } public static void main(String[] args) throws Exception { testMarkdown("api"); testMarkdown("func"); testMarkdown("README"); } }
實(shí)際項(xiàng)目中是需要web導(dǎo)出下載word文件的,所以在接口調(diào)用的時候,需要修改成輸出流的方式就可以了。具體使用方法可以參考官方中文文檔:Poi-tl Documentation
不過今天訪問不了了,網(wǎng)站掛掉了,昨天還是可以正常訪問的
現(xiàn)在直接提示這個,不知道什么原因,可能過幾天就可以正常訪問了 ,還有就是模版文件中有個海豚的背景,項(xiàng)目中使用不想要這個背景,可以在word文檔中去掉
通過菜單欄【設(shè)計(jì)】->【水印】->【刪除水印】即可
以上就是Java實(shí)現(xiàn)markdown格式內(nèi)容轉(zhuǎn)換為word的詳細(xì)內(nèi)容,更多關(guān)于Java markdown轉(zhuǎn)word的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解讀nextLine().split(“[\\s]“)的意思
這篇文章主要介紹了解讀nextLine().split(“[\\s]“)的意思,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04druid多數(shù)據(jù)源配置+Datasurce動態(tài)切換方式
這篇文章主要介紹了druid多數(shù)據(jù)源配置+Datasurce動態(tài)切換方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09為什么程序中突然多了 200 個 Dubbo-thread 線程的說明
這篇文章主要介紹了為什么程序中突然多了 200 個 Dubbo-thread 線程的說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09Java基礎(chǔ)學(xué)習(xí)之方法的重載知識總結(jié)
今天帶大家來回顧Java基礎(chǔ)知識,文中對Java方法的重載相關(guān)知識作了非常詳細(xì)的介紹,對正在學(xué)習(xí)java的小伙伴們有很好的幫助,需要的朋友可以參考下2021-05-05Spring?Boot?Shiro?auto-configure工作流程詳解
這篇文章主要為大家介紹了Spring?Boot?Shiro?auto-configure工作流程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02java Date裝成英文String后,無法再轉(zhuǎn)回Date的解決方案
本文介紹了java Date裝成英文String后,無法再轉(zhuǎn)回Date的解決方案。具有一定的參考價值,下面跟著小編一起來看下吧2017-01-01通過實(shí)例了解Java jdk和jre的區(qū)別
這篇文章主要介紹了通過實(shí)例了解Java jdk和jre的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-05-05java webApp異步上傳圖片實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了java webApp異步上傳圖片實(shí)現(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11