Java生成Markdown格式內(nèi)容完整代碼示例
前一篇寫的是markdown格式的文本內(nèi)容轉(zhuǎn)換保存為word文檔,是假定已經(jīng)有一個(gè)現(xiàn)成的markdown格式的文本,然后直接轉(zhuǎn)換保存為word文檔,不過在開發(fā)中,通常情況下,數(shù)據(jù)是從數(shù)據(jù)庫中獲取,拿到的數(shù)據(jù)映射到j(luò)ava對(duì)象上,這一篇就是處理如何將java對(duì)象數(shù)據(jù)生成為markdown文本。
添加Maven依賴:
<!-- excel工具 練習(xí)的項(xiàng)目自身的依賴-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<!-- 新添加的依賴-->
<!-- markdown格式轉(zhuǎn)換為html -->
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.21.0</version>
</dependency>
<!-- poi-tl和poi-tl-plugin-markdown是處理markdown格式轉(zhuǎn)換為word格式,處理只處理markdown轉(zhuǎn)換為html,只需要commonnark依賴即可-->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.10.1</version>
</dependency>
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl-plugin-markdown</artifactId>
<version>1.0.3</version>
</dependency>1.首先編寫一個(gè)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;
}
}
/**
* 代表一行,可以是一個(gè)普通文本或一個(gè)K-V(s)數(shù)據(jù)
*/
public static class MetaData {
// ~ public constants
// -------------------------------------------------------------------------------------------------------------
public static final String DEFAULT_SEPARATOR = ":";
public static final String DEFAULT_VALUE_SEPARATOR = " | ";
public static final String LINK_TEMPLATE = "[%s?](%s)";
// ~ private fields
// -------------------------------------------------------------------------------------------------------------
private final Type type;
private final Fonts text;
private final Collection<Fonts> values;
private final String separator = DEFAULT_SEPARATOR;
private final String valueSeparator = DEFAULT_VALUE_SEPARATOR;
public MetaData(Fonts text) {
this(text, null);
}
public MetaData(Type type) {
this(type, null, null);
}
public MetaData(Fonts text, Collection<Fonts> values) {
this(Type.NORMAL, text, values);
}
public MetaData(Type type, Fonts text, Collection<Fonts> values) {
this.type = type;
this.text = text;
this.values = values;
}
@Override
public String toString() {
return generateString(this.valueSeparator);
}
/**
* generate one line
*/
private String generateString(String valueSeparator) {
boolean hasValues = values != null && !values.isEmpty();
boolean hasText = text != null && !text.isEmpty();
StringJoiner joiner = new StringJoiner(valueSeparator);
String ret = "";
switch (type) {
case NORMAL:
if (hasText && hasValues) {
values.forEach(v -> joiner.add(v.toString()));
ret = text + separator + joiner;
} else if (!hasText && hasValues) {
values.forEach(v -> joiner.add(v.toString()));
ret = joiner.toString();
} else if (hasText) {
ret = text.toString();
}
break;
case LINK:
if (hasText && hasValues) {
Fonts fonts = values.stream().findFirst().orElse(null);
if (fonts == null) {
break;
}
ret = String.format(LINK_TEMPLATE, text, fonts);
} else if (!hasText && hasValues) {
Fonts url = values.stream().findFirst().orElse(null);
if (url == null) {
break;
}
ret = String.format(LINK_TEMPLATE, url, url);
} else if (hasText) {
ret = String.format(LINK_TEMPLATE, text, text);
}
break;
case LINK_LIST:
if (hasText && hasValues) {
ret = text + separator + generateLinkList(values);
} else if (!hasText && hasValues) {
ret = generateLinkList(values);
} else if (hasText) {
ret = String.format(LINK_TEMPLATE, text, text);
}
break;
case BR:
ret = "
";
}
return ret;
}
// ~ private methods
// -------------------------------------------------------------------------------------------------------------
private String generateLinkList(Collection<Fonts> values) {
if (values == null || values.isEmpty()) {
return "";
}
Object[] valueArr = values.toArray();
StringJoiner linkList = new StringJoiner(valueSeparator);
for (int i = 0; i + 1 < valueArr.length; i += 2) {
linkList.add(String.format(LINK_TEMPLATE, valueArr[i], valueArr[i + 1]));
}
boolean isPairNum = (valueArr.length % 2) == 0;
if (!isPairNum) {
String lastUrl = valueArr[valueArr.length - 1].toString();
linkList.add(String.format(LINK_TEMPLATE, lastUrl, lastUrl));
}
return linkList.toString();
}
private enum Type {
/** only plain text, plain text list with a name */
NORMAL,
/**
* text : link name
* values: index 0 is URL if existed.
*/
LINK, LINK_LIST,
BR,
}
}
// ~ private class & private implements
// -----------------------------------------------------------------------------------------------------------------
private static class Section {
private final int depth;
private Type type;
private Object data;
private Section parent;
private List<Section> children;
private Section(Type type, Object data, Section parent, List<Section> children, int depth) {
this.type = type;
this.data = data;
this.parent = parent;
this.children = children;
this.depth = depth;
}
// ~ public methods
// -------------------------------------------------------------------------------------------------------------
public void addChild(Section child) {
lazyInitChildren();
children.add(child);
}
public boolean childIsEmpty() {
return children == null || children.isEmpty();
}
// ~ private methods
// -------------------------------------------------------------------------------------------------------------
private StringBuilder parse(StringBuilder latestData) {
switch (type) {
case LINK:
case NORMAL:
latestData.append('\n').append(parseData(""));
return latestData;
case BIG_TITLE:
latestData.append('\n').append(parseData("# "));
return latestData;
case TITLE:
latestData.append('\n').append(parseData("##### "));
return latestData;
case SUBTITLE:
latestData.append('\n').append(parseData("### "));
return latestData;
case REF:
return parseRefSection(latestData);
case CODE:
StringBuilder codeBlock = new StringBuilder(latestData.length() + 10);
codeBlock.append("\n```").append(latestData).append("\n```");
return codeBlock;
case ORDER_LIST:
return parseOrderListSection(latestData);
case UN_ORDER_LIST:
return parseUnOrderListSection(latestData);
case TABLE:
return parseTableSection(latestData);
case BR:
return latestData.append(parseData(""));
}
return latestData;
}
private String parseData(String prefix) {
if (data == null) {
return "";
}
return prefix + data;
}
private StringBuilder parseRefSection(StringBuilder latestData) {
char[] chars = latestData.toString().toCharArray();
if (chars.length <= 0) {
return latestData;
}
StringBuilder data = new StringBuilder(chars.length * 2);
if (chars[0] != '\n') {
data.append("> ");
}
char last = 0;
for (char c : chars) {
if (last == '\n') {
data.append("> ");
}
data.append(c);
last = c;
}
return data;
}
private StringBuilder parseOrderListSection(StringBuilder latestData) {
char[] chars = latestData.toString().toCharArray();
if (chars.length <= 0) {
return latestData;
}
StringBuilder data = new StringBuilder(chars.length * 2);
String padding = String.join("", Collections.nCopies(depth * 4, " "));
int order = 1;
if (chars[0] != '\n') {
data.append(padding).append(order++).append(". ");
}
char last = 0;
for (char c : chars) {
if (last == '\n' && c != '\n' && c != ' ') {
data.append(padding).append(order++).append(". ");
}
data.append(c);
last = c;
}
return data;
}
private StringBuilder parseUnOrderListSection(StringBuilder latestData) {
char[] chars = latestData.toString().toCharArray();
if (chars.length <= 0) {
return latestData;
}
StringBuilder data = new StringBuilder(chars.length * 2);
String padding = String.join("", Collections.nCopies(depth * 4, " "));
if (chars[0] != '\n') {
data.append(padding).append("- ");
}
char last = 0;
for (char c : chars) {
if (last == '\n' && c != '\n' && c != ' ') {
data.append(padding).append("- ");
}
data.append(c);
last = c;
}
return data;
}
private StringBuilder parseTableSection(StringBuilder latestData) {
if (data != null) {
Object[][] tableData = (Object[][]) data;
if (tableData.length > 0 && tableData[0].length > 0) {
StringJoiner titles = new StringJoiner(" | "), extras = new StringJoiner(" | ");
for (Object t : tableData[0]) {
titles.add(t != null ? t.toString() : "");
extras.add("-");
}
latestData.append("\n\n").append(titles).append('\n').append(extras);
for (int i = 1; i < tableData.length; i++) {
StringJoiner dataJoiner = new StringJoiner(" | ");
for (int j = 0; j < tableData[i].length; j++) {
dataJoiner.add(tableData[i][j] != null ? tableData[i][j].toString() : "");
}
latestData.append('\n').append(dataJoiner);
}
}
}
return latestData.append('\n');
}
private void lazyInitChildren() {
if (children == null) {
children = new ArrayList<>();
}
}
// ~ getter & setter
// -------------------------------------------------------------------------------------------------------------
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Section getParent() {
return parent;
}
public void setParent(Section parent) {
this.parent = parent;
}
public List<Section> getChildren() {
return children;
}
public void setChildren(List<Section> children) {
this.children = children;
}
public int getDepth() {
return depth;
}
private enum Type {
/**
* data is {@link MetaData} and plain text
*/
NORMAL,
/**
* data is {@link MetaData} and h2
*/
BIG_TITLE,
/**
* data is {@link MetaData} and h3
*/
TITLE,
/**
* data is {@link MetaData} and h4
*/
SUBTITLE,
/**
* data is {@code null}, content is children
*/
REF,
/**
* data is {@code null}, content is children
*/
CODE,
/**
* data is matrix, aka String[][]
*/
TABLE,
/**
* data is {@code null}, content is children
*/
ORDER_LIST,
/**
* data is {@code null}, content is children
*/
UN_ORDER_LIST,
/**
* data is {@link MetaData}
*/
LINK,
BR
}
}
public static class SectionBuilder {
private static final MdParser parser = new MdParser();
/**
* first is root
*/
private final Section curSec;
/**
* code, ref curr -> par
*/
private Section parentSec;
/**
* init null
*/
private SectionBuilder parentBuilder;
private SectionBuilder(Section curSec) {
this.curSec = curSec;
}
private SectionBuilder(Section curSec, Section parentSec, SectionBuilder parentBuilder) {
this.curSec = curSec;
this.parentSec = parentSec;
this.parentBuilder = parentBuilder;
}
// ~ public methods
// -------------------------------------------------------------------------------------------------------------
public SectionBuilder text(String text) {
return text(text, (String) null);
}
public SectionBuilder text(String name, String value) {
if (name != null) {
Collection<Fonts> values
= value != null ? Collections.singletonList(Fonts.of(value)) : Collections.emptyList();
curSec.addChild(new Section(Section.Type.NORMAL,
new MetaData(MetaData.Type.NORMAL, Fonts.of(name, (Style) null), values),
curSec, null, curSec.getDepth()));
}
return this;
}
public SectionBuilder text(String text, Style... style) {
if (text != null) {
curSec.addChild(new Section(Section.Type.NORMAL, new MetaData(Fonts.of(text, style)), curSec,
null, curSec.getDepth()));
}
return this;
}
public SectionBuilder text(Collection<String> values) {
if (values != null && !values.isEmpty()) {
text(null, values);
}
return this;
}
public SectionBuilder text(String name, Collection<String> values) {
if (values == null || values.size() <= 0) {
return text(name);
}
return text(name, null, values);
}
public SectionBuilder text(String name, Style valueStyle, Collection<String> values) {
if (values == null || values.size() <= 0) {
return text(name);
}
if (valueStyle == null) {
valueStyle = Style.NORMAL;
}
List<Fonts> ele = new ArrayList<>(values.size());
for (String value : values) {
ele.add(Fonts.of(value, valueStyle));
}
curSec.addChild(new Section(Section.Type.NORMAL, new MetaData(Fonts.of(name), ele), curSec, null,
curSec.getDepth()));
return this;
}
public SectionBuilder bigTitle(String title) {
if (StringUtils.isNotBlank(title)) {
curSec.addChild(new Section(Section.Type.BIG_TITLE, new MetaData(Fonts.of(title)), curSec,
null, curSec.getDepth()));
}
return this;
}
public SectionBuilder title(String title) {
return title(title, Style.NORMAL);
}
public SectionBuilder title(String title, Style color) {
if (StringUtils.isNotBlank(title)) {
curSec.addChild(new Section(Section.Type.TITLE, new MetaData(Fonts.of(title, color)),
curSec, null, curSec.getDepth()));
}
return this;
}
public SectionBuilder title(String title, Fonts... label) {
return title(title, null, label);
}
public SectionBuilder title(String title, Style titleColor, Fonts... label) {
if (StringUtils.isNotBlank(title)) {
if (titleColor == null) {
titleColor = Style.NORMAL;
}
List<Fonts> labelList = label != null ? Arrays.asList(label) : Collections.emptyList();
curSec.addChild(new Section(Section.Type.TITLE, new MetaData(Fonts.of(title, titleColor), labelList),
curSec, null, curSec.getDepth()));
}
return this;
}
public SectionBuilder subTitle(String title) {
if (StringUtils.isNotBlank(title)) {
curSec.addChild(new Section(Section.Type.SUBTITLE, new MetaData(Fonts.of(title)),
curSec, null, curSec.getDepth()));
}
return this;
}
public SectionBuilder ref() {
Section refSection = new Section(Section.Type.REF, null, curSec, new ArrayList<>(), curSec.getDepth());
curSec.addChild(refSection);
return new SectionBuilder(refSection, curSec, this);
}
public SectionBuilder endRef() {
return this.parentBuilder != null ? this.parentBuilder : this;
}
public TableDataBuilder table() {
return new TableDataBuilder(curSec, this);
}
public SectionBuilder link(String url) {
return link(null, url);
}
public SectionBuilder link(String name, String url) {
if (StringUtils.isBlank(name)) {
name = url;
}
if (StringUtils.isNotBlank(url)) {
MetaData links = new MetaData(MetaData.Type.LINK, Fonts.of(name),
Collections.singletonList(Fonts.of(url)));
curSec.addChild(new Section(Section.Type.NORMAL, links, curSec, null, curSec.getDepth()));
}
return this;
}
public SectionBuilder links(Map<String, String> urlMappings) {
return links(null, urlMappings);
}
public SectionBuilder links(String name, Map<String, String> urlMappings) {
if (urlMappings != null && !urlMappings.isEmpty()) {
List<Fonts> serialUrlInfos = new ArrayList<>();
for (Map.Entry<String, String> entry : urlMappings.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
serialUrlInfos.add(Fonts.of(key != null ? key : ""));
serialUrlInfos.add(Fonts.of(value != null ? value : ""));
}
Fonts wrappedName = StringUtils.isNotBlank(name) ? Fonts.of(name) : Fonts.EMPTY;
MetaData linksGroup = new MetaData(MetaData.Type.LINK_LIST, wrappedName, serialUrlInfos);
curSec.addChild(new Section(Section.Type.NORMAL, linksGroup, curSec, null, curSec.getDepth()));
}
return this;
}
public SectionBuilder ol() {
int depth = (curSec.getType() == Section.Type.ORDER_LIST || curSec.getType() == Section.Type.UN_ORDER_LIST)
? curSec.getDepth() + 1
: curSec.getDepth();
Section OrderListSec = new Section(Section.Type.ORDER_LIST, null, curSec, new ArrayList<>(), depth);
curSec.addChild(OrderListSec);
return new SectionBuilder(OrderListSec, curSec, this);
}
public SectionBuilder endOl() {
return this.parentBuilder != null ? this.parentBuilder : this;
}
public SectionBuilder ul() {
int depth = (curSec.getType() == Section.Type.ORDER_LIST || curSec.getType() == Section.Type.UN_ORDER_LIST)
? curSec.getDepth() + 1
: curSec.getDepth();
Section unOrderListSec = new Section(Section.Type.UN_ORDER_LIST, null, curSec, new ArrayList<>(), depth);
curSec.addChild(unOrderListSec);
return new SectionBuilder(unOrderListSec, curSec, this);
}
public SectionBuilder endUl() {
return this.parentBuilder != null ? this.parentBuilder : this;
}
public SectionBuilder code() {
Section codeSec = new Section(Section.Type.CODE, null, curSec, new ArrayList<>(), curSec.getDepth());
curSec.addChild(codeSec);
return new SectionBuilder(codeSec, curSec, this);
}
public SectionBuilder endCode() {
return this.parentBuilder != null ? this.parentBuilder : this;
}
public SectionBuilder br() {
curSec.addChild(new Section(Section.Type.BR, new MetaData(MetaData.Type.BR), parentSec, null,
curSec.getDepth()));
return this;
}
public String build() {
return parser.parse(curSec);
}
}
public static class TableDataBuilder {
private final Section parentSec;
private final SectionBuilder parentBuilder;
private Object[][] tableData;
private TableDataBuilder(Section parentSec, SectionBuilder parentBuilder) {
this.parentSec = parentSec;
this.parentBuilder = parentBuilder;
}
// ~ public methods
// -------------------------------------------------------------------------------------------------------------
public TableDataBuilder data(Object[][] table) {
if (table != null && table.length > 0 && table[0].length > 0) {
tableData = table;
}
return this;
}
public TableDataBuilder data(Object[] title, Object[][] data) {
if (title == null && data != null) {
return data(data);
}
if (data != null && data.length > 0 && data[0].length > 0) {
int minCol = Math.min(title.length, data[0].length);
tableData = new Object[data.length + 1][minCol];
tableData[0] = Arrays.copyOfRange(title, 0, minCol);
for (int i = 0; i < data.length; i++) {
tableData[i + 1] = Arrays.copyOfRange(data[i], 0, minCol);
}
}
return this;
}
public SectionBuilder endTable() {
parentSec.addChild(new Section(Section.Type.TABLE, tableData, parentSec, null, parentSec.getDepth()));
return parentBuilder;
}
}
private static class MdParser {
// ~ public methods
// -------------------------------------------------------------------------------------------------------------
public String parse(Section sec) {
Section root = findRoot(sec);
return doParse(root, root).toString().trim();
}
// ~ private methods
// -------------------------------------------------------------------------------------------------------------
private Section findRoot(Section sec) {
if (sec.getParent() == null) {
return sec;
}
return findRoot(sec.getParent());
}
private StringBuilder doParse(Section cur, Section root) {
if (cur == null) {
return null;
}
if (cur.childIsEmpty()) {
return cur.parse(new StringBuilder());
}
StringBuilder childData = new StringBuilder();
for (Section child : cur.getChildren()) {
StringBuilder part = doParse(child, root);
if (part != null) {
childData.append(part);
}
}
return cur.parse(childData).append(cur.getParent() == root ? '\n' : "");
}
}
}有了通用的markdown語法生成處理類,則可以根據(jù)項(xiàng)目要求,再次封裝需要生成的對(duì)應(yīng)的文檔中的各類元素對(duì)象,比如生成有序列表,無序列表,文檔首頁標(biāo)題,副標(biāo)題,鏈接,圖像,表格等
2. 所以再寫一個(gè)生成類,里邊附帶了測試方法
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 = "";
// 生成一個(gè)簡單的多元素的word文檔
JavaToMarkdownGenerator javaToMarkdownGenerator = new JavaToMarkdownGenerator();
// 首頁大標(biāo)題;注意每部分元素之間一定要換行,不然最后輸出到word文檔中的時(shí)候就會(huì)變成帶有markdown語法的文本了,
// 比如經(jīng)濟(jì)環(huán)境分析后邊沒加"\n\r",那么在word文檔中就會(huì)變成帶有#的目錄,會(huì)變成 "經(jīng)濟(jì)環(huán)境分析###目錄" 這樣子
String title = javaToMarkdownGenerator.generateTitle("經(jīng)濟(jì)環(huán)境分析")+"\n\r";
// 首頁目錄
String catalog = javaToMarkdownGenerator.generatecatalog()+"\n\r";
// 插入項(xiàng)目本地的logo圖片
// 注意企業(yè)項(xiàng)目中不要直接放在resources目錄下,因?yàn)楂@取的路徑放在markdown的圖片類型的語法中(最終是這樣:),
// 在轉(zhuǎn)換word時(shí)候還是無法通過路徑找到圖片,linux系統(tǒng)獲取文件需要通過流讀取,路徑的方式會(huì)提示找不到文件,windows系統(tǒng)不受影響
// 所以可以將需要插入到markdown中的圖片上傳到oss中,然后獲取oss的圖片的完整地址,網(wǎng)絡(luò)超鏈接的形式,再插入到markdown中,
// 這樣在轉(zhuǎn)換成word時(shí)候,會(huì)從網(wǎng)絡(luò)上下載,以流的方式插入到word中
String imgPath = JavaToMarkdownGenerator.class.getResource("/static/canton.jpg").getPath();
String logo = javaToMarkdownGenerator.resloveImg(imgPath, "logo")+"\n\r";
theWholeMarkdownContent = title + catalog + logo;
// 插入正文
List<List<String>> dataList = new ArrayList<>();
// java實(shí)踐中一般是一個(gè)java對(duì)象,從數(shù)據(jù)庫查詢出來的一個(gè)list集合,需要循環(huán)獲取對(duì)象,然后添加到dataList中
// 模擬數(shù)據(jù)庫中查詢出來數(shù)據(jù)
Employee employee = new Employee();
List<Employee> employeeList = employee.getEmployees();
if (CollectionUtils.isNotEmpty(employeeList)) {
for (Employee employee1 : employeeList) {
List<String> list = new ArrayList<>();
list.add(employee1.getName());
list.add(employee1.getSex());
list.add(employee1.getAge());
list.add(employee1.getHeight());
dataList.add(list);
}
String firstTable= javaToMarkdownGenerator.generateTable(dataList, "表格1","姓名", "性別", "芳齡", "身高");
theWholeMarkdownContent=theWholeMarkdownContent + firstTable;
}
// 直接拼接一段富文本,因?yàn)榫W(wǎng)頁上新增填寫內(nèi)容的時(shí)候,有些參數(shù)輸入是使用的markdown富文本編輯器
String markdownContent = "\n# 一級(jí)標(biāo)題\n" +
"## 二級(jí)標(biāo)題\n" +
"### 三級(jí)標(biāo)題\n" +
"#### 四級(jí)標(biāo)題\n" +
"##### 五級(jí)標(biāo)題\n" +
"###### 六級(jí)標(biāo)題\n" +
"## 段落\n" +
"這是一段普通的段落。\n" +
"## 列表\n" +
"### 無序列表\n" +
"- 項(xiàng)目1\n" +
"- 項(xiàng)目2\n" +
"- 項(xiàng)目3\n" +
"### 有序列表\n" +
"1. 項(xiàng)目1\n" +
"2. 項(xiàng)目2\n" +
"3. 項(xiàng)目3\n" +
"## 鏈接\n" +
"[百度](https://www.baidu.com)\n" +
"## 圖片\n" +
"\n" +
"## 表格\n" +
"| 表頭1 | 表頭2 | 表頭3 |\n" +
"|-------|-------|-------|\n" +
"| 單元格1 | 單元格2 | 單元格3 |\n" +
"| 單元格4 | 單元格5 | 單元格6 |";
theWholeMarkdownContent=theWholeMarkdownContent+markdownContent;
MarkdownUtil.toDoc(theWholeMarkdownContent,"test228");
System.out.println(catalog);
}
/**
* 使用markdown的有序列表實(shí)現(xiàn)生成目錄效果
* @return
*/
public String generatecatalog(){
String md = MarkdownHandler.of()
.subTitle("目錄")
// 不要加ref方法,可以正常生成markdown文本,但是在轉(zhuǎn)換成word內(nèi)容的時(shí)候,commonmark會(huì)報(bào)錯(cuò)
// org.commonmark.node.OrderedList cannot be cast to org.commonmark.node.Paragra
// .ol()
.text("文檔介紹")
// .endOl()
.ol()
.text("經(jīng)濟(jì)環(huán)境")
.ol()
.text("1.1 全球化背景")
.text("1.2 通縮問題產(chǎn)生的原因")
.text("1.3 如何應(yīng)對(duì)通縮")
.text("1.4 國家實(shí)施的財(cái)政政策和貨幣政策")
.endOl()
.endOl()
.ol()
.text("失業(yè)問題")
.ol()
.text("2.1 失業(yè)率的概念")
.text("2.2 如何統(tǒng)計(jì)失業(yè)率")
.text("2.3 如何提升就業(yè)率")
.endOl()
.endOl()
.ol()
.text("理財(cái)投資")
.ol()
.text("3.1 理財(cái)投資的重要性")
.text("3.2 如何選擇理財(cái)投資產(chǎn)品")
.text("3.3 理財(cái)投資的風(fēng)險(xiǎn)管理")
.endOl()
.endOl()
.build();
return md;
}
/**
* 生成表格
* @paramJataList
@paramtableHead
@return
**/
public String generateTable(List<List<String>> datalist, String tableTitle, String... tableHead){
// 添加表頭(表格第一行,列標(biāo)題)
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;
}
/**
* 文檔首頁大標(biāo)題效果
* @param title
* @return
*/
public String generateTitle(String title){
return MarkdownHandler.of().bigTitle(title).build();
}
/**
* 處理圖片
* @param imgPath
* @param imgName
* @return
*/
String resloveImg(String imgPath,String imgName){
return "";
}
}
@Data
class Employee{
private String name;
private String sex;
private String age;
// 身高
private String height;
// 體重
private String weight;
// 籍貫
private String nativePlace;
// 職位
private String position;
// 薪資
private String salary;
/**
* 模擬從數(shù)據(jù)庫中查出多條數(shù)據(jù)
* @return
*/
public List<Employee> getEmployees(){
List<Employee> employees = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Employee employee = new Employee();
employee.setName("張三" + i);
employee.setSex("男");
employee.setAge("18" + i);
employee.setHeight("180" + i);
employee.setWeight("70" + i);
employee.setNativePlace("北京" + i);
employee.setPosition("java開發(fā)" + i);
employee.setSalary("10000" + i);
employees.add(employee);
}
return employees;
}
}
測試main方法會(huì)調(diào)用MarkdownUtil,用于生成word文檔保存在本地,或者通過網(wǎng)絡(luò)進(jìn)行下載保存。
3.MarkdownUtil工具類:
package com.xiaomifeng1010.common.utils;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.data.style.*;
import com.deepoove.poi.plugin.markdown.MarkdownRenderData;
import com.deepoove.poi.plugin.markdown.MarkdownRenderPolicy;
import com.deepoove.poi.plugin.markdown.MarkdownStyle;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.core.io.ClassPathResource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* @author xiaomifeng1010
* @version 1.0
* @date: 2024-08-24 17:23
* @Description
*/
@UtilityClass
@Slf4j
public class MarkdownUtil {
/**
* markdown轉(zhuǎn)html
*
* @param markdownContent
* @return
*/
public String markdownToHtml(String markdownContent) {
Parser parser = Parser.builder().build();
Node document = parser.parse(markdownContent);
HtmlRenderer renderer = HtmlRenderer.builder().build();
String htmlContent = renderer.render(document);
log.info(htmlContent);
return htmlContent;
}
/**
* 將markdown格式內(nèi)容轉(zhuǎn)換為word并保存在本地
*
* @param markdownContent
* @param outputFileName
*/
public void toDoc(String markdownContent, String outputFileName) {
log.info("markdownContent:{}", markdownContent);
MarkdownRenderData code = new MarkdownRenderData();
code.setMarkdown(markdownContent);
MarkdownStyle style = MarkdownStyle.newStyle();
style = setMarkdownStyle(style);
code.setStyle(style);
// markdown樣式處理與word模板中的標(biāo)簽{{md}}綁定
Map<String, Object> data = new HashMap<>();
data.put("md", code);
Configure config = Configure.builder().bind("md", new MarkdownRenderPolicy()).build();
try {
// 獲取classpath
String path = MarkdownUtil.class.getClassLoader().getResource("").getPath();
log.info("classpath:{}", path);
//由于部署到linux上后,程序是從jar包中去讀取resources下的文件的,所以需要使用流的方式讀取,所以獲取流,而不是直接使用文件路徑
// 所以可以這樣獲取 InputStream resourceAsStream = MarkdownUtil.class.getClassLoader().getResourceAsStream("");
// 建議使用spring的工具類來獲取,如下
ClassPathResource resource = new ClassPathResource("markdown" + File.separator + "markdown_template.docx");
InputStream resourceAsStream = resource.getInputStream();
XWPFTemplate.compile(resourceAsStream, config)
.render(data)
.writeToFile(path + "out_markdown_" + outputFileName + ".docx");
} catch (IOException e) {
log.error("保存為word出錯(cuò)");
}
}
/**
* 將markdown轉(zhuǎn)換為word文檔并下載
*
* @param markdownContent
* @param response
* @param fileName
*/
public void convertAndDownloadWordDocument(String markdownContent, HttpServletResponse response, String fileName) {
log.info("markdownContent:{}", markdownContent);
MarkdownRenderData code = new MarkdownRenderData();
code.setMarkdown(markdownContent);
MarkdownStyle style = MarkdownStyle.newStyle();
style = setMarkdownStyle(style);
code.setStyle(style);
// markdown樣式處理與word模板中的標(biāo)簽{{md}}綁定
Map<String, Object> data = new HashMap<>();
data.put("md", code);
Configure configure = Configure.builder().bind("md", new MarkdownRenderPolicy()).build();
try {
fileName=URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
//由于部署到linux上后,程序是從jar包中去讀取resources下的文件的,所以需要使用流的方式讀取,所以獲取流,而不是直接使用文件路徑
// 所以可以這樣獲取 InputStream resourceAsStream = MarkdownUtil.class.getClassLoader().getResourceAsStream("");
// 建議使用spring的工具類來獲取,如下
ClassPathResource resource = new ClassPathResource("markdown" + File.separator + "markdown_template.docx");
InputStream resourceAsStream = resource.getInputStream();
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8") + ".docx");
// contentType不設(shè)置也是也可以的,可以正常解析到
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=utf-8");
XWPFTemplate template = XWPFTemplate.compile(resourceAsStream, configure)
.render(data);
template.writeAndClose(response.getOutputStream());
} catch (IOException e) {
log.error("下載word文檔失敗:{}", e.getMessage());
}
}
/**
* 設(shè)置轉(zhuǎn)換為word文檔時(shí)的基本樣式
* @param style
* @return
*/
public MarkdownStyle setMarkdownStyle(MarkdownStyle style) {
// 一定設(shè)置為false,不然生成的word文檔中各元素前邊都會(huì)加上有層級(jí)效果的一串?dāng)?shù)字,
// 比如一級(jí)標(biāo)題 前邊出現(xiàn)1 二級(jí)標(biāo)題出現(xiàn)1.1 三級(jí)標(biāo)題出現(xiàn)1.1.1這樣的數(shù)字
style.setShowHeaderNumber(false);
// 修改默認(rèn)的表格樣式
// table header style(表格頭部,通常為表格頂部第一行,用于設(shè)置列標(biāo)題)
RowStyle headerStyle = new RowStyle();
CellStyle cellStyle = new CellStyle();
// 設(shè)置表格頭部的背景色為灰色
cellStyle.setBackgroundColor("cccccc");
Style textStyle = new Style();
// 設(shè)置表格頭部的文字顏色為黑色
textStyle.setColor("000000");
// 頭部文字加粗
textStyle.setBold(true);
// 設(shè)置表格頭部文字大小為12
textStyle.setFontSize(12);
// 設(shè)置表格頭部文字垂直居中
cellStyle.setVertAlign(XWPFTableCell.XWPFVertAlign.CENTER);
cellStyle.setDefaultParagraphStyle(ParagraphStyle.builder().withDefaultTextStyle(textStyle).build());
headerStyle.setDefaultCellStyle(cellStyle);
style.setTableHeaderStyle(headerStyle);
// table border style(表格邊框樣式)
BorderStyle borderStyle = new BorderStyle();
// 設(shè)置表格邊框顏色為黑色
borderStyle.setColor("000000");
// 設(shè)置表格邊框?qū)挾葹?px
borderStyle.setSize(3);
// 設(shè)置表格邊框樣式為實(shí)線
borderStyle.setType(XWPFTable.XWPFBorderType.SINGLE);
style.setTableBorderStyle(borderStyle);
// 設(shè)置普通的引用文本樣式
ParagraphStyle quoteStyle = new ParagraphStyle();
// 設(shè)置段落樣式
quoteStyle.setSpacingBeforeLines(0.5d);
quoteStyle.setSpacingAfterLines(0.5d);
// 設(shè)置段落的文本樣式
Style quoteTextStyle = new Style();
quoteTextStyle.setColor("000000");
quoteTextStyle.setFontSize(8);
quoteTextStyle.setItalic(true);
quoteStyle.setDefaultTextStyle(quoteTextStyle);
style.setQuoteStyle(quoteStyle);
return style;
}
public static void main(String[] args) {
String markdownContent = "# 一級(jí)標(biāo)題\n" +
"## 二級(jí)標(biāo)題\n" +
"### 三級(jí)標(biāo)題\n" +
"#### 四級(jí)標(biāo)題\n" +
"##### 五級(jí)標(biāo)題\n" +
"###### 六級(jí)標(biāo)題\n" +
"## 段落\n" +
"這是一段普通的段落。\n" +
"## 列表\n" +
"### 無序列表\n" +
"- 項(xiàng)目1\n" +
"- 項(xiàng)目2\n" +
"- 項(xiàng)目3\n" +
"### 有序列表\n" +
"1. 項(xiàng)目1\n" +
"2. 項(xiàng)目2\n" +
"3. 項(xiàng)目3\n" +
"## 鏈接\n" +
"[百度](https://www.baidu.com)\n" +
"## 圖片\n" +
"\n" +
"## 表格\n" +
"| 表頭1 | 表頭2 | 表頭3 |\n" +
"|-------|-------|-------|\n" +
"| 單元格1 | 單元格2 | 單元格3 |\n" +
"| 單元格4 | 單元格5 | 單元格6 |";
toDoc(markdownContent, "test23");
}
}
4.最終的輸出效果

注意在生成有序列表或者無序列表時(shí)候不要加ref方法,因?yàn)榧恿藃ef方法雖然可以正常生成markdown文本,但是在轉(zhuǎn)換成word內(nèi)容的時(shí)候,commonmark會(huì)報(bào)錯(cuò)org.commonmark.node.OrderedList cannot be cast to org.commonmark.node.Paragra
還有一個(gè)注意事項(xiàng),就是換行不要直接調(diào)用br()方法,因?yàn)檗D(zhuǎn)換的時(shí)候,在word文檔中轉(zhuǎn)換不了,直接生成了“<br>” 這樣的文字,所以直接在markdown文本中使用"\n"來換行
總結(jié)
到此這篇關(guān)于Java生成Markdown格式內(nèi)容的文章就介紹到這了,更多相關(guān)Java生成Markdown格式內(nèi)容內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java 二進(jìn)制數(shù)據(jù)與16進(jìn)制字符串相互轉(zhuǎn)化方法
今天小編就為大家分享一篇java 二進(jìn)制數(shù)據(jù)與16進(jìn)制字符串相互轉(zhuǎn)化方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-07-07
詳解Java中String,StringBuffer和StringBuilder的使用
這篇文章主要為大家詳細(xì)介紹了Java中String,StringBuffer和StringBuilder三者的區(qū)別以及使用,文中的少了講解詳細(xì),感興趣的可以了解一下2022-07-07
Java并發(fā)工具之Exchanger線程間交換數(shù)據(jù)詳解
這篇文章主要介紹了Java并發(fā)工具之Exchanger線程間交換數(shù)據(jù)詳解,Exchanger是一個(gè)用于線程間協(xié)作的工具類,Exchanger用于進(jìn)行線程間的數(shù)據(jù)交 換,它提供一個(gè)同步點(diǎn),在這個(gè)同步點(diǎn),兩個(gè)線程可以交換彼此的數(shù)據(jù),需要的朋友可以參考下2023-12-12
java判斷兩個(gè)List<String>集合是否存在交集三種方法
這篇文章主要介紹了三種判斷Java中兩個(gè)List集合是否存在交集的方法,分別是使用retainAll方法、使用Stream和anyMatch以及使用Set提高性能,每種方法都有其適用場景和優(yōu)缺點(diǎn),需要的朋友可以參考下2025-03-03
Java中使用ForkJoinPool的實(shí)現(xiàn)示例
ForkJoinPool是一個(gè)功能強(qiáng)大的Java類,用于處理計(jì)算密集型任務(wù),本文主要介紹了Java中使用ForkJoinPool的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09

