Java生成Markdown格式内容完整代码示例
作者:xiaomifeng1010
这篇文章主要介绍了将Java对象数据生成为Markdown文本,并提供了一个MarkdownUtil工具类进行处理,文中通过代码介绍的非常详细,需要的朋友可以参考下
前一篇写的是markdown格式的文本内容转换保存为word文档,是假定已经有一个现成的markdown格式的文本,然后直接转换保存为word文档,不过在开发中,通常情况下,数据是从数据库中获取,拿到的数据映射到java对象上,这一篇就是处理如何将java对象数据生成为markdown文本。
添加Maven依赖:
<!-- excel工具 练习的项目自身的依赖--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.2</version> </dependency> <!-- 新添加的依赖--> <!-- markdown格式转换为html --> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> <version>0.21.0</version> </dependency> <!-- poi-tl和poi-tl-plugin-markdown是处理markdown格式转换为word格式,处理只处理markdown转换为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)数据 */ 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语法生成处理类,则可以根据项目要求,再次封装需要生成的对应的文档中的各类元素对象,比如生成有序列表,无序列表,文档首页标题,副标题,链接,图像,表格等
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语法的文本了, // 比如经济环境分析后边没加"\n\r",那么在word文档中就会变成带有#的目录,会变成 "经济环境分析###目录" 这样子 String title = javaToMarkdownGenerator.generateTitle("经济环境分析")+"\n\r"; // 首页目录 String catalog = javaToMarkdownGenerator.generatecatalog()+"\n\r"; // 插入项目本地的logo图片 // 注意企业项目中不要直接放在resources目录下,因为获取的路径放在markdown的图片类型的语法中(最终是这样:), // 在转换word时候还是无法通过路径找到图片,linux系统获取文件需要通过流读取,路径的方式会提示找不到文件,windows系统不受影响 // 所以可以将需要插入到markdown中的图片上传到oss中,然后获取oss的图片的完整地址,网络超链接的形式,再插入到markdown中, // 这样在转换成word时候,会从网络上下载,以流的方式插入到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对象,从数据库查询出来的一个list集合,需要循环获取对象,然后添加到dataList中 // 模拟数据库中查询出来数据 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; } // 直接拼接一段富文本,因为网页上新增填写内容的时候,有些参数输入是使用的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的有序列表实现生成目录效果 * @return */ public String generatecatalog(){ String md = MarkdownHandler.of() .subTitle("目录") // 不要加ref方法,可以正常生成markdown文本,但是在转换成word内容的时候,commonmark会报错 // org.commonmark.node.OrderedList cannot be cast to org.commonmark.node.Paragra // .ol() .text("文档介绍") // .endOl() .ol() .text("经济环境") .ol() .text("1.1 全球化背景") .text("1.2 通缩问题产生的原因") .text("1.3 如何应对通缩") .text("1.4 国家实施的财政政策和货币政策") .endOl() .endOl() .ol() .text("失业问题") .ol() .text("2.1 失业率的概念") .text("2.2 如何统计失业率") .text("2.3 如何提升就业率") .endOl() .endOl() .ol() .text("理财投资") .ol() .text("3.1 理财投资的重要性") .text("3.2 如何选择理财投资产品") .text("3.3 理财投资的风险管理") .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; /** * 模拟从数据库中查出多条数据 * @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开发" + i); employee.setSalary("10000" + i); employees.add(employee); } return employees; } }
测试main方法会调用MarkdownUtil,用于生成word文档保存在本地,或者通过网络进行下载保存。
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转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格式内容转换为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转换为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不设置也是也可以的,可以正常解析到 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()); } } /** * 设置转换为word文档时的基本样式 * @param style * @return */ public MarkdownStyle setMarkdownStyle(MarkdownStyle style) { // 一定设置为false,不然生成的word文档中各元素前边都会加上有层级效果的一串数字, // 比如一级标题 前边出现1 二级标题出现1.1 三级标题出现1.1.1这样的数字 style.setShowHeaderNumber(false); // 修改默认的表格样式 // table header style(表格头部,通常为表格顶部第一行,用于设置列标题) RowStyle headerStyle = new RowStyle(); CellStyle cellStyle = new CellStyle(); // 设置表格头部的背景色为灰色 cellStyle.setBackgroundColor("cccccc"); Style textStyle = new Style(); // 设置表格头部的文字颜色为黑色 textStyle.setColor("000000"); // 头部文字加粗 textStyle.setBold(true); // 设置表格头部文字大小为12 textStyle.setFontSize(12); // 设置表格头部文字垂直居中 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(); // 设置表格边框颜色为黑色 borderStyle.setColor("000000"); // 设置表格边框宽度为3px borderStyle.setSize(3); // 设置表格边框样式为实线 borderStyle.setType(XWPFTable.XWPFBorderType.SINGLE); style.setTableBorderStyle(borderStyle); // 设置普通的引用文本样式 ParagraphStyle quoteStyle = new ParagraphStyle(); // 设置段落样式 quoteStyle.setSpacingBeforeLines(0.5d); quoteStyle.setSpacingAfterLines(0.5d); // 设置段落的文本样式 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文本,但是在转换成word内容的时候,commonmark会报错org.commonmark.node.OrderedList cannot be cast to org.commonmark.node.Paragra
还有一个注意事项,就是换行不要直接调用br()方法,因为转换的时候,在word文档中转换不了,直接生成了“<br>” 这样的文字,所以直接在markdown文本中使用"\n"来换行
总结
到此这篇关于Java生成Markdown格式内容的文章就介绍到这了,更多相关Java生成Markdown格式内容内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!