1. markdown轉word 第一步: markdown轉html
1. 簡介
最近因為項目需求需要將AI輸出的結果導出到word中, 但AI輸出的格式為markdown格式,因為word展示內容的時候需要有相應的格式(標題, 段落, 列表, 表格等), 所以不能直接將markdown輸出到word中, 否則word中展示的就是markdown純文本了, 調研一番后發現如果想要word展示效果好一點的話需要分成兩步
- 將
markdown→html - 將
html→ooxml(Office Open XML) word內容,word元信息本身就是個xml)
所以本章先實現第一步 markdown → html, 使用的組件為flexmark
2. 環境信息
為了兼容更多的場景, 所以并沒有用一些高版本的SDK, 信息如下
Java: 8
Flexmark: 0.60.2
3. Maven
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ldx</groupId>
<artifactId>md2html</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<flexmark.version>0.60.2</flexmark.version>
</properties>
<dependencies>
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark</artifactId>
<version>${flexmark.version}</version>
</dependency>
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-ext-tables</artifactId>
<version>${flexmark.version}</version>
</dependency>
</dependencies>
</project>
4. Markdown轉Html
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.data.MutableDataSet;
public class MarkdownToHtml {
public static String convertMarkdownToHtml(String markdown) {
// 創建配置集
MutableDataSet options = new MutableDataSet();
// 創建解析器和渲染器
Parser parser = Parser.builder(options).build();
HtmlRenderer renderer = HtmlRenderer.builder(options).build();
// 解析 Markdown 文本
Node document = parser.parse(markdown);
// 渲染為 HTML
return renderer.render(document);
}
public static void main(String[] args) {
String markdown = "## 嘉文四世\n" + "\n" + "> 德瑪西亞\n" + "\n" + "**給我找些更強的敵人!**";
final String html = convertMarkdownToHtml(markdown);
System.out.println(html);
}
}
測試結果如下:
<h2>嘉文四世</h2>
<blockquote>
<p>德瑪西亞</p>
</blockquote>
<p><strong>給我找些更強的敵人!</strong></p>
5. 高級用法
5.1 啟用Table擴展
flexmark 支持多種擴展,需要通過 Extension 注冊, 比如啟用表格語法, flexmark默認沒有啟用表格語法比如測試
public static void main(String[] args) {
String markdown = "| 列1 | 列2 |\n" + "| ----- | ----- |\n" + "| 數據1 | 數據2 |";
final String html = convertMarkdownToHtml(markdown);
System.out.println(html);
}
測試結果如下:
<p>| 列1 | 列2 |
| ----- | ----- |
| 數據1 | 數據2 |</p>
沒有將表格轉換為html table標簽, 所以需要啟用表格擴展, 如下:
MutableDataSet options = new MutableDataSet();
// 啟用表格擴展,支持 Markdown 表格語法
options.set(Parser.EXTENSIONS, Collections.singletonList(TablesExtension.create()));
// 禁用跨列
options.set(TablesExtension.COLUMN_SPANS, false);
// 表頭固定為 1 行
options.set(TablesExtension.MIN_HEADER_ROWS, 1);
options.set(TablesExtension.MAX_HEADER_ROWS, 1);
// 自動補全缺失列、丟棄多余列
options.set(TablesExtension.APPEND_MISSING_COLUMNS, true);
options.set(TablesExtension.DISCARD_EXTRA_COLUMNS, true);
測試結果如下:
<table>
<thead>
<tr><th>列1</th><th>列2</th></tr>
</thead>
<tbody>
<tr><td>數據1</td><td>數據2</td></tr>
</tbody>
</table>
5.2 標簽屬性擴展
flexmark支持對標簽屬性的操作, 需要實現其AttributeProviderFactory類, 比如給對應標簽添加class屬性, 如下:
HtmlRenderer renderer = HtmlRenderer.builder(options)
.attributeProviderFactory(new IndependentAttributeProviderFactory() {
@Override
public @NotNull AttributeProvider apply(@NotNull LinkResolverContext context) {
return (node, part, attributes) -> {
// 標題
if (node instanceof Heading) {
Heading heading = (Heading) node;
attributes.addValue("class", "heading" + heading.getLevel());
}
// 正文
if (node instanceof Text) {
attributes.addValue("class", "Normal");
}
// 段落
if (node instanceof Paragraph) {
attributes.addValue("class", "paragraph");
}
// 無序列表
if (node instanceof BulletList) {
attributes.addValue("class", "bulletList");
}
// 有序列表
if (node instanceof OrderedList) {
attributes.addValue("class", "bulletList");
}
// 表格
if (node instanceof TableBlock) {
attributes.addValue("class", "tableBlock");
}
};
}
})
.build();
測試如下內容:
public static void main(String[] args) {
String markdown = "## 嘉文四世\n" + "\n" + "> 德瑪西亞\n" + "\n" + "**給我找些更強的敵人!**\n" + "\n" + "| 列1 | 列2 |\n" + "| ----- | ----- |\n" + "| 數據1 | 數據2 |";
final String html = convertMarkdownToHtml(markdown);
System.out.println(html);
}
測試結果如下:
<h2 class="heading2">嘉文四世</h2>
<blockquote>
<p class="paragraph">德瑪西亞</p>
</blockquote>
<p class="paragraph"><strong>給我找些更強的敵人!</strong></p>
<table class="tableBlock">
<thead>
<tr><th>列1</th><th>列2</th></tr>
</thead>
<tbody>
<tr><td>數據1</td><td>數據2</td></tr>
</tbody>
</table>
5.3 完善Html結構
上述的測試結果中輸出的都是markdown語句翻譯后的html代碼塊, 并不是一個完整的html頁面內容, 比如要將結果輸出成html文件并展示的話還需要html完整的骨架標簽如:<html><body>等, 這時候就需要使用jsoup進行優化
-
添加對應的坐標
<jsoup.version>1.17.2</jsoup.version> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>${jsoup.version}</version> </dependency> -
完善html結構
public static String wrapperHtml(String htmlContent) { org.jsoup.nodes.Document jsoupDoc = Jsoup.parse(htmlContent); jsoupDoc.outputSettings() // 內容輸出時遵循XML語法規則 .syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml) // 內容轉義時遵循xhtml規范 .escapeMode(Entities.EscapeMode.xhtml) // 禁用格式化輸出 .prettyPrint(false); return jsoupDoc.html(); } public static void main(String[] args) { String markdown = "## 嘉文四世\n" + "\n" + "> 德瑪西亞\n" + "\n" + "**給我找些更強的敵人!**\n" + "\n" + "| 列1 | 列2 |\n" + "| ----- | ----- |\n" + "| 數據1 | 數據2 |"; final String html = convertMarkdownToHtml(markdown); final String wrappedHtml = wrapperHtml(html); System.out.println(wrappedHtml); }測試結果如下:
<html><head></head><body><h2 class="heading2">嘉文四世</h2> <blockquote> <p class="paragraph">德瑪西亞</p> </blockquote> <p class="paragraph"><strong>給我找些更強的敵人!</strong></p> <table class="tableBlock"> <thead> <tr><th>列1</th><th>列2</th></tr> </thead> <tbody> <tr><td>數據1</td><td>數據2</td></tr> </tbody> </table> </body></html>
6. 完整測試代碼
package md2html;
import com.vladsch.flexmark.ast.BulletList;
import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.ast.OrderedList;
import com.vladsch.flexmark.ast.Paragraph;
import com.vladsch.flexmark.ast.Text;
import com.vladsch.flexmark.ext.tables.TableBlock;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.html.AttributeProvider;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.html.IndependentAttributeProviderFactory;
import com.vladsch.flexmark.html.renderer.LinkResolverContext;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.data.MutableDataSet;
import org.jetbrains.annotations.NotNull;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Entities;
import java.util.Collections;
public class MarkdownToHtml {
public static String convertMarkdownToHtml(String markdown) {
// 創建配置集
MutableDataSet options = new MutableDataSet();
// 啟用表格擴展,支持 Markdown 表格語法
options.set(Parser.EXTENSIONS, Collections.singletonList(TablesExtension.create()));
// 禁用跨列
options.set(TablesExtension.COLUMN_SPANS, false);
// 表頭固定為 1 行
options.set(TablesExtension.MIN_HEADER_ROWS, 1);
options.set(TablesExtension.MAX_HEADER_ROWS, 1);
// 自動補全缺失列、丟棄多余列
options.set(TablesExtension.APPEND_MISSING_COLUMNS, true);
options.set(TablesExtension.DISCARD_EXTRA_COLUMNS, true);
// 創建解析器和渲染器
Parser parser = Parser.builder(options)
.build();
HtmlRenderer renderer = HtmlRenderer.builder(options)
.attributeProviderFactory(new IndependentAttributeProviderFactory() {
@Override
public @NotNull AttributeProvider apply(@NotNull LinkResolverContext context) {
return (node, part, attributes) -> {
// 標題
if (node instanceof Heading) {
Heading heading = (Heading) node;
attributes.addValue("class", "heading" + heading.getLevel());
}
// 正文
if (node instanceof Text) {
attributes.addValue("class", "Normal");
}
// 段落
if (node instanceof Paragraph) {
attributes.addValue("class", "paragraph");
}
// 無序列表
if (node instanceof BulletList) {
attributes.addValue("class", "bulletList");
}
// 有序列表
if (node instanceof OrderedList) {
attributes.addValue("class", "bulletList");
}
// 表格
if (node instanceof TableBlock) {
attributes.addValue("class", "tableBlock");
}
};
}
})
.build();
// 解析 Markdown 文本
Node document = parser.parse(markdown);
// 渲染為 HTML
return renderer.render(document);
}
public static String wrapperHtml(String htmlContent) {
org.jsoup.nodes.Document jsoupDoc = Jsoup.parse(htmlContent);
jsoupDoc.outputSettings()
// 內容輸出時遵循XML語法規則
.syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml)
// 內容轉義時遵循xhtml規范
.escapeMode(Entities.EscapeMode.xhtml)
// 禁用格式化輸出
.prettyPrint(false);
return jsoupDoc.html();
}
public static void main(String[] args) {
String markdown = "## 嘉文四世\n" + "\n" + "> 德瑪西亞\n" + "\n" + "**給我找些更強的敵人!**\n" + "\n" + "| 列1 | 列2 |\n" + "| ----- | ----- |\n" + "| 數據1 | 數據2 |";
final String html = convertMarkdownToHtml(markdown);
final String wrappedHtml = wrapperHtml(html);
System.out.println(wrappedHtml);
}
}
7. 封裝工具類
為了更方便的使用flexmark, 我將其常用的方法封裝成鏈式調用的工具類, 內容如下:
import com.vladsch.flexmark.ast.BlockQuote;
import com.vladsch.flexmark.ast.BulletList;
import com.vladsch.flexmark.ast.Code;
import com.vladsch.flexmark.ast.Emphasis;
import com.vladsch.flexmark.ast.FencedCodeBlock;
import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.ast.Image;
import com.vladsch.flexmark.ast.IndentedCodeBlock;
import com.vladsch.flexmark.ast.Link;
import com.vladsch.flexmark.ast.ListItem;
import com.vladsch.flexmark.ast.OrderedList;
import com.vladsch.flexmark.ast.Paragraph;
import com.vladsch.flexmark.ast.StrongEmphasis;
import com.vladsch.flexmark.ast.ThematicBreak;
import com.vladsch.flexmark.ext.tables.TableBlock;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.html.AttributeProvider;
import com.vladsch.flexmark.html.AttributeProviderFactory;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.html.IndependentAttributeProviderFactory;
import com.vladsch.flexmark.html.renderer.LinkResolverContext;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.data.MutableDataSet;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Entities;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collections;
/**
* markdown 工具類
*
* @author ludangxin
* @since 2025/10/14
*/
@Slf4j
public class Markdowns {
public static MarkdownBuilder builder(InputStream inputStream, String charset) {
String markdownContent = readMarkdownContent(inputStream, charset);
return builder(markdownContent);
}
public static MarkdownBuilder builder(InputStream inputStream) {
String markdownContent = readMarkdownContent(inputStream);
return builder(markdownContent);
}
public static MarkdownBuilder builder(File file) {
String markdownContent = readMarkdownContent(file);
return builder(markdownContent);
}
public static MarkdownBuilder builder(String markdownContent) {
return new MarkdownBuilder().content(markdownContent);
}
public static String readMarkdownContent(File file) {
if (file == null || !file.exists()) {
return "";
}
try {
return readMarkdownContent(new FileReader(file));
}
catch (Exception e) {
log.error("failed to read markdown content", e);
}
return "";
}
public static String readMarkdownContent(InputStream inputStream) {
try {
return readMarkdownContent(new InputStreamReader(inputStream));
}
catch (Exception e) {
log.error("failed to read markdown content", e);
}
return "";
}
public static String readMarkdownContent(InputStream inputStream, String charset) {
if (charset == null || charset.isEmpty()) {
return readMarkdownContent(new InputStreamReader(inputStream));
}
try {
return readMarkdownContent(new InputStreamReader(inputStream, charset));
}
catch (Exception e) {
log.error("failed to read markdown content", e);
}
return "";
}
public static String readMarkdownContent(InputStreamReader inputStreamReader) {
try (BufferedReader reader = new BufferedReader(inputStreamReader)) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append(System.lineSeparator());
}
return sb.toString();
}
catch (IOException e) {
log.error("failed to read markdown content", e);
}
return "";
}
public static class MarkdownBuilder {
private String content;
private MutableDataSet options;
private AttributeProviderFactory attributeProviderFactory;
private AttributeProvider attributeProvider;
private MarkdownBuilder content(String content) {
this.content = content;
return this;
}
public MarkdownBuilder options(MutableDataSet options) {
this.options = options;
return this;
}
public MarkdownBuilder attributeProviderFactory(AttributeProviderFactory attributeProviderFactory) {
this.attributeProviderFactory = attributeProviderFactory;
return this;
}
public MarkdownBuilder attributeProvider(AttributeProvider attributeProvider) {
this.attributeProvider = attributeProvider;
return this;
}
public MarkdownBuilder printContent() {
System.out.println(content);
return this;
}
public boolean isMarkdown() {
if (content == null || content.trim()
.isEmpty()) {
return false;
}
final Document document = this.buildDocument();
return hasMarkdownNodes(document);
}
public Document buildDocument() {
Parser parser = Parser.builder(this.getOptionsOrDefault())
.build();
return parser.parse(content);
}
public String buildHtmlContent() {
return this.wrapperHtml(this.getHtmlRenderer()
.render(this.buildDocument()));
}
public String buildRawHtmlContent() {
return this.getHtmlRenderer()
.render(this.buildDocument());
}
public String buildRawHtmlIfMarkdown() {
if (this.isMarkdown()) {
return this.buildRawHtmlContent();
}
return content;
}
public String buildHtmlIfMarkdown() {
if (this.isMarkdown()) {
return this.buildHtmlContent();
}
return content;
}
private HtmlRenderer getHtmlRenderer() {
final HtmlRenderer.Builder builder = HtmlRenderer.builder(getOptionsOrDefault());
if (attributeProviderFactory != null) {
builder.attributeProviderFactory(attributeProviderFactory);
}
if (attributeProviderFactory == null && attributeProvider != null) {
final IndependentAttributeProviderFactory independentAttributeProviderFactory = new IndependentAttributeProviderFactory() {
@Override
public @NotNull AttributeProvider apply(@NotNull LinkResolverContext linkResolverContext) {
return attributeProvider;
}
};
builder.attributeProviderFactory(independentAttributeProviderFactory);
}
return builder.build();
}
private MutableDataSet getOptionsOrDefault() {
if (options == null) {
return this.defaultOptions();
}
else {
return options;
}
}
private MutableDataSet defaultOptions() {
MutableDataSet options = new MutableDataSet();
// 啟用表格擴展,支持 Markdown 表格語法
options.set(Parser.EXTENSIONS, Collections.singletonList(TablesExtension.create()));
// 禁用跨列
options.set(TablesExtension.COLUMN_SPANS, false);
// 表頭固定為 1 行
options.set(TablesExtension.MIN_HEADER_ROWS, 1);
options.set(TablesExtension.MAX_HEADER_ROWS, 1);
// 自動補全缺失列、丟棄多余列
options.set(TablesExtension.APPEND_MISSING_COLUMNS, true);
options.set(TablesExtension.DISCARD_EXTRA_COLUMNS, true);
return options;
}
private String wrapperHtml(String htmlContent) {
org.jsoup.nodes.Document jsoupDoc = Jsoup.parse(htmlContent);
jsoupDoc.outputSettings()
// 內容輸出時遵循XML語法規則
.syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml)
// 內容轉義時遵循xhtml規范
.escapeMode(Entities.EscapeMode.xhtml)
// 禁用格式化輸出
.prettyPrint(false);
return jsoupDoc.html();
}
/**
* 檢查 AST 中是否存在 Markdown 特有節點(非純文本段落)
*/
private static boolean hasMarkdownNodes(Node node) {
if (node == null) {
return false;
}
// 判斷當前節點是否為 Markdown 特有節點(非純文本)
if (isMarkdownSpecificNode(node)) {
return true;
}
// 遞歸檢查子節點
Node child = node.getFirstChild();
while (child != null) {
if (hasMarkdownNodes(child)) {
return true;
}
child = child.getNext();
}
return false;
}
/**
* 判定節點是否為 Markdown 特有節點(非純文本段落)
* 純文本段落(Paragraph)且無任何格式(如鏈接、粗體等)則視為非 Markdown
*/
private static boolean isMarkdownSpecificNode(Node node) {
// 標題(# 標題)
if (node instanceof Heading) {
return true;
}
// 列表(有序/無序)
if (node instanceof BulletList || node instanceof OrderedList) {
return true;
}
// 列表項
if (node instanceof ListItem) {
return true;
}
// 鏈接([文本](url))
if (node instanceof Link) {
return true;
}
// 圖片()
if (node instanceof Image) {
return true;
}
// 粗體(**文本** 或 __文本__)
if (node instanceof StrongEmphasis) {
return true;
}
// 斜體(*文本* 或 _文本_)
if (node instanceof Emphasis) {
return true;
}
// 代碼塊(```代碼```)
if (node instanceof FencedCodeBlock || node instanceof IndentedCodeBlock) {
return true;
}
// 表格(| 表頭 | ... |)
if (node instanceof TableBlock) {
return true;
}
// 引用(> 引用內容)
if (node instanceof BlockQuote) {
return true;
}
// 水平線(--- 或 ***)
if (node instanceof ThematicBreak) {
return true;
}
// 段落節點需進一步檢查是否包含 inline 格式(如粗體、鏈接等)
if (node instanceof Paragraph) {
return hasInlineMarkdownNodes(node);
}
// 其他節點(如文本節點)視為非特有
return false;
}
/**
* 檢查段落中是否包含 inline 格式(如粗體、鏈接等)
*/
private static boolean hasInlineMarkdownNodes(Node paragraph) {
Node child = paragraph.getFirstChild();
while (child != null) {
// 若段落中包含任何 Markdown inline 節點,則視為 Markdown
if (child instanceof Link || child instanceof Image || child instanceof StrongEmphasis || child instanceof Emphasis || child instanceof Code) {
return true;
}
child = child.getNext();
}
return false;
}
}
}
8. 測試示例
import com.vladsch.flexmark.ast.BulletList;
import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.ast.OrderedList;
import com.vladsch.flexmark.ast.Paragraph;
import com.vladsch.flexmark.ast.Text;
import com.vladsch.flexmark.ext.tables.TableBlock;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* 測試工具類
*
* @author ludangxin
* @since 2025/10/14
*/
@Slf4j
public class Md2htmlTest {
@Test
public void given_md_str_then_print_complete_html() {
final String html = Markdowns.builder("# 簡介 \n hello world~")
// 打印md內容
.printContent()
// 構建html內容, 自動完善html結構
.buildHtmlContent();
log.info(html);
// # 簡介
// hello world~
//[main] INFO md2html.Md2htmlTest -- <html><head></head><body><h1>簡介</h1>
//<p>hello world~</p>
//</body></html>
}
@Test
public void given_md_str_then_print_raw_html() {
final String html = Markdowns.builder("# 簡介 \n hello world~")
// 構建raw html內容
.buildRawHtmlContent();
log.info(html);
//[main] INFO md2html.Md2htmlTest -- <h1>簡介</h1>
//<p>hello world~</p>
}
@Test
public void given_md_file_then_print_raw_html() {
final String html = Markdowns.builder(new File("src/test/resources/test.md"))
// 構建raw html內容
.buildRawHtmlContent();
log.info(html);
//[main] INFO md2html.Md2htmlTest -- <h2>嘉文四世</h2>
//<blockquote>
//<p>德瑪西亞</p>
//</blockquote>
//<p><strong>給我找些更強的敵人!</strong></p>
//<table>
//<thead>
//<tr><th>列1</th><th>列2</th></tr>
//</thead>
//<tbody>
//<tr><td>數據1</td><td>數據2</td></tr>
//</tbody>
//</table>
}
@Test
@SneakyThrows
public void given_md_stream_then_print_complete_html() {
final InputStream fileInputStream = Files.newInputStream(Paths.get("src/test/resources/test.md"));
final String html = Markdowns.builder(fileInputStream)
// 構建html內容
.buildHtmlContent();
log.info(html);
//[main] INFO md2html.Md2htmlTest -- <html><head></head><body><h2>嘉文四世</h2>
//<blockquote>
//<p>德瑪西亞</p>
//</blockquote>
//<p><strong>給我找些更強的敵人!</strong></p>
//<table>
//<thead>
//<tr><th>列1</th><th>列2</th></tr>
//</thead>
//<tbody>
//<tr><td>數據1</td><td>數據2</td></tr>
//</tbody>
//</table>
//</body></html>
}
@Test
public void given_non_md_content_then_print_complete_html() {
// 輸入非markdown語法的內容
final String html = Markdowns.builder("hello world~")
// 構建html內容 (如果內容是md語法則轉換為html, 如不不是 則原樣輸出)
.buildHtmlIfMarkdown();
// 輸入非markdown語法的內容
final String html2 = Markdowns.builder("## hello world~")
// 構建html內容 (如果內容是md語法則轉換為html, 如不不是 則原樣輸出)
.buildHtmlIfMarkdown();
log.info(html);
//[main] INFO md2html.Md2htmlTest -- hello world~
log.info(html2);
//[main] INFO md2html.Md2htmlTest -- <html><head></head><body><h2>hello world~</h2>
}
@Test
@SneakyThrows
public void given_md_stream_and_attr_provider_then_print_raw_html() {
final InputStream fileInputStream = Files.newInputStream(Paths.get("src/test/resources/test.md"));
final String html = Markdowns.builder(fileInputStream)
.attributeProvider((node, attributablePart, attributes) -> {
// 標題
if (node instanceof Heading) {
Heading heading = (Heading) node;
attributes.addValue("class", "heading" + heading.getLevel());
}
// 正文
if (node instanceof Text) {
attributes.addValue("class", "Normal");
}
// 段落
if (node instanceof Paragraph) {
attributes.addValue("class", "paragraph");
}
// 無序列表
if (node instanceof BulletList) {
attributes.addValue("class", "bulletList");
}
// 有序列表
if (node instanceof OrderedList) {
attributes.addValue("class", "bulletList");
}
// 表格
if (node instanceof TableBlock) {
attributes.addValue("class", "tableBlock");
}
})
.buildRawHtmlContent();
log.info(html);
//[main] INFO md2html.Md2htmlTest -- <h2 class="heading2">嘉文四世</h2>
//<blockquote>
//<p class="paragraph">德瑪西亞</p>
//</blockquote>
//<p class="paragraph"><strong>給我找些更強的敵人!</strong></p>
//<table class="tableBlock">
//<thead>
//<tr><th>列1</th><th>列2</th></tr>
//</thead>
//<tbody>
//<tr><td>數據1</td><td>數據2</td></tr>
//</tbody>
//</table>
}
}
9. 小節
本章使用flexmark將markdown內容轉換為html內容, 并介紹了其高級的配置功能和使用jsoup完善html結構,最后封裝鏈式調用的工具類和對應的單元測試代碼, 能夠方便的將各種形式的markdown內容轉換為html內容, 下一章將介紹將html轉換為word內容
10. 源碼
測試過程中的代碼已全部上傳至github, 歡迎點贊收藏 倉庫地址: https://github.com/ludangxin/markdown2html

浙公網安備 33010602011771號