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

<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进行优化

  1. 添加对应的坐标

    <jsoup.version>1.17.2</jsoup.version>
    
    <dependency>
        <groupId>org.jsoup</groupId>
        <artifactId>jsoup</artifactId>
        <version>${jsoup.version}</version>
    </dependency>
    
  2. 完善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;
            }
            // 图片(![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. 测试示例

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

posted @ 2025-11-04 20:27  张铁牛  阅读(45)  评论(0)    收藏  举报