前一篇写的是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();
}
@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 = "<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 {
// 获取classpath
String 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 {
//获取classpath
String 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"来换行