Java生成Markdown格式内容

         前一篇写的是markdown格式的文本内容转换保存为word文档,是假定已经有一个现成的markdown格式的文本,然后直接转换保存为word文档,不过在开发中,通常情况下,数据是从数据库中获取,拿到的数据映射到java对象上,这一篇就是处理如何将java对象数据生成为markdown文本

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();}@Overridepublic 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;}@Overridepublic 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 = "<br>";}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.google.common.collect.Lists;
import com.ruoyi.common.markdown.MarkdownHandler;
import com.ruoyi.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();
//        首页大标题String title = javaToMarkdownGenerator.generateTitle("经济环境分析\n");
//        首页目录String catalog = javaToMarkdownGenerator.generatecatalog();
//        插入项目本地的logo图片String imgPath = JavaToMarkdownGenerator.class.getResource("/static/canton.jpg").getPath();String logo = javaToMarkdownGenerator.resloveImg(imgPath, "logo");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" +"![图片描述](https://www.baidu.com/img/bd_logo1.png)\n" +"## 表格\n" +"| 表头1 | 表头2 | 表头3 |\n" +"|-------|-------|-------|\n" +"| 单元格1 | 单元格2 | 单元格3 |\n" +"| 单元格4 | 单元格5 | 单元格6 |";theWholeMarkdownContent=theWholeMarkdownContent+markdownContent;MarkdownUtil.toDoc(theWholeMarkdownContent,"test225");System.out.println(catalog);}/*** 使用markdown的有序列表实现生成目录效果* @return*/public String generatecatalog(){String md = MarkdownHandler.of().title("目录")
//                不要加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 "!["+imgName+"]("+imgPath+")";}}@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.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.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
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.setShowHeaderNumber(true);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 {
//            获取classpathString path = MarkdownUtil.class.getClassLoader().getResource("").getPath();log.info("classpath:{}", path);XWPFTemplate.compile(path + "markdown" + File.separator + "markdown_template.docx", 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.setShowHeaderNumber(true);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 {//获取classpathString path = MarkdownUtil.class.getClassLoader().getResource("").getPath();log.info("classpath:{}", path);XWPFTemplate template = XWPFTemplate.compile(path + "markdown" + File.separator + "markdown_template.docx", config).render(data);template.writeAndClose(response.getOutputStream());response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8") + ".docx");response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=utf-8");} catch (IOException e) {log.error("下载word文档失败:{}", e.getMessage());}}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" +"![图片描述](https://www.baidu.com/img/bd_logo1.png)\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"来换行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1548961.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式学习--LinuxDay04

嵌入式学习--LinuxDay04 shell脚本 1.1数组 1.1.1数组的赋值 1.1.2数组的调用 1.2函数 1.2.1函数的定义方式 1.2.2函数的调用 2.分文件编程 2.1源文件 2.2头文件 3.编译工具 3.1 gcc编译工具 3.2 gdb调试 4.make工具 4.1定义 4.2Makefile格式 4.3Makefile管理多个文件 4.4Makef…

Gartner 魔力象限:单一供应商安全访问服务边缘 2024,Palo Alto Networks 再次荣膺领导者

Gartner Magic Quadrant for Single-Vendor SASE 2024 Gartner 魔力象限&#xff1a;单一供应商安全访问服务边缘 2024&#xff0c;Palo Alto Networks 再次荣膺领导者 请访问原文链接&#xff1a;https://sysin.org/blog/gartner-magic-quadrant-single-vendor-sase-2024/&a…

成都睿明智科技有限公司抖音电商服务靠谱吗?

在这个电商风起云涌的时代&#xff0c;抖音作为短视频直播的超级流量池&#xff0c;正深刻改变着人们的购物习惯。无数商家蜂拥而至&#xff0c;渴望在这片蓝海中找到属于自己的岛屿。而提及抖音电商服务&#xff0c;成都睿明智科技有限公司无疑是一个备受瞩目的名字。那么&…

了解Webpack并处理样式文件

目录 引入定义安装和使用配置文件命令配置单独文件指定文件 处理样式css-loader使用 style-loaderless-loaderPostCSSpostcss-loaderpostcss-preset-env 引入 随着前端的快速发展&#xff0c;目前前端的开发已经变的越来越复杂了&#xff1a; 比如开发过程中我们需要通过模块化…

Harbor使用

文章目录 1、上传镜像1.1、在Harbor上创建一个项目1.2、docker添加安全访问权限1.3、推送docker镜像到该项目中1.3.1、登录到Harbor1.3.2、给镜像重新打一个标签1.3.3、推送镜像到Harbor中 2、拉取镜像2.1、先删掉原来的镜像2.2、执行拉取命令 1、上传镜像 需求&#xff1a;将…

OpenHarmony标准系统上实现对rk系列芯片NPU的支持(驱动移植)

1.将RKNPU驱动移植到Openharmony内核 本文以rk3568为例&#xff0c;将RKNPU驱动移植到Openharmony使用的kernel 5.10中 开发环境 DAYU200 rk3568开发板OpenHarmony 4.1 Release 64位系统 文档约定&#xff1a;4.1r_3568为OpenHarmony标准系统源码根目录 1.0 环境准备 1.搭建O…

RedisBoost Web缓存加速平台

1.产品介绍 产品名称:RedisBoost Web缓存加速平台 主要功能: 智能缓存策略配置 功能描述:RedisBoost提供了一套直观易用的缓存策略配置界面,允许用户根据业务场景自定义缓存策略,包括缓存时间(TTL)、缓存淘汰算法(如LRU、LFU)、数据分区与分片策略等。支持动态调整策…

Cisco ASA 9.22.1 发布下载,新增功能概览

Cisco ASA 9.22.1 - 思科自适应安全设备 (ASA) 软件 Cisco Adaptive Security Appliance (ASA) 请访问原文链接&#xff1a;https://sysin.org/blog/cisco-asa/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 新增功能 重要…

【机器学习】---深入探讨图神经网络(GNN)

深入探讨图神经网络 1. 图的基本构成示例图邻接矩阵 2. GNN的基本原理消息传递机制更新公式 3. GNN的类型及应用3.1 Graph Convolutional Networks (GCN)GCN实现示例 3.2 Graph Attention Networks (GAT)GAT实现示例 3.3 GraphSAGEGraphSAGE实现示例 4. GNN的应用场景5. GNN的挑…

【BurpSuite】访问控制漏洞和权限提升 | Access control vulnerabilities (3-6)

&#x1f3d8;️个人主页&#xff1a; 点燃银河尽头的篝火(●’◡’●) 如果文章有帮到你的话记得点赞&#x1f44d;收藏&#x1f497;支持一下哦 【BurpSuite】访问控制漏洞和权限提升 | Access control vulnerabilities (3-6&#xff09; 实验三 Lab: User role controlled b…

1. AOSP源码导入到AndroidStudio

1. AOSP源码导入到AndroidStudio 原文地址:http://www.androidcrack.com/index.php/archives/6/ ⚠️ 在执行一下操作前, 请先完整的编译一次系统, 若不清楚如何编译系统. 请访问下面文章 http://www.androidcrack.com/index.php/archives/3/ 1. 生成idegen.jar source build…

微服务学习笔记之Docker

目录 认识Docker 安装Docker 安装yum工具 配置Docker的yum源 更新yum&#xff0c;建立缓存 安装Docker 启动并校验 配置镜像加速 Docker常见命令 命令 演示 给命令起别名 Docker数据卷 认识数据卷 数据卷常见命令 nginx的html目录挂载演示 数据卷挂载本地目录或…

使用Docker-Compose部署SpringBoot项目的案例

Docker-Compose是Docker官方的一个开源项目&#xff0c;主要用于实现对Docker容器集群的快速编排和管理。该项目由Python编写&#xff0c;通过调用Docker服务提供的API来管理容器。只要所操作的平台支持Docker API&#xff0c;就可以利用Docker-Compose进行编排管理。Docker-Co…

OpenMV与STM32通信全面指南

目录 引言 一、OpenMV和STM32简介 1.1 OpenMV简介 1.2 STM32简介 二、通信协议概述 三、硬件连接 3.1 硬件准备 3.2 引脚连接 四、软件环境搭建 4.1 OpenMV IDE安装 4.2 STM32开发环境 五、UART通信实现 5.1 OpenMV端编程 5.2 STM32端编程 六、SPI通信实现 6.1 …

Vue devtools 插件

一、安装 去这下载https://chrome.zzzmh.cn/ 打开chrome的扩展程序 再打开开发模式 把刚才下载的拖到这里 然后把它固定到工具栏 就是这样了。 二、使用 程序通过open on live server后&#xff0c;打开开发者工具&#xff0c;找到vue就可以了。 这是代码 <div id"ap…

【Redis 源码】3dict字典数据结构

1 数据结构说明 dictionary数据结构&#xff0c;也称为哈希表&#xff08;hash table&#xff09;。用于存储字典。哈希是一个键值对的集合&#xff0c;每个键都是唯一的并与一个值相关联。字典的设计旨在提供高效的查找、插入和删除操作。 2 核心数据结构 hash 的数据结构定…

腾讯云SDK基本概念

本文旨在介绍您在使用音视频终端 SDK&#xff08;腾讯云视立方&#xff09;产品过程中可能会涉及到的基本概念。 音视频终端 SDK&#xff08;腾讯云视立方&#xff09; 应用 音视频终端 SDK&#xff08;腾讯云视立方&#xff09;通过应用的形式来管理您的项目&#xff08;Ap…

LabVIEW提高开发效率技巧----合理管理程序架构

在LabVIEW开发中&#xff0c;合理管理程序架构是保持项目可维护性和扩展性的关键。随着项目复杂度的增加&#xff0c;良好的架构设计可以避免代码混乱&#xff0c;并且便于后期的修改和扩展。以下是两种常见且有效的架构管理方式&#xff1a; 1. 面向对象编程&#xff08;OOP&a…

初识Tomcat

Tomcat是一款可以运行javaWebAPP的服务器软件。 一个服务器想要执行java代码&#xff0c;则需要JRE&#xff08;jvm、java运行环境等&#xff09;&#xff0c;但是需要执行javaWEB项目则还需要服务器软件&#xff0c;Tomacat就是其中很流行的一款。因为一个javaWEB项目会有很多…

USB2.0主机设备检测过程以及信号分析

一&#xff0c;USB协议发展 USB接口自1994年推出以来&#xff0c;经过30年的发展&#xff0c;从USB1.0发展到了现在的USB4.0&#xff0c;传输速率也从最开始的1.5Mbps&#xff0c;大幅提高到了最新的40Gbps。 USB协议按照速度等级和连接方式分可分为7个版本&#xff0c;但是从…