1. markdown转word 第一步: markdown转html

1. 简介

最近因为项目需求需要将AI输出的结果导出到word中, 但AI输出的格式为markdown格式,因为word展示内容的时候需要有相应的格式(标题, 段落, 列表, 表格等), 所以不能直接将markdown输出到word中, 否则word中展示的就是markdown纯文本了, 调研一番后发现如果想要word展示效果好一点的话需要分成两步

  1. markdownhtml
  2. htmlooxml(Office Open XML) word内容,word元信息本身就是个xml)

所以本章先实现第一步 markdownhtml, 使用的组件为flexmark

2. 环境信息

为了兼容更多的场景, 所以并没有用一些高版本的SDK, 信息如下

复制代码
Java: 8
Flexmark: 0.60.2

3. Maven

xml 复制代码
<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

java 复制代码
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);
    }
}

测试结果如下:

xml 复制代码
<h2>嘉文四世</h2>
<blockquote>
<p>德玛西亚</p>
</blockquote>
<p><strong>给我找些更强的敌人!</strong></p>

5. 高级用法

5.1 启用Table扩展

flexmark 支持多种扩展,需要通过 Extension 注册, 比如启用表格语法, flexmark默认没有启用表格语法比如测试

java 复制代码
public static void main(String[] args) {
    String markdown = "| 列1   | 列2   |\n" + "| ----- | ----- |\n" + "| 数据1 | 数据2 |";
    final String html = convertMarkdownToHtml(markdown);
    System.out.println(html);
}

测试结果如下:

xml 复制代码
<p>| 列1   | 列2   |
| ----- | ----- |
| 数据1 | 数据2 |</p>

没有将表格转换为html table标签, 所以需要启用表格扩展, 如下:

java 复制代码
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);

测试结果如下:

xml 复制代码
<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属性, 如下:

java 复制代码
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();

测试如下内容:

java 复制代码
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);
}

测试结果如下:

xml 复制代码
<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进行优化

  1. 添加对应的坐标

    复制代码
    <jsoup.version>1.17.2</jsoup.version>
    
    <dependency>
        <groupId>org.jsoup</groupId>
        <artifactId>jsoup</artifactId>
        <version>${jsoup.version}</version>
    </dependency>
  2. 完善html结构

    java 复制代码
    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);
    }

    测试结果如下:

    xml 复制代码
    <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. 完整测试代码

java 复制代码
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, 我将其常用的方法封装成链式调用的工具类, 内容如下:

java 复制代码
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;
            }
            // 图片(![alt](url))
            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. 测试示例

java 复制代码
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. 小节

本章使用flexmarkmarkdown内容转换为html内容, 并介绍了其高级的配置功能和使用jsoup完善html结构,最后封装链式调用的工具类和对应的单元测试代码, 能够方便的将各种形式的markdown内容转换为html内容, 下一章将介绍将html转换为word内容

10. 源码

测试过程中的代码已全部上传至github, 欢迎点赞收藏 仓库地址: https://github.com/ludangxin/markdown2html

相关推荐
AlfredZhao12 天前
高效办公:用SQL*Loader轻松实现Excel数据入库
excel·csv·tools·sqlldr·sql*loader
张铁牛21 天前
2. markdown转word
tools
Kay_Liang1 个月前
大语言模型如何精准调用函数—— Function Calling 系统笔记
java·大数据·spring boot·笔记·ai·langchain·tools
IT·陈寒3 个月前
新手小白零基础搭建MCP教程
python·ai·tools·mcp
物与我皆无尽也4 个月前
Agent交互细节
java·llm·agent·tools·mcp·mcp server
素雪风华8 个月前
大模型LLMs框架Langchain之工具Tools
langchain·大模型·tools·llms·langchain工具包
X.Cristiano8 个月前
开源模型中的 Function Call 方案深度剖析
人工智能·function call·tools
背太阳的牧羊人8 个月前
pop_dialog_state(state: State)弹出对话栈并返回到主助手,让整个对话流程图可以明确追踪对话流,并将控制权委派给特定的子对话图。
python·agent·tools·langgraph
背太阳的牧羊人9 个月前
create_react_agent(model, tools) 和 graph_builder.add_conditional_edges 的联系和区别
人工智能·agent·react·tools·langgraph·聊天模型