Java生成Markdown格式內(nèi)容完整代碼示例
前一篇寫的是markdown格式的文本內(nèi)容轉(zhuǎn)換保存為word文檔,是假定已經(jīng)有一個現(xiàn)成的markdown格式的文本,然后直接轉(zhuǎn)換保存為word文檔,不過在開發(fā)中,通常情況下,數(shù)據(jù)是從數(shù)據(jù)庫中獲取,拿到的數(shù)據(jù)映射到j(luò)ava對象上,這一篇就是處理如何將java對象數(shù)據(jù)生成為markdown文本。
添加Maven依賴:
<!-- excel工具 練習(xí)的項目自身的依賴--> <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>
1.首先編寫一個markdown的語法生成的處理類:
package com.xiaomifeng1010.common.markdown; import org.apache.commons.lang3.StringUtils; import java.util.*; /** * @author xiaomifeng1010 * @version 1.0 * @date: 2024-09-21 20:50 * @Description */ public class MarkdownHandler { // ~ APIs // ----------------------------------------------------------------------------------------------------------------- public static SectionBuilder of() { return new SectionBuilder(new Section(Section.Type.NORMAL, null, null, null, 0)); } // ~ public classes & public constants & public enums // ----------------------------------------------------------------------------------------------------------------- public enum Style { NORMAL("normal"), BOLD("bold"), ITALIC("italic"), RED("red"), GREEN("green"), GRAY("gray"), YELLOW("gold"), BLUE("blue"); private final String name; Style(String name) { this.name = name; } public String getName() { return name; } } public static class Fonts { public static final Fonts EMPTY = Fonts.of(""); private final String text; // ~ private fields // ------------------------------------------------------------------------------------------------------------- private Set<Style> styles = Collections.emptySet(); private Fonts(String text, Style... style) { this.text = text != null ? text : ""; if (style != null) { this.styles = new HashSet<>(Arrays.asList(style)); } } // ~ public methods // ------------------------------------------------------------------------------------------------------------- public static Fonts of(String text) { return new Fonts(text, Style.NORMAL); } public static Fonts of(String text, Style... style) { return new Fonts(text, style); } public boolean isEmpty() { return this.text == null || this.text.isEmpty(); } @Override public String toString() { if (styles.contains(Style.NORMAL)) { return text; } String last = text; for (Style style : styles) { last = parseStyle(last, style); } return last; } // ~ private methods // ------------------------------------------------------------------------------------------------------------- private String parseStyle(String text, Style style) { if (text == null || style == null) { return text; } switch (style) { case NORMAL: break; case BOLD: return "**" + text + "**"; case ITALIC: return "*" + text + "*"; case RED: case GREEN: case BLUE: case YELLOW: return "<font color='" + style.getName() + "'>" + text + "</font>"; } return text; } } /** * 代表一行,可以是一個普通文本或一個K-V(s)數(shù)據(jù) */ public static class MetaData { // ~ public constants // ------------------------------------------------------------------------------------------------------------- public static final String DEFAULT_SEPARATOR = ":"; public static final String DEFAULT_VALUE_SEPARATOR = " | "; public static final String LINK_TEMPLATE = "[%s?](%s)"; // ~ private fields // ------------------------------------------------------------------------------------------------------------- private final Type type; private final Fonts text; private final Collection<Fonts> values; private final String separator = DEFAULT_SEPARATOR; private final String valueSeparator = DEFAULT_VALUE_SEPARATOR; public MetaData(Fonts text) { this(text, null); } public MetaData(Type type) { this(type, null, null); } public MetaData(Fonts text, Collection<Fonts> values) { this(Type.NORMAL, text, values); } public MetaData(Type type, Fonts text, Collection<Fonts> values) { this.type = type; this.text = text; this.values = values; } @Override public String toString() { return generateString(this.valueSeparator); } /** * generate one line */ private String generateString(String valueSeparator) { boolean hasValues = values != null && !values.isEmpty(); boolean hasText = text != null && !text.isEmpty(); StringJoiner joiner = new StringJoiner(valueSeparator); String ret = ""; switch (type) { case NORMAL: if (hasText && hasValues) { values.forEach(v -> joiner.add(v.toString())); ret = text + separator + joiner; } else if (!hasText && hasValues) { values.forEach(v -> joiner.add(v.toString())); ret = joiner.toString(); } else if (hasText) { ret = text.toString(); } break; case LINK: if (hasText && hasValues) { Fonts fonts = values.stream().findFirst().orElse(null); if (fonts == null) { break; } ret = String.format(LINK_TEMPLATE, text, fonts); } else if (!hasText && hasValues) { Fonts url = values.stream().findFirst().orElse(null); if (url == null) { break; } ret = String.format(LINK_TEMPLATE, url, url); } else if (hasText) { ret = String.format(LINK_TEMPLATE, text, text); } break; case LINK_LIST: if (hasText && hasValues) { ret = text + separator + generateLinkList(values); } else if (!hasText && hasValues) { ret = generateLinkList(values); } else if (hasText) { ret = String.format(LINK_TEMPLATE, text, text); } break; case BR: ret = " "; } return ret; } // ~ private methods // ------------------------------------------------------------------------------------------------------------- private String generateLinkList(Collection<Fonts> values) { if (values == null || values.isEmpty()) { return ""; } Object[] valueArr = values.toArray(); StringJoiner linkList = new StringJoiner(valueSeparator); for (int i = 0; i + 1 < valueArr.length; i += 2) { linkList.add(String.format(LINK_TEMPLATE, valueArr[i], valueArr[i + 1])); } boolean isPairNum = (valueArr.length % 2) == 0; if (!isPairNum) { String lastUrl = valueArr[valueArr.length - 1].toString(); linkList.add(String.format(LINK_TEMPLATE, lastUrl, lastUrl)); } return linkList.toString(); } private enum Type { /** only plain text, plain text list with a name */ NORMAL, /** * text : link name * values: index 0 is URL if existed. */ LINK, LINK_LIST, BR, } } // ~ private class & private implements // ----------------------------------------------------------------------------------------------------------------- private static class Section { private final int depth; private Type type; private Object data; private Section parent; private List<Section> children; private Section(Type type, Object data, Section parent, List<Section> children, int depth) { this.type = type; this.data = data; this.parent = parent; this.children = children; this.depth = depth; } // ~ public methods // ------------------------------------------------------------------------------------------------------------- public void addChild(Section child) { lazyInitChildren(); children.add(child); } public boolean childIsEmpty() { return children == null || children.isEmpty(); } // ~ private methods // ------------------------------------------------------------------------------------------------------------- private StringBuilder parse(StringBuilder latestData) { switch (type) { case LINK: case NORMAL: latestData.append('\n').append(parseData("")); return latestData; case BIG_TITLE: latestData.append('\n').append(parseData("# ")); return latestData; case TITLE: latestData.append('\n').append(parseData("##### ")); return latestData; case SUBTITLE: latestData.append('\n').append(parseData("### ")); return latestData; case REF: return parseRefSection(latestData); case CODE: StringBuilder codeBlock = new StringBuilder(latestData.length() + 10); codeBlock.append("\n```").append(latestData).append("\n```"); return codeBlock; case ORDER_LIST: return parseOrderListSection(latestData); case UN_ORDER_LIST: return parseUnOrderListSection(latestData); case TABLE: return parseTableSection(latestData); case BR: return latestData.append(parseData("")); } return latestData; } private String parseData(String prefix) { if (data == null) { return ""; } return prefix + data; } private StringBuilder parseRefSection(StringBuilder latestData) { char[] chars = latestData.toString().toCharArray(); if (chars.length <= 0) { return latestData; } StringBuilder data = new StringBuilder(chars.length * 2); if (chars[0] != '\n') { data.append("> "); } char last = 0; for (char c : chars) { if (last == '\n') { data.append("> "); } data.append(c); last = c; } return data; } private StringBuilder parseOrderListSection(StringBuilder latestData) { char[] chars = latestData.toString().toCharArray(); if (chars.length <= 0) { return latestData; } StringBuilder data = new StringBuilder(chars.length * 2); String padding = String.join("", Collections.nCopies(depth * 4, " ")); int order = 1; if (chars[0] != '\n') { data.append(padding).append(order++).append(". "); } char last = 0; for (char c : chars) { if (last == '\n' && c != '\n' && c != ' ') { data.append(padding).append(order++).append(". "); } data.append(c); last = c; } return data; } private StringBuilder parseUnOrderListSection(StringBuilder latestData) { char[] chars = latestData.toString().toCharArray(); if (chars.length <= 0) { return latestData; } StringBuilder data = new StringBuilder(chars.length * 2); String padding = String.join("", Collections.nCopies(depth * 4, " ")); if (chars[0] != '\n') { data.append(padding).append("- "); } char last = 0; for (char c : chars) { if (last == '\n' && c != '\n' && c != ' ') { data.append(padding).append("- "); } data.append(c); last = c; } return data; } private StringBuilder parseTableSection(StringBuilder latestData) { if (data != null) { Object[][] tableData = (Object[][]) data; if (tableData.length > 0 && tableData[0].length > 0) { StringJoiner titles = new StringJoiner(" | "), extras = new StringJoiner(" | "); for (Object t : tableData[0]) { titles.add(t != null ? t.toString() : ""); extras.add("-"); } latestData.append("\n\n").append(titles).append('\n').append(extras); for (int i = 1; i < tableData.length; i++) { StringJoiner dataJoiner = new StringJoiner(" | "); for (int j = 0; j < tableData[i].length; j++) { dataJoiner.add(tableData[i][j] != null ? tableData[i][j].toString() : ""); } latestData.append('\n').append(dataJoiner); } } } return latestData.append('\n'); } private void lazyInitChildren() { if (children == null) { children = new ArrayList<>(); } } // ~ getter & setter // ------------------------------------------------------------------------------------------------------------- public Type getType() { return type; } public void setType(Type type) { this.type = type; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public Section getParent() { return parent; } public void setParent(Section parent) { this.parent = parent; } public List<Section> getChildren() { return children; } public void setChildren(List<Section> children) { this.children = children; } public int getDepth() { return depth; } private enum Type { /** * data is {@link MetaData} and plain text */ NORMAL, /** * data is {@link MetaData} and h2 */ BIG_TITLE, /** * data is {@link MetaData} and h3 */ TITLE, /** * data is {@link MetaData} and h4 */ SUBTITLE, /** * data is {@code null}, content is children */ REF, /** * data is {@code null}, content is children */ CODE, /** * data is matrix, aka String[][] */ TABLE, /** * data is {@code null}, content is children */ ORDER_LIST, /** * data is {@code null}, content is children */ UN_ORDER_LIST, /** * data is {@link MetaData} */ LINK, BR } } public static class SectionBuilder { private static final MdParser parser = new MdParser(); /** * first is root */ private final Section curSec; /** * code, ref curr -> par */ private Section parentSec; /** * init null */ private SectionBuilder parentBuilder; private SectionBuilder(Section curSec) { this.curSec = curSec; } private SectionBuilder(Section curSec, Section parentSec, SectionBuilder parentBuilder) { this.curSec = curSec; this.parentSec = parentSec; this.parentBuilder = parentBuilder; } // ~ public methods // ------------------------------------------------------------------------------------------------------------- public SectionBuilder text(String text) { return text(text, (String) null); } public SectionBuilder text(String name, String value) { if (name != null) { Collection<Fonts> values = value != null ? Collections.singletonList(Fonts.of(value)) : Collections.emptyList(); curSec.addChild(new Section(Section.Type.NORMAL, new MetaData(MetaData.Type.NORMAL, Fonts.of(name, (Style) null), values), curSec, null, curSec.getDepth())); } return this; } public SectionBuilder text(String text, Style... style) { if (text != null) { curSec.addChild(new Section(Section.Type.NORMAL, new MetaData(Fonts.of(text, style)), curSec, null, curSec.getDepth())); } return this; } public SectionBuilder text(Collection<String> values) { if (values != null && !values.isEmpty()) { text(null, values); } return this; } public SectionBuilder text(String name, Collection<String> values) { if (values == null || values.size() <= 0) { return text(name); } return text(name, null, values); } public SectionBuilder text(String name, Style valueStyle, Collection<String> values) { if (values == null || values.size() <= 0) { return text(name); } if (valueStyle == null) { valueStyle = Style.NORMAL; } List<Fonts> ele = new ArrayList<>(values.size()); for (String value : values) { ele.add(Fonts.of(value, valueStyle)); } curSec.addChild(new Section(Section.Type.NORMAL, new MetaData(Fonts.of(name), ele), curSec, null, curSec.getDepth())); return this; } public SectionBuilder bigTitle(String title) { if (StringUtils.isNotBlank(title)) { curSec.addChild(new Section(Section.Type.BIG_TITLE, new MetaData(Fonts.of(title)), curSec, null, curSec.getDepth())); } return this; } public SectionBuilder title(String title) { return title(title, Style.NORMAL); } public SectionBuilder title(String title, Style color) { if (StringUtils.isNotBlank(title)) { curSec.addChild(new Section(Section.Type.TITLE, new MetaData(Fonts.of(title, color)), curSec, null, curSec.getDepth())); } return this; } public SectionBuilder title(String title, Fonts... label) { return title(title, null, label); } public SectionBuilder title(String title, Style titleColor, Fonts... label) { if (StringUtils.isNotBlank(title)) { if (titleColor == null) { titleColor = Style.NORMAL; } List<Fonts> labelList = label != null ? Arrays.asList(label) : Collections.emptyList(); curSec.addChild(new Section(Section.Type.TITLE, new MetaData(Fonts.of(title, titleColor), labelList), curSec, null, curSec.getDepth())); } return this; } public SectionBuilder subTitle(String title) { if (StringUtils.isNotBlank(title)) { curSec.addChild(new Section(Section.Type.SUBTITLE, new MetaData(Fonts.of(title)), curSec, null, curSec.getDepth())); } return this; } public SectionBuilder ref() { Section refSection = new Section(Section.Type.REF, null, curSec, new ArrayList<>(), curSec.getDepth()); curSec.addChild(refSection); return new SectionBuilder(refSection, curSec, this); } public SectionBuilder endRef() { return this.parentBuilder != null ? this.parentBuilder : this; } public TableDataBuilder table() { return new TableDataBuilder(curSec, this); } public SectionBuilder link(String url) { return link(null, url); } public SectionBuilder link(String name, String url) { if (StringUtils.isBlank(name)) { name = url; } if (StringUtils.isNotBlank(url)) { MetaData links = new MetaData(MetaData.Type.LINK, Fonts.of(name), Collections.singletonList(Fonts.of(url))); curSec.addChild(new Section(Section.Type.NORMAL, links, curSec, null, curSec.getDepth())); } return this; } public SectionBuilder links(Map<String, String> urlMappings) { return links(null, urlMappings); } public SectionBuilder links(String name, Map<String, String> urlMappings) { if (urlMappings != null && !urlMappings.isEmpty()) { List<Fonts> serialUrlInfos = new ArrayList<>(); for (Map.Entry<String, String> entry : urlMappings.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); serialUrlInfos.add(Fonts.of(key != null ? key : "")); serialUrlInfos.add(Fonts.of(value != null ? value : "")); } Fonts wrappedName = StringUtils.isNotBlank(name) ? Fonts.of(name) : Fonts.EMPTY; MetaData linksGroup = new MetaData(MetaData.Type.LINK_LIST, wrappedName, serialUrlInfos); curSec.addChild(new Section(Section.Type.NORMAL, linksGroup, curSec, null, curSec.getDepth())); } return this; } public SectionBuilder ol() { int depth = (curSec.getType() == Section.Type.ORDER_LIST || curSec.getType() == Section.Type.UN_ORDER_LIST) ? curSec.getDepth() + 1 : curSec.getDepth(); Section OrderListSec = new Section(Section.Type.ORDER_LIST, null, curSec, new ArrayList<>(), depth); curSec.addChild(OrderListSec); return new SectionBuilder(OrderListSec, curSec, this); } public SectionBuilder endOl() { return this.parentBuilder != null ? this.parentBuilder : this; } public SectionBuilder ul() { int depth = (curSec.getType() == Section.Type.ORDER_LIST || curSec.getType() == Section.Type.UN_ORDER_LIST) ? curSec.getDepth() + 1 : curSec.getDepth(); Section unOrderListSec = new Section(Section.Type.UN_ORDER_LIST, null, curSec, new ArrayList<>(), depth); curSec.addChild(unOrderListSec); return new SectionBuilder(unOrderListSec, curSec, this); } public SectionBuilder endUl() { return this.parentBuilder != null ? this.parentBuilder : this; } public SectionBuilder code() { Section codeSec = new Section(Section.Type.CODE, null, curSec, new ArrayList<>(), curSec.getDepth()); curSec.addChild(codeSec); return new SectionBuilder(codeSec, curSec, this); } public SectionBuilder endCode() { return this.parentBuilder != null ? this.parentBuilder : this; } public SectionBuilder br() { curSec.addChild(new Section(Section.Type.BR, new MetaData(MetaData.Type.BR), parentSec, null, curSec.getDepth())); return this; } public String build() { return parser.parse(curSec); } } public static class TableDataBuilder { private final Section parentSec; private final SectionBuilder parentBuilder; private Object[][] tableData; private TableDataBuilder(Section parentSec, SectionBuilder parentBuilder) { this.parentSec = parentSec; this.parentBuilder = parentBuilder; } // ~ public methods // ------------------------------------------------------------------------------------------------------------- public TableDataBuilder data(Object[][] table) { if (table != null && table.length > 0 && table[0].length > 0) { tableData = table; } return this; } public TableDataBuilder data(Object[] title, Object[][] data) { if (title == null && data != null) { return data(data); } if (data != null && data.length > 0 && data[0].length > 0) { int minCol = Math.min(title.length, data[0].length); tableData = new Object[data.length + 1][minCol]; tableData[0] = Arrays.copyOfRange(title, 0, minCol); for (int i = 0; i < data.length; i++) { tableData[i + 1] = Arrays.copyOfRange(data[i], 0, minCol); } } return this; } public SectionBuilder endTable() { parentSec.addChild(new Section(Section.Type.TABLE, tableData, parentSec, null, parentSec.getDepth())); return parentBuilder; } } private static class MdParser { // ~ public methods // ------------------------------------------------------------------------------------------------------------- public String parse(Section sec) { Section root = findRoot(sec); return doParse(root, root).toString().trim(); } // ~ private methods // ------------------------------------------------------------------------------------------------------------- private Section findRoot(Section sec) { if (sec.getParent() == null) { return sec; } return findRoot(sec.getParent()); } private StringBuilder doParse(Section cur, Section root) { if (cur == null) { return null; } if (cur.childIsEmpty()) { return cur.parse(new StringBuilder()); } StringBuilder childData = new StringBuilder(); for (Section child : cur.getChildren()) { StringBuilder part = doParse(child, root); if (part != null) { childData.append(part); } } return cur.parse(childData).append(cur.getParent() == root ? '\n' : ""); } } }
有了通用的markdown語法生成處理類,則可以根據(jù)項目要求,再次封裝需要生成的對應(yīng)的文檔中的各類元素對象,比如生成有序列表,無序列表,文檔首頁標題,副標題,鏈接,圖像,表格等
2. 所以再寫一個生成類,里邊附帶了測試方法
package com.xiaomifeng1010.common.markdown.todoc; import com.xiaomifeng1010.common.markdown.MarkdownHandler; import com.xiaomifeng1010.common.utils.MarkdownUtil; import lombok.Data; import org.apache.commons.collections4.CollectionUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @author xiaomifeng1010 * @version 1.0 * @date: 2024-09-27 18:08 * @Description */ public class JavaToMarkdownGenerator { public static void main(String[] args) { String theWholeMarkdownContent = ""; // 生成一個簡單的多元素的word文檔 JavaToMarkdownGenerator javaToMarkdownGenerator = new JavaToMarkdownGenerator(); // 首頁大標題;注意每部分元素之間一定要換行,不然最后輸出到word文檔中的時候就會變成帶有markdown語法的文本了, // 比如經(jīng)濟環(huán)境分析后邊沒加"\n\r",那么在word文檔中就會變成帶有#的目錄,會變成 "經(jīng)濟環(huán)境分析###目錄" 這樣子 String title = javaToMarkdownGenerator.generateTitle("經(jīng)濟環(huán)境分析")+"\n\r"; // 首頁目錄 String catalog = javaToMarkdownGenerator.generatecatalog()+"\n\r"; // 插入項目本地的logo圖片 // 注意企業(yè)項目中不要直接放在resources目錄下,因為獲取的路徑放在markdown的圖片類型的語法中(最終是這樣:), // 在轉(zhuǎn)換word時候還是無法通過路徑找到圖片,linux系統(tǒng)獲取文件需要通過流讀取,路徑的方式會提示找不到文件,windows系統(tǒng)不受影響 // 所以可以將需要插入到markdown中的圖片上傳到oss中,然后獲取oss的圖片的完整地址,網(wǎng)絡(luò)超鏈接的形式,再插入到markdown中, // 這樣在轉(zhuǎn)換成word時候,會從網(wǎng)絡(luò)上下載,以流的方式插入到word中 String imgPath = JavaToMarkdownGenerator.class.getResource("/static/canton.jpg").getPath(); String logo = javaToMarkdownGenerator.resloveImg(imgPath, "logo")+"\n\r"; theWholeMarkdownContent = title + catalog + logo; // 插入正文 List<List<String>> dataList = new ArrayList<>(); // java實踐中一般是一個java對象,從數(shù)據(jù)庫查詢出來的一個list集合,需要循環(huán)獲取對象,然后添加到dataList中 // 模擬數(shù)據(jù)庫中查詢出來數(shù)據(jù) Employee employee = new Employee(); List<Employee> employeeList = employee.getEmployees(); if (CollectionUtils.isNotEmpty(employeeList)) { for (Employee employee1 : employeeList) { List<String> list = new ArrayList<>(); list.add(employee1.getName()); list.add(employee1.getSex()); list.add(employee1.getAge()); list.add(employee1.getHeight()); dataList.add(list); } String firstTable= javaToMarkdownGenerator.generateTable(dataList, "表格1","姓名", "性別", "芳齡", "身高"); theWholeMarkdownContent=theWholeMarkdownContent + firstTable; } // 直接拼接一段富文本,因為網(wǎng)頁上新增填寫內(nèi)容的時候,有些參數(shù)輸入是使用的markdown富文本編輯器 String markdownContent = "\n# 一級標題\n" + "## 二級標題\n" + "### 三級標題\n" + "#### 四級標題\n" + "##### 五級標題\n" + "###### 六級標題\n" + "## 段落\n" + "這是一段普通的段落。\n" + "## 列表\n" + "### 無序列表\n" + "- 項目1\n" + "- 項目2\n" + "- 項目3\n" + "### 有序列表\n" + "1. 項目1\n" + "2. 項目2\n" + "3. 項目3\n" + "## 鏈接\n" + "[百度](https://www.baidu.com)\n" + "## 圖片\n" + "\n" + "## 表格\n" + "| 表頭1 | 表頭2 | 表頭3 |\n" + "|-------|-------|-------|\n" + "| 單元格1 | 單元格2 | 單元格3 |\n" + "| 單元格4 | 單元格5 | 單元格6 |"; theWholeMarkdownContent=theWholeMarkdownContent+markdownContent; MarkdownUtil.toDoc(theWholeMarkdownContent,"test228"); System.out.println(catalog); } /** * 使用markdown的有序列表實現(xiàn)生成目錄效果 * @return */ public String generatecatalog(){ String md = MarkdownHandler.of() .subTitle("目錄") // 不要加ref方法,可以正常生成markdown文本,但是在轉(zhuǎn)換成word內(nèi)容的時候,commonmark會報錯 // org.commonmark.node.OrderedList cannot be cast to org.commonmark.node.Paragra // .ol() .text("文檔介紹") // .endOl() .ol() .text("經(jīng)濟環(huán)境") .ol() .text("1.1 全球化背景") .text("1.2 通縮問題產(chǎn)生的原因") .text("1.3 如何應(yīng)對通縮") .text("1.4 國家實施的財政政策和貨幣政策") .endOl() .endOl() .ol() .text("失業(yè)問題") .ol() .text("2.1 失業(yè)率的概念") .text("2.2 如何統(tǒng)計失業(yè)率") .text("2.3 如何提升就業(yè)率") .endOl() .endOl() .ol() .text("理財投資") .ol() .text("3.1 理財投資的重要性") .text("3.2 如何選擇理財投資產(chǎn)品") .text("3.3 理財投資的風(fēng)險管理") .endOl() .endOl() .build(); return md; } /** * 生成表格 * @paramJataList @paramtableHead @return **/ public String generateTable(List<List<String>> datalist, String tableTitle, String... tableHead){ // 添加表頭(表格第一行,列標題) datalist.add( 0, Arrays.asList(tableHead)); String[][] data=datalist.stream().map(list->list.toArray(new String[0])).toArray(String[][]::new); String markdownContent = MarkdownHandler.of() .title(tableTitle) .table() .data(data) .endTable() .build(); return markdownContent; } /** * 文檔首頁大標題效果 * @param title * @return */ public String generateTitle(String title){ return MarkdownHandler.of().bigTitle(title).build(); } /** * 處理圖片 * @param imgPath * @param imgName * @return */ String resloveImg(String imgPath,String imgName){ return ""; } } @Data class Employee{ private String name; private String sex; private String age; // 身高 private String height; // 體重 private String weight; // 籍貫 private String nativePlace; // 職位 private String position; // 薪資 private String salary; /** * 模擬從數(shù)據(jù)庫中查出多條數(shù)據(jù) * @return */ public List<Employee> getEmployees(){ List<Employee> employees = new ArrayList<>(); for (int i = 0; i < 10; i++) { Employee employee = new Employee(); employee.setName("張三" + i); employee.setSex("男"); employee.setAge("18" + i); employee.setHeight("180" + i); employee.setWeight("70" + i); employee.setNativePlace("北京" + i); employee.setPosition("java開發(fā)" + i); employee.setSalary("10000" + i); employees.add(employee); } return employees; } }
測試main方法會調(diào)用MarkdownUtil,用于生成word文檔保存在本地,或者通過網(wǎng)絡(luò)進行下載保存。
3.MarkdownUtil工具類:
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模板中的標簽{{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模板中的標簽{{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ù)字, // 比如一級標題 前邊出現(xiàn)1 二級標題出現(xiàn)1.1 三級標題出現(xiàn)1.1.1這樣的數(shù)字 style.setShowHeaderNumber(false); // 修改默認的表格樣式 // table header style(表格頭部,通常為表格頂部第一行,用于設(shè)置列標題) 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è)置表格邊框樣式為實線 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 = "# 一級標題\n" + "## 二級標題\n" + "### 三級標題\n" + "#### 四級標題\n" + "##### 五級標題\n" + "###### 六級標題\n" + "## 段落\n" + "這是一段普通的段落。\n" + "## 列表\n" + "### 無序列表\n" + "- 項目1\n" + "- 項目2\n" + "- 項目3\n" + "### 有序列表\n" + "1. 項目1\n" + "2. 項目2\n" + "3. 項目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"); } }
4.最終的輸出效果
注意在生成有序列表或者無序列表時候不要加ref方法,因為加了ref方法雖然可以正常生成markdown文本,但是在轉(zhuǎn)換成word內(nèi)容的時候,commonmark會報錯org.commonmark.node.OrderedList cannot be cast to org.commonmark.node.Paragra
還有一個注意事項,就是換行不要直接調(diào)用br()方法,因為轉(zhuǎn)換的時候,在word文檔中轉(zhuǎn)換不了,直接生成了“<br>” 這樣的文字,所以直接在markdown文本中使用"\n"來換行
總結(jié)
到此這篇關(guān)于Java生成Markdown格式內(nèi)容的文章就介紹到這了,更多相關(guān)Java生成Markdown格式內(nèi)容內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java 二進制數(shù)據(jù)與16進制字符串相互轉(zhuǎn)化方法
今天小編就為大家分享一篇java 二進制數(shù)據(jù)與16進制字符串相互轉(zhuǎn)化方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-07-07詳解Java中String,StringBuffer和StringBuilder的使用
這篇文章主要為大家詳細介紹了Java中String,StringBuffer和StringBuilder三者的區(qū)別以及使用,文中的少了講解詳細,感興趣的可以了解一下2022-07-07Java并發(fā)工具之Exchanger線程間交換數(shù)據(jù)詳解
這篇文章主要介紹了Java并發(fā)工具之Exchanger線程間交換數(shù)據(jù)詳解,Exchanger是一個用于線程間協(xié)作的工具類,Exchanger用于進行線程間的數(shù)據(jù)交 換,它提供一個同步點,在這個同步點,兩個線程可以交換彼此的數(shù)據(jù),需要的朋友可以參考下2023-12-12java判斷兩個List<String>集合是否存在交集三種方法
這篇文章主要介紹了三種判斷Java中兩個List集合是否存在交集的方法,分別是使用retainAll方法、使用Stream和anyMatch以及使用Set提高性能,每種方法都有其適用場景和優(yōu)缺點,需要的朋友可以參考下2025-03-03Java中使用ForkJoinPool的實現(xiàn)示例
ForkJoinPool是一個功能強大的Java類,用于處理計算密集型任務(wù),本文主要介紹了Java中使用ForkJoinPool的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下2023-09-09