找回密码
 立即注册
首页 业界区 业界 1. markdown转word 第一步: markdown转html

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

梢疠 昨天 20:40
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, 信息如下
  1. Java: 8
  2. Flexmark: 0.60.2
复制代码
3. Maven
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3.     <modelVersion>4.0.0</modelVersion>
  4.     <groupId>com.ldx</groupId>
  5.     md2html</artifactId>
  6.     <version>1.0-SNAPSHOT</version>
  7.     <properties>
  8.         <flexmark.version>0.60.2</flexmark.version>
  9.     </properties>
  10.     <dependencies>
  11.         <dependency>
  12.             <groupId>com.vladsch.flexmark</groupId>
  13.             flexmark</artifactId>
  14.             <version>${flexmark.version}</version>
  15.         </dependency>
  16.         <dependency>
  17.             <groupId>com.vladsch.flexmark</groupId>
  18.             flexmark-ext-tables</artifactId>
  19.             <version>${flexmark.version}</version>
  20.         </dependency>
  21.     </dependencies>
  22. </project>
复制代码
4. Markdown转Html
  1. import com.vladsch.flexmark.html.HtmlRenderer;
  2. import com.vladsch.flexmark.parser.Parser;
  3. import com.vladsch.flexmark.util.ast.Node;
  4. import com.vladsch.flexmark.util.data.MutableDataSet;
  5. public class MarkdownToHtml {
  6.     public static String convertMarkdownToHtml(String markdown) {
  7.         // 创建配置集
  8.         MutableDataSet options = new MutableDataSet();
  9.         // 创建解析器和渲染器
  10.         Parser parser = Parser.builder(options).build();
  11.         HtmlRenderer renderer = HtmlRenderer.builder(options).build();
  12.         // 解析 Markdown 文本
  13.         Node document = parser.parse(markdown);
  14.         // 渲染为 HTML
  15.         return renderer.render(document);
  16.     }
  17.     public static void main(String[] args) {
  18.         String markdown = "## 嘉文四世\n" + "\n" + "> 德玛西亚\n" + "\n" + "**给我找些更强的敌人!**";
  19.         final String html = convertMarkdownToHtml(markdown);
  20.         System.out.println(html);
  21.     }
  22. }
复制代码
测试结果如下:
  1. <h2>嘉文四世</h2>
  2. <blockquote>
  3. <p>德玛西亚</p>
  4. </blockquote>
  5. <p><strong>给我找些更强的敌人!</strong></p>
复制代码
5. 高级用法

5.1 启用Table扩展

flexmark 支持多种扩展,需要通过 Extension 注册, 比如启用表格语法, flexmark默认没有启用表格语法比如测试
  1. public static void main(String[] args) {
  2.     String markdown = "| 列1   | 列2   |\n" + "| ----- | ----- |\n" + "| 数据1 | 数据2 |";
  3.     final String html = convertMarkdownToHtml(markdown);
  4.     System.out.println(html);
  5. }
复制代码
测试结果如下:
  1. <p>| 列1   | 列2   |
  2. | ----- | ----- |
  3. | 数据1 | 数据2 |</p>
复制代码
没有将表格转换为html table标签, 所以需要启用表格扩展, 如下:
  1. MutableDataSet options = new MutableDataSet();
  2. // 启用表格扩展,支持 Markdown 表格语法
  3. options.set(Parser.EXTENSIONS, Collections.singletonList(TablesExtension.create()));
  4. // 禁用跨列
  5. options.set(TablesExtension.COLUMN_SPANS, false);
  6. // 表头固定为 1 行
  7. options.set(TablesExtension.MIN_HEADER_ROWS, 1);
  8. options.set(TablesExtension.MAX_HEADER_ROWS, 1);
  9. // 自动补全缺失列、丢弃多余列
  10. options.set(TablesExtension.APPEND_MISSING_COLUMNS, true);
  11. options.set(TablesExtension.DISCARD_EXTRA_COLUMNS, true);
复制代码
测试结果如下:
  1. <table>
  2. <thead>
  3. <tr><th>列1</th><th>列2</th></tr>
  4. </thead>
  5. <tbody>
  6. <tr><td>数据1</td><td>数据2</td></tr>
  7. </tbody>
  8. </table>
复制代码
5.2 标签属性扩展

flexmark支持对标签属性的操作, 需要实现其AttributeProviderFactory类, 比如给对应标签添加class属性, 如下:
  1. HtmlRenderer renderer = HtmlRenderer.builder(options)
  2.   .attributeProviderFactory(new IndependentAttributeProviderFactory() {
  3.     @Override
  4.     public @NotNull AttributeProvider apply(@NotNull LinkResolverContext context) {
  5.       return (node, part, attributes) -> {
  6.         // 标题
  7.         if (node instanceof Heading) {
  8.           Heading heading = (Heading) node;
  9.           attributes.addValue("class", "heading" + heading.getLevel());
  10.         }
  11.         // 正文
  12.         if (node instanceof Text) {
  13.           attributes.addValue("class", "Normal");
  14.         }
  15.         // 段落
  16.         if (node instanceof Paragraph) {
  17.           attributes.addValue("class", "paragraph");
  18.         }
  19.         // 无序列表
  20.         if (node instanceof BulletList) {
  21.           attributes.addValue("class", "bulletList");
  22.         }
  23.         // 有序列表
  24.         if (node instanceof OrderedList) {
  25.           attributes.addValue("class", "bulletList");
  26.         }
  27.         // 表格
  28.         if (node instanceof TableBlock) {
  29.           attributes.addValue("class", "tableBlock");
  30.         }
  31.       };
  32.     }
  33.   })
  34.   .build();
复制代码
测试如下内容:
  1. public static void main(String[] args) {
  2.     String markdown = "## 嘉文四世\n" + "\n" + "> 德玛西亚\n" + "\n" + "**给我找些更强的敌人!**\n" + "\n" + "| 列1   | 列2   |\n" + "| ----- | ----- |\n" + "| 数据1 | 数据2 |";
  3.     final String html = convertMarkdownToHtml(markdown);
  4.     System.out.println(html);
  5. }
复制代码
测试结果如下:
  1. <h2>嘉文四世</h2>
  2. <blockquote>
  3. <p>德玛西亚</p>
  4. </blockquote>
  5. <p><strong>给我找些更强的敌人!</strong></p><table>
  6. <thead>
  7. <tr><th>列1</th><th>列2</th></tr>
  8. </thead>
  9. <tbody>
  10. <tr><td>数据1</td><td>数据2</td></tr>
  11. </tbody>
  12. </table>
复制代码
5.3 完善Html结构

上述的测试结果中输出的都是markdown语句翻译后的html代码块, 并不是一个完整的html页面内容, 比如要将结果输出成html文件并展示的话还需要html完整的骨架标签如:等, 这时候就需要使用jsoup进行优化

  • 添加对应的坐标
    1. <jsoup.version>1.17.2</jsoup.version>
    2. <dependency>
    3.     <groupId>org.jsoup</groupId>
    4.     jsoup</artifactId>
    5.     <version>${jsoup.version}</version>
    6. </dependency>
    复制代码
  • 完善html结构
    1. public static String wrapperHtml(String htmlContent) {
    2.     org.jsoup.nodes.Document jsoupDoc = Jsoup.parse(htmlContent);
    3.     jsoupDoc.outputSettings()
    4.         // 内容输出时遵循XML语法规则
    5.         .syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml)
    6.         // 内容转义时遵循xhtml规范
    7.         .escapeMode(Entities.EscapeMode.xhtml)
    8.         // 禁用格式化输出
    9.         .prettyPrint(false);
    10.     return jsoupDoc.html();
    11. }
    12. public static void main(String[] args) {
    13.     String markdown = "## 嘉文四世\n" + "\n" + "> 德玛西亚\n" + "\n" + "**给我找些更强的敌人!**\n" + "\n" + "| 列1   | 列2   |\n" + "| ----- | ----- |\n" + "| 数据1 | 数据2 |";
    14.     final String html = convertMarkdownToHtml(markdown);
    15.     final String wrappedHtml = wrapperHtml(html);
    16.     System.out.println(wrappedHtml);
    17. }
    复制代码
    测试结果如下:
    1. <h2>嘉文四世</h2>
    2. <blockquote>
    3. <p>德玛西亚</p>
    4. </blockquote>
    5. <p><strong>给我找些更强的敌人!</strong></p><table>
    6. <thead>
    7. <tr><th>列1</th><th>列2</th></tr>
    8. </thead>
    9. <tbody>
    10. <tr><td>数据1</td><td>数据2</td></tr>
    11. </tbody>
    12. </table>
    复制代码
6. 完整测试代码
  1. package md2html;
  2. import com.vladsch.flexmark.ast.BulletList;
  3. import com.vladsch.flexmark.ast.Heading;
  4. import com.vladsch.flexmark.ast.OrderedList;
  5. import com.vladsch.flexmark.ast.Paragraph;
  6. import com.vladsch.flexmark.ast.Text;
  7. import com.vladsch.flexmark.ext.tables.TableBlock;
  8. import com.vladsch.flexmark.ext.tables.TablesExtension;
  9. import com.vladsch.flexmark.html.AttributeProvider;
  10. import com.vladsch.flexmark.html.HtmlRenderer;
  11. import com.vladsch.flexmark.html.IndependentAttributeProviderFactory;
  12. import com.vladsch.flexmark.html.renderer.LinkResolverContext;
  13. import com.vladsch.flexmark.parser.Parser;
  14. import com.vladsch.flexmark.util.ast.Node;
  15. import com.vladsch.flexmark.util.data.MutableDataSet;
  16. import org.jetbrains.annotations.NotNull;
  17. import org.jsoup.Jsoup;
  18. import org.jsoup.nodes.Entities;
  19. import java.util.Collections;
  20. public class MarkdownToHtml {
  21.     public static String convertMarkdownToHtml(String markdown) {
  22.         // 创建配置集
  23.         MutableDataSet options = new MutableDataSet();
  24.         // 启用表格扩展,支持 Markdown 表格语法
  25.         options.set(Parser.EXTENSIONS, Collections.singletonList(TablesExtension.create()));
  26.         // 禁用跨列
  27.         options.set(TablesExtension.COLUMN_SPANS, false);
  28.         // 表头固定为 1 行
  29.         options.set(TablesExtension.MIN_HEADER_ROWS, 1);
  30.         options.set(TablesExtension.MAX_HEADER_ROWS, 1);
  31.         // 自动补全缺失列、丢弃多余列
  32.         options.set(TablesExtension.APPEND_MISSING_COLUMNS, true);
  33.         options.set(TablesExtension.DISCARD_EXTRA_COLUMNS, true);
  34.         // 创建解析器和渲染器
  35.         Parser parser = Parser.builder(options)
  36.                               .build();
  37.         HtmlRenderer renderer = HtmlRenderer.builder(options)
  38.                                             .attributeProviderFactory(new IndependentAttributeProviderFactory() {
  39.                                                 @Override
  40.                                                 public @NotNull AttributeProvider apply(@NotNull LinkResolverContext context) {
  41.                                                     return (node, part, attributes) -> {
  42.                                                         // 标题
  43.                                                         if (node instanceof Heading) {
  44.                                                             Heading heading = (Heading) node;
  45.                                                             attributes.addValue("class", "heading" + heading.getLevel());
  46.                                                         }
  47.                                                         // 正文
  48.                                                         if (node instanceof Text) {
  49.                                                             attributes.addValue("class", "Normal");
  50.                                                         }
  51.                                                         // 段落
  52.                                                         if (node instanceof Paragraph) {
  53.                                                             attributes.addValue("class", "paragraph");
  54.                                                         }
  55.                                                         // 无序列表
  56.                                                         if (node instanceof BulletList) {
  57.                                                             attributes.addValue("class", "bulletList");
  58.                                                         }
  59.                                                         // 有序列表
  60.                                                         if (node instanceof OrderedList) {
  61.                                                             attributes.addValue("class", "bulletList");
  62.                                                         }
  63.                                                         // 表格
  64.                                                         if (node instanceof TableBlock) {
  65.                                                             attributes.addValue("class", "tableBlock");
  66.                                                         }
  67.                                                     };
  68.                                                 }
  69.                                             })
  70.                                             .build();
  71.         // 解析 Markdown 文本
  72.         Node document = parser.parse(markdown);
  73.         // 渲染为 HTML
  74.         return renderer.render(document);
  75.     }
  76.     public static String wrapperHtml(String htmlContent) {
  77.         org.jsoup.nodes.Document jsoupDoc = Jsoup.parse(htmlContent);
  78.         jsoupDoc.outputSettings()
  79.                 // 内容输出时遵循XML语法规则
  80.                 .syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml)
  81.                 // 内容转义时遵循xhtml规范
  82.                 .escapeMode(Entities.EscapeMode.xhtml)
  83.                 // 禁用格式化输出
  84.                 .prettyPrint(false);
  85.         return jsoupDoc.html();
  86.     }
  87.     public static void main(String[] args) {
  88.         String markdown = "## 嘉文四世\n" + "\n" + "> 德玛西亚\n" + "\n" + "**给我找些更强的敌人!**\n" + "\n" + "| 列1   | 列2   |\n" + "| ----- | ----- |\n" + "| 数据1 | 数据2 |";
  89.         final String html = convertMarkdownToHtml(markdown);
  90.         final String wrappedHtml = wrapperHtml(html);
  91.         System.out.println(wrappedHtml);
  92.     }
  93. }
复制代码
7. 封装工具类

为了更方便的使用flexmark, 我将其常用的方法封装成链式调用的工具类, 内容如下:
  1. import com.vladsch.flexmark.ast.BlockQuote;
  2. import com.vladsch.flexmark.ast.BulletList;
  3. import com.vladsch.flexmark.ast.Code;
  4. import com.vladsch.flexmark.ast.Emphasis;
  5. import com.vladsch.flexmark.ast.FencedCodeBlock;
  6. import com.vladsch.flexmark.ast.Heading;
  7. import com.vladsch.flexmark.ast.Image;
  8. import com.vladsch.flexmark.ast.IndentedCodeBlock;
  9. import com.vladsch.flexmark.ast.Link;
  10. import com.vladsch.flexmark.ast.ListItem;
  11. import com.vladsch.flexmark.ast.OrderedList;
  12. import com.vladsch.flexmark.ast.Paragraph;
  13. import com.vladsch.flexmark.ast.StrongEmphasis;
  14. import com.vladsch.flexmark.ast.ThematicBreak;
  15. import com.vladsch.flexmark.ext.tables.TableBlock;
  16. import com.vladsch.flexmark.ext.tables.TablesExtension;
  17. import com.vladsch.flexmark.html.AttributeProvider;
  18. import com.vladsch.flexmark.html.AttributeProviderFactory;
  19. import com.vladsch.flexmark.html.HtmlRenderer;
  20. import com.vladsch.flexmark.html.IndependentAttributeProviderFactory;
  21. import com.vladsch.flexmark.html.renderer.LinkResolverContext;
  22. import com.vladsch.flexmark.parser.Parser;
  23. import com.vladsch.flexmark.util.ast.Document;
  24. import com.vladsch.flexmark.util.ast.Node;
  25. import com.vladsch.flexmark.util.data.MutableDataSet;
  26. import lombok.extern.slf4j.Slf4j;
  27. import org.jetbrains.annotations.NotNull;
  28. import org.jsoup.Jsoup;
  29. import org.jsoup.nodes.Entities;
  30. import java.io.BufferedReader;
  31. import java.io.File;
  32. import java.io.FileReader;
  33. import java.io.IOException;
  34. import java.io.InputStream;
  35. import java.io.InputStreamReader;
  36. import java.util.Collections;
  37. /**
  38. * markdown 工具类
  39. *
  40. * @author ludangxin
  41. * @since 2025/10/14
  42. */
  43. @Slf4j
  44. public class Markdowns {
  45.     public static MarkdownBuilder builder(InputStream inputStream, String charset) {
  46.         String markdownContent = readMarkdownContent(inputStream, charset);
  47.         return builder(markdownContent);
  48.     }
  49.     public static MarkdownBuilder builder(InputStream inputStream) {
  50.         String markdownContent = readMarkdownContent(inputStream);
  51.         return builder(markdownContent);
  52.     }
  53.     public static MarkdownBuilder builder(File file) {
  54.         String markdownContent = readMarkdownContent(file);
  55.         return builder(markdownContent);
  56.     }
  57.     public static MarkdownBuilder builder(String markdownContent) {
  58.         return new MarkdownBuilder().content(markdownContent);
  59.     }
  60.     public static String readMarkdownContent(File file) {
  61.         if (file == null || !file.exists()) {
  62.             return "";
  63.         }
  64.         try {
  65.             return readMarkdownContent(new FileReader(file));
  66.         }
  67.         catch (Exception e) {
  68.             log.error("failed to read markdown content", e);
  69.         }
  70.         return "";
  71.     }
  72.     public static String readMarkdownContent(InputStream inputStream) {
  73.         try {
  74.             return readMarkdownContent(new InputStreamReader(inputStream));
  75.         }
  76.         catch (Exception e) {
  77.             log.error("failed to read markdown content", e);
  78.         }
  79.         return "";
  80.     }
  81.     public static String readMarkdownContent(InputStream inputStream, String charset) {
  82.         if (charset == null || charset.isEmpty()) {
  83.             return readMarkdownContent(new InputStreamReader(inputStream));
  84.         }
  85.         try {
  86.             return readMarkdownContent(new InputStreamReader(inputStream, charset));
  87.         }
  88.         catch (Exception e) {
  89.             log.error("failed to read markdown content", e);
  90.         }
  91.         return "";
  92.     }
  93.     public static String readMarkdownContent(InputStreamReader inputStreamReader) {
  94.         try (BufferedReader reader = new BufferedReader(inputStreamReader)) {
  95.             StringBuilder sb = new StringBuilder();
  96.             String line;
  97.             while ((line = reader.readLine()) != null) {
  98.                 sb.append(line);
  99.                 sb.append(System.lineSeparator());
  100.             }
  101.             return sb.toString();
  102.         }
  103.         catch (IOException e) {
  104.             log.error("failed to read markdown content", e);
  105.         }
  106.         return "";
  107.     }
  108.     public static class MarkdownBuilder {
  109.         private String content;
  110.         private MutableDataSet options;
  111.         private AttributeProviderFactory attributeProviderFactory;
  112.         private AttributeProvider attributeProvider;
  113.         private MarkdownBuilder content(String content) {
  114.             this.content = content;
  115.             return this;
  116.         }
  117.         public MarkdownBuilder options(MutableDataSet options) {
  118.             this.options = options;
  119.             return this;
  120.         }
  121.         public MarkdownBuilder attributeProviderFactory(AttributeProviderFactory attributeProviderFactory) {
  122.             this.attributeProviderFactory = attributeProviderFactory;
  123.             return this;
  124.         }
  125.         public MarkdownBuilder attributeProvider(AttributeProvider attributeProvider) {
  126.             this.attributeProvider = attributeProvider;
  127.             return this;
  128.         }
  129.         public MarkdownBuilder printContent() {
  130.             System.out.println(content);
  131.             return this;
  132.         }
  133.         public boolean isMarkdown() {
  134.             if (content == null || content.trim()
  135.                                           .isEmpty()) {
  136.                 return false;
  137.             }
  138.             final Document document = this.buildDocument();
  139.             return hasMarkdownNodes(document);
  140.         }
  141.         public Document buildDocument() {
  142.             Parser parser = Parser.builder(this.getOptionsOrDefault())
  143.                                   .build();
  144.             return parser.parse(content);
  145.         }
  146.         public String buildHtmlContent() {
  147.             return this.wrapperHtml(this.getHtmlRenderer()
  148.                                         .render(this.buildDocument()));
  149.         }
  150.         public String buildRawHtmlContent() {
  151.             return this.getHtmlRenderer()
  152.                        .render(this.buildDocument());
  153.         }
  154.         public String buildRawHtmlIfMarkdown() {
  155.             if (this.isMarkdown()) {
  156.                 return this.buildRawHtmlContent();
  157.             }
  158.             return content;
  159.         }
  160.         public String buildHtmlIfMarkdown() {
  161.             if (this.isMarkdown()) {
  162.                 return this.buildHtmlContent();
  163.             }
  164.             return content;
  165.         }
  166.         private HtmlRenderer getHtmlRenderer() {
  167.             final HtmlRenderer.Builder builder = HtmlRenderer.builder(getOptionsOrDefault());
  168.             if (attributeProviderFactory != null) {
  169.                 builder.attributeProviderFactory(attributeProviderFactory);
  170.             }
  171.             if (attributeProviderFactory == null && attributeProvider != null) {
  172.                 final IndependentAttributeProviderFactory independentAttributeProviderFactory = new IndependentAttributeProviderFactory() {
  173.                     @Override
  174.                     public @NotNull AttributeProvider apply(@NotNull LinkResolverContext linkResolverContext) {
  175.                         return attributeProvider;
  176.                     }
  177.                 };
  178.                 builder.attributeProviderFactory(independentAttributeProviderFactory);
  179.             }
  180.             return builder.build();
  181.         }
  182.         private MutableDataSet getOptionsOrDefault() {
  183.             if (options == null) {
  184.                 return this.defaultOptions();
  185.             }
  186.             else {
  187.                 return options;
  188.             }
  189.         }
  190.         private MutableDataSet defaultOptions() {
  191.             MutableDataSet options = new MutableDataSet();
  192.             // 启用表格扩展,支持 Markdown 表格语法
  193.             options.set(Parser.EXTENSIONS, Collections.singletonList(TablesExtension.create()));
  194.             // 禁用跨列
  195.             options.set(TablesExtension.COLUMN_SPANS, false);
  196.             // 表头固定为 1 行
  197.             options.set(TablesExtension.MIN_HEADER_ROWS, 1);
  198.             options.set(TablesExtension.MAX_HEADER_ROWS, 1);
  199.             // 自动补全缺失列、丢弃多余列
  200.             options.set(TablesExtension.APPEND_MISSING_COLUMNS, true);
  201.             options.set(TablesExtension.DISCARD_EXTRA_COLUMNS, true);
  202.             return options;
  203.         }
  204.         private String wrapperHtml(String htmlContent) {
  205.             org.jsoup.nodes.Document jsoupDoc = Jsoup.parse(htmlContent);
  206.             jsoupDoc.outputSettings()
  207.                     // 内容输出时遵循XML语法规则
  208.                     .syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml)
  209.                     // 内容转义时遵循xhtml规范
  210.                     .escapeMode(Entities.EscapeMode.xhtml)
  211.                     // 禁用格式化输出
  212.                     .prettyPrint(false);
  213.             return jsoupDoc.html();
  214.         }
  215.         /**
  216.          * 检查 AST 中是否存在 Markdown 特有节点(非纯文本段落)
  217.          */
  218.         private static boolean hasMarkdownNodes(Node node) {
  219.             if (node == null) {
  220.                 return false;
  221.             }
  222.             // 判断当前节点是否为 Markdown 特有节点(非纯文本)
  223.             if (isMarkdownSpecificNode(node)) {
  224.                 return true;
  225.             }
  226.             // 递归检查子节点
  227.             Node child = node.getFirstChild();
  228.             while (child != null) {
  229.                 if (hasMarkdownNodes(child)) {
  230.                     return true;
  231.                 }
  232.                 child = child.getNext();
  233.             }
  234.             return false;
  235.         }
  236.         /**
  237.          * 判定节点是否为 Markdown 特有节点(非纯文本段落)
  238.          * 纯文本段落(Paragraph)且无任何格式(如链接、粗体等)则视为非 Markdown
  239.          */
  240.         private static boolean isMarkdownSpecificNode(Node node) {
  241.             // 标题(# 标题)
  242.             if (node instanceof Heading) {
  243.                 return true;
  244.             }
  245.             // 列表(有序/无序)
  246.             if (node instanceof BulletList || node instanceof OrderedList) {
  247.                 return true;
  248.             }
  249.             // 列表项
  250.             if (node instanceof ListItem) {
  251.                 return true;
  252.             }
  253.             // 链接([文本](url))
  254.             if (node instanceof Link) {
  255.                 return true;
  256.             }
  257.             // 图片(![alt](url))
  258.             if (node instanceof Image) {
  259.                 return true;
  260.             }
  261.             // 粗体(**文本** 或 __文本__)
  262.             if (node instanceof StrongEmphasis) {
  263.                 return true;
  264.             }
  265.             // 斜体(*文本* 或 _文本_)
  266.             if (node instanceof Emphasis) {
  267.                 return true;
  268.             }
  269.             // 代码块(```代码```)
  270.             if (node instanceof FencedCodeBlock || node instanceof IndentedCodeBlock) {
  271.                 return true;
  272.             }
  273.             // 表格(| 表头 | ... |)
  274.             if (node instanceof TableBlock) {
  275.                 return true;
  276.             }
  277.             // 引用(> 引用内容)
  278.             if (node instanceof BlockQuote) {
  279.                 return true;
  280.             }
  281.             // 水平线(--- 或 ***)
  282.             if (node instanceof ThematicBreak) {
  283.                 return true;
  284.             }
  285.             // 段落节点需进一步检查是否包含 inline 格式(如粗体、链接等)
  286.             if (node instanceof Paragraph) {
  287.                 return hasInlineMarkdownNodes(node);
  288.             }
  289.             // 其他节点(如文本节点)视为非特有
  290.             return false;
  291.         }
  292.         /**
  293.          * 检查段落中是否包含 inline 格式(如粗体、链接等)
  294.          */
  295.         private static boolean hasInlineMarkdownNodes(Node paragraph) {
  296.             Node child = paragraph.getFirstChild();
  297.             while (child != null) {
  298.                 // 若段落中包含任何 Markdown  inline 节点,则视为 Markdown
  299.                 if (child instanceof Link || child instanceof Image || child instanceof StrongEmphasis || child instanceof Emphasis || child instanceof Code) {
  300.                     return true;
  301.                 }
  302.                 child = child.getNext();
  303.             }
  304.             return false;
  305.         }
  306.     }
  307. }
复制代码
8. 测试示例
  1. import com.vladsch.flexmark.ast.BulletList;
  2. import com.vladsch.flexmark.ast.Heading;
  3. import com.vladsch.flexmark.ast.OrderedList;
  4. import com.vladsch.flexmark.ast.Paragraph;
  5. import com.vladsch.flexmark.ast.Text;
  6. import com.vladsch.flexmark.ext.tables.TableBlock;
  7. import lombok.SneakyThrows;
  8. import lombok.extern.slf4j.Slf4j;
  9. import org.junit.Test;
  10. import java.io.File;
  11. import java.io.FileInputStream;
  12. import java.io.InputStream;
  13. import java.nio.file.Files;
  14. import java.nio.file.Paths;
  15. /**
  16. * 测试工具类
  17. *
  18. * @author ludangxin
  19. * @since 2025/10/14
  20. */
  21. @Slf4j
  22. public class Md2htmlTest {
  23.     @Test
  24.     public void given_md_str_then_print_complete_html() {
  25.         final String html = Markdowns.builder("# 简介 \n hello world~")
  26.                                   // 打印md内容
  27.                                   .printContent()
  28.                                   // 构建html内容, 自动完善html结构
  29.                                   .buildHtmlContent();
  30.         log.info(html);
  31.         // # 简介
  32.         // hello world~
  33.         //[main] INFO md2html.Md2htmlTest -- <html><head></head><body><h1>简介</h1>
  34.         //<p>hello world~</p>
  35.         //</body></html>
  36.     }
  37.     @Test
  38.     public void given_md_str_then_print_raw_html() {
  39.         final String html = Markdowns.builder("# 简介 \n hello world~")
  40.                                   // 构建raw html内容
  41.                                   .buildRawHtmlContent();
  42.         log.info(html);
  43.         //[main] INFO md2html.Md2htmlTest -- <h1>简介</h1>
  44.         //<p>hello world~</p>
  45.     }
  46.     @Test
  47.     public void given_md_file_then_print_raw_html() {
  48.         final String html = Markdowns.builder(new File("src/test/resources/test.md"))
  49.                                   // 构建raw html内容
  50.                                   .buildRawHtmlContent();
  51.         log.info(html);
  52.         //[main] INFO md2html.Md2htmlTest -- <h2>嘉文四世</h2>
  53.         //<blockquote>
  54.         //<p>德玛西亚</p>
  55.         //</blockquote>
  56.         //<p><strong>给我找些更强的敌人!</strong></p>
  57.         //<table>
  58.         //<thead>
  59.         //<tr><th>列1</th><th>列2</th></tr>
  60.         //</thead>
  61.         //<tbody>
  62.         //<tr><td>数据1</td><td>数据2</td></tr>
  63.         //</tbody>
  64.         //</table>
  65.     }
  66.     @Test
  67.     @SneakyThrows
  68.     public void given_md_stream_then_print_complete_html() {
  69.         final InputStream fileInputStream = Files.newInputStream(Paths.get("src/test/resources/test.md"));
  70.         final String html = Markdowns.builder(fileInputStream)
  71.                                      // 构建html内容
  72.                                      .buildHtmlContent();
  73.         log.info(html);
  74.         //[main] INFO md2html.Md2htmlTest -- <html><head></head><body><h2>嘉文四世</h2>
  75.         //<blockquote>
  76.         //<p>德玛西亚</p>
  77.         //</blockquote>
  78.         //<p><strong>给我找些更强的敌人!</strong></p>
  79.         //<table>
  80.         //<thead>
  81.         //<tr><th>列1</th><th>列2</th></tr>
  82.         //</thead>
  83.         //<tbody>
  84.         //<tr><td>数据1</td><td>数据2</td></tr>
  85.         //</tbody>
  86.         //</table>
  87.         //</body></html>
  88.     }
  89.     @Test
  90.     public void given_non_md_content_then_print_complete_html() {
  91.         // 输入非markdown语法的内容
  92.         final String html = Markdowns.builder("hello world~")
  93.                                      // 构建html内容 (如果内容是md语法则转换为html, 如不不是 则原样输出)
  94.                                      .buildHtmlIfMarkdown();
  95.         // 输入非markdown语法的内容
  96.         final String html2 = Markdowns.builder("## hello world~")
  97.                                      // 构建html内容 (如果内容是md语法则转换为html, 如不不是 则原样输出)
  98.                                      .buildHtmlIfMarkdown();
  99.         log.info(html);
  100.         //[main] INFO md2html.Md2htmlTest -- hello world~
  101.         log.info(html2);
  102.         //[main] INFO md2html.Md2htmlTest -- <html><head></head><body><h2>hello world~</h2>
  103.     }
  104.     @Test
  105.     @SneakyThrows
  106.     public void given_md_stream_and_attr_provider_then_print_raw_html() {
  107.         final InputStream fileInputStream = Files.newInputStream(Paths.get("src/test/resources/test.md"));
  108.         final String html = Markdowns.builder(fileInputStream)
  109.                         .attributeProvider((node, attributablePart, attributes) -> {
  110.                             // 标题
  111.                             if (node instanceof Heading) {
  112.                                 Heading heading = (Heading) node;
  113.                                 attributes.addValue("class", "heading" + heading.getLevel());
  114.                             }
  115.                             // 正文
  116.                             if (node instanceof Text) {
  117.                                 attributes.addValue("class", "Normal");
  118.                             }
  119.                             // 段落
  120.                             if (node instanceof Paragraph) {
  121.                                 attributes.addValue("class", "paragraph");
  122.                             }
  123.                             // 无序列表
  124.                             if (node instanceof BulletList) {
  125.                                 attributes.addValue("class", "bulletList");
  126.                             }
  127.                             // 有序列表
  128.                             if (node instanceof OrderedList) {
  129.                                 attributes.addValue("class", "bulletList");
  130.                             }
  131.                             // 表格
  132.                             if (node instanceof TableBlock) {
  133.                                 attributes.addValue("class", "tableBlock");
  134.                             }
  135.                         })
  136.                         .buildRawHtmlContent();
  137.         log.info(html);
  138.         //[main] INFO md2html.Md2htmlTest -- <h2 >嘉文四世</h2>
  139.         //<blockquote>
  140.         //<p >德玛西亚</p>
  141.         //</blockquote>
  142.         //<p ><strong>给我找些更强的敌人!</strong></p>
  143.         //<table >
  144.         //<thead>
  145.         //<tr><th>列1</th><th>列2</th></tr>
  146.         //</thead>
  147.         //<tbody>
  148.         //<tr><td>数据1</td><td>数据2</td></tr>
  149.         //</tbody>
  150.         //</table>
  151.     }
  152. }
复制代码
9. 小节

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

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

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册