flexmark-java 是一个基于 Java 实现的 CommonMark (spec 0.28) 解析器,它采用了“先块后内联”的 Markdown 解析架构;本文主要介绍其基本使用,文中所使用到的软件版本:Java 17.0.5、flexmark 0.64.8。
1、简介
flexmark-java 的优势在于速度快、灵活性高、基于 Markdown 源元素的抽象语法树(AST)能够精确到组成元素的单个字符词素的源位置,以及强大的可扩展性。
flexmark-java 允许对解析过程进行细粒度控制,并针对安装大量扩展的解析场景进行了优化。解析器和扩展都提供了丰富的选项,用于调整解析器行为和 HTML 渲染变体。最终目标是让解析器和渲染器能够以极高的精度模拟其他解析器。现在,通过实现 “Markdown 处理器仿真” 功能,这一目标已部分完成。
该项目的动机是为了替换 JetBrains IDEs 的 Markdown Navigator 插件中使用的 pegdown 解析器。pegdown 的功能集非常强大,但其整体速度并不理想,并且在遇到病态输入时,解析过程会挂起或实际上处于挂起状态。
对于 0.62.2 及以下版本:需要 Java 8+;对于 0.64.0 及以上版本:需要 Java 11+。
2、flexmark-java 使用
2.1、样例文件
解析 Markdown 时使用该样例文件。

## 1 Markdown语法教程 ### 1.1 标题 不同数量的`#`可以完成不同的标题,如下: # 一级标题 ## 二级标题 ### 三级标题 ### 1.2 字体 粗体、斜体、粗体和斜体,删除线,需要在文字前后加不同的标记符号。如下: **这个是粗体** *这个是斜体* ***这个是粗体加斜体*** ~~这里是删除线~~ 注:如果想给字体换颜色、字体或者居中显示,需要使用内嵌HTML来实现。 ### 1.3 无序列表 无序列表的使用,在符号`-`后加空格使用。如下: - 无序列表 1 - 无序列表 2 - 无序列表 3 如果要控制列表的层级,则需要在符号`-`前使用空格。如下: - 无序列表 1 - 无序列表 2 - 无序列表 2.1 - 无序列表 2.2 ### 1.4 有序列表 有序列表的使用,在数字及符号`.`后加空格后输入内容,如下: 1. 有序列表 1 2. 有序列表 2 3. 有序列表 3 ### 1.5 引用 引用的格式是在符号`>`后面书写文字。如下: > 读一本好书,就是在和高尚的人谈话。 ——歌德 > 雇用制度对工人不利,但工人根本无力摆脱这个制度。 ——阮一峰 ### 1.7 链接 微信公众号仅支持公众号文章链接,即域名为`https://mp.weixin.qq.com/`的合法链接。使用方法如下所示: 对于该论述,欢迎读者查阅之前发过的文章,[你是《未来世界的幸存者》么?](https://mp.weixin.qq.com/s/s5IhxV2ooX3JN_X416nidA) ### 1.8 图片 插入图片,格式如下:  支持 jpg、png、gif、svg 等图片格式,**其中 svg 文件仅可在微信公众平台中使用**,svg 文件示例如下:  支持图片**拖拽和截图粘贴**到编辑器中。 注:支持图片 ***拖拽和截图粘贴*** 到编辑器中,仅支持 https 的图片,图片粘贴到微信时会自动上传微信服务器。 ### 1.9 分割线 可以在一行中用三个以上的减号来建立一个分隔线,同时需要在分隔线的上面空一行。如下: --- ### 1.10 表格 可以使用冒号来定义表格的对齐方式,如下: | 姓名 | 年龄 | 工作 | | :----- | :--: | -------: | | 小可爱 | 18 | 吃可爱多 | | 小小勇敢 | 20 | 爬棵勇敢树 | | 小小小机智 | 22 | 看一本机智书 |
test.md
2.2、引入依赖
<dependency> <groupId>com.vladsch.flexmark</groupId> <artifactId>flexmark-all</artifactId> <version>0.64.8</version> </dependency>
2.3、API 方式生成 HTML 或 Markdown 文档
@Test public void serialize() { Document document = new Document(null, BasedSequence.EMPTY); Heading heading = new Heading(); heading.setLevel(2); heading.appendChild(new Text("1 Markdown语法教程")); document.appendChild(heading); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.1 标题")); document.appendChild(heading); Paragraph paragraph = new Paragraph(); paragraph.appendChild(new Text("不同数量的")); Code code = new Code(); code.setText(BasedSequence.of("#")); paragraph.appendChild(code); paragraph.appendChild(new Text("可以完成不同的标题,如下:")); document.appendChild(paragraph); heading = new Heading(); heading.setLevel(1); heading.appendChild(new Text("一级标题")); document.appendChild(heading); heading = new Heading(); heading.setLevel(2); heading.appendChild(new Text("二级标题")); document.appendChild(heading); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("三级标题")); document.appendChild(heading); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.2 字体")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("粗体、斜体、粗体和斜体,删除线,需要在文字前后加不同的标记符号。如下:")); document.appendChild(paragraph); paragraph = new Paragraph(); StrongEmphasis strongEmphasis = new StrongEmphasis(); strongEmphasis.appendChild(new Text("这个是粗体")); paragraph.appendChild(strongEmphasis); document.appendChild(paragraph); paragraph = new Paragraph(); Emphasis emphasis = new Emphasis(); emphasis.appendChild(new Text("这个是斜体")); paragraph.appendChild(emphasis); document.appendChild(paragraph); paragraph = new Paragraph(); strongEmphasis = new StrongEmphasis(); emphasis = new Emphasis(); emphasis.appendChild(new Text("这个是粗体加斜体")); strongEmphasis.appendChild(emphasis); paragraph.appendChild(strongEmphasis); document.appendChild(paragraph); Strikethrough strikethrough = new Strikethrough(); strikethrough.appendChild(new Text("这里是删除线")); paragraph = new Paragraph(); paragraph.appendChild(strikethrough); document.appendChild(paragraph); paragraph = new Paragraph(); paragraph.appendChild(new Text("注:如果想给字体换颜色、字体或者居中显示,需要使用内嵌HTML来实现。")); document.appendChild(paragraph); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.3 无序列表")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("无序列表的使用,在符号")); code = new Code(); code.setText(BasedSequence.of("-")); paragraph.appendChild(code); paragraph.appendChild(new Text("后加空格使用。如下:")); document.appendChild(paragraph); BulletList bulletList = new BulletList(); BulletListItem bulletListItem = new BulletListItem(); bulletListItem.appendChild(new Text("无序列表 1")); bulletList.appendChild(bulletListItem); bulletListItem = new BulletListItem(); bulletListItem.appendChild(new Text("无序列表 2")); bulletList.appendChild(bulletListItem); bulletListItem = new BulletListItem(); bulletListItem.appendChild(new Text("无序列表 3")); bulletList.appendChild(bulletListItem); document.appendChild(bulletList); paragraph = new Paragraph(); paragraph.appendChild(new Text("如果要控制列表的层级,则需要在符号")); code = new Code(); code.setText(BasedSequence.of("-")); paragraph.appendChild(code); paragraph.appendChild(new Text("前使用空格。如下:")); document.appendChild(paragraph); bulletList = new BulletList(); bulletListItem = new BulletListItem(); bulletListItem.appendChild(new Text("无序列表 1")); bulletList.appendChild(bulletListItem); bulletListItem = new BulletListItem(); bulletListItem.appendChild(new Text("无序列表 2")); bulletList.appendChild(bulletListItem); BulletList bulletList2 = new BulletList(); bulletListItem = new BulletListItem(); bulletListItem.appendChild(new Text("无序列表 2.1")); bulletList2.appendChild(bulletListItem); bulletListItem = new BulletListItem(); bulletListItem.appendChild(new Text("无序列表 2.2")); bulletList2.appendChild(bulletListItem); bulletList.appendChild(bulletList2); document.appendChild(bulletList); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.4 有序列表")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("有序列表的使用,在数字及符号")); code = new Code(); code.setText(BasedSequence.of(".")); paragraph.appendChild(code); paragraph.appendChild(new Text("后加空格后输入内容,如下:")); document.appendChild(paragraph); OrderedList orderedList = new OrderedList(); orderedList.setStartNumber(1); OrderedListItem orderedListItem = new OrderedListItem(); orderedListItem.appendChild(new Text("有序列表 1")); orderedList.appendChild(orderedListItem); orderedListItem = new OrderedListItem(); orderedListItem.appendChild(new Text("有序列表 2")); orderedList.appendChild(orderedListItem); orderedListItem = new OrderedListItem(); orderedListItem.appendChild(new Text("有序列表 3")); orderedList.appendChild(orderedListItem); document.appendChild(orderedList); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.5 引用")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("引用的格式是在符号")); code = new Code(); code.setText(BasedSequence.of(">")); paragraph.appendChild(code); paragraph.appendChild(new Text("后面书写文字。如下:")); document.appendChild(paragraph); paragraph = new Paragraph(); paragraph.appendChild(new Text(" ")); document.appendChild(paragraph); BlockQuote blockQuote = new BlockQuote(); paragraph = new Paragraph(); paragraph.appendChild(new Text("读一本好书,就是在和高尚的人谈话。 ——歌德")); blockQuote.appendChild(paragraph); document.appendChild(blockQuote); paragraph = new Paragraph(); paragraph.appendChild(new Text(" ")); document.appendChild(paragraph); blockQuote = new BlockQuote(); paragraph = new Paragraph(); paragraph.appendChild(new Text("雇用制度对工人不利,但工人根本无力摆脱这个制度。 ——阮一峰")); blockQuote.appendChild(paragraph); document.appendChild(blockQuote); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.7 链接")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("微信公众号仅支持公众号文章链接,即域名为")); code = new Code(); code.setText(BasedSequence.of("https://mp.weixin.qq.com/")); paragraph.appendChild(code); paragraph.appendChild(new Text("的合法链接。使用方法如下所示:")); document.appendChild(paragraph); paragraph = new Paragraph(); Link link = new Link(); link.setUrl(BasedSequence.of("https://mp.weixin.qq.com/s/s5IhxV2ooX3JN_X416nidA")); link.appendChild(new Text("你是《未来世界的幸存者》么?")); paragraph.appendChild(new Text("对于该论述,欢迎读者查阅之前发过的文章,")); paragraph.appendChild(link); document.appendChild(paragraph); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.8 图片")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("插入图片,格式如下:")); document.appendChild(paragraph); paragraph = new Paragraph(); Image image = new Image(); image.setUrl(BasedSequence.of("https://markdown.com.cn/images/qrcode_for_gh_82cf87d482f0_258.jpg")); image.appendChild(new Text("这里写图片描述")); paragraph.appendChild(image); document.appendChild(paragraph); paragraph = new Paragraph(); paragraph.appendChild(new Text("支持 jpg、png、gif、svg 等图片格式,")); strongEmphasis = new StrongEmphasis(); strongEmphasis.appendChild(new Text("其中 svg 文件仅可在微信公众平台中使用")); paragraph.appendChild(strongEmphasis); paragraph.appendChild(new Text(",svg 文件示例如下:")); document.appendChild(paragraph); paragraph = new Paragraph(); image = new Image(); image.setUrl(BasedSequence.of("https://markdown.com.cn/images/i-am-svg.svg")); paragraph.appendChild(image); document.appendChild(paragraph); paragraph = new Paragraph(); paragraph.appendChild(new Text("支持图片")); strongEmphasis = new StrongEmphasis(); strongEmphasis.appendChild(new Text("拖拽和截图粘贴")); paragraph.appendChild(strongEmphasis); paragraph.appendChild(new Text("到编辑器中。")); document.appendChild(paragraph); paragraph = new Paragraph(); paragraph.appendChild(new Text("注:支持图片 ")); strongEmphasis = new StrongEmphasis(); emphasis = new Emphasis(); emphasis.appendChild(new Text("拖拽和截图粘贴")); strongEmphasis.appendChild(emphasis); paragraph.appendChild(strongEmphasis); paragraph.appendChild(new Text(" 到编辑器中,仅支持 https 的图片,图片粘贴到微信时会自动上传微信服务器。")); document.appendChild(paragraph); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.9 分割线")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("可以在一行中用三个以上的减号来建立一个分隔线,同时需要在分隔线的上面空一行。如下:")); document.appendChild(paragraph); ThematicBreak thematicBreak = new ThematicBreak(); document.appendChild(thematicBreak); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.10 表格")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("可以使用冒号来定义表格的对齐方式,如下:")); document.appendChild(paragraph); TableBlock tableBlock = new TableBlock(); TableHead tableHead = new TableHead(); TableRow tableRow = new TableRow(); TableCell tableCell = new TableCell(); tableCell.appendChild(new Text("姓名")); tableCell.setAlignment(TableCell.Alignment.LEFT); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("年龄")); tableCell.setAlignment(TableCell.Alignment.CENTER); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("工作")); tableCell.setAlignment(TableCell.Alignment.RIGHT); tableRow.appendChild(tableCell); tableHead.appendChild(tableRow); TableBody tableBody = new TableBody(); tableRow = new TableRow(); tableCell = new TableCell(); tableCell.appendChild(new Text("小可爱")); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("18")); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("吃可爱多")); tableRow.appendChild(tableCell); tableBody.appendChild(tableRow); tableRow = new TableRow(); tableCell = new TableCell(); tableCell.appendChild(new Text("小小勇敢")); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("20")); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("爬棵勇敢树")); tableRow.appendChild(tableCell); tableBody.appendChild(tableRow); tableRow = new TableRow(); tableCell = new TableCell(); tableCell.appendChild(new Text("小小小机智")); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("22")); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("看一本机智书")); tableRow.appendChild(tableCell); tableBody.appendChild(tableRow); tableBlock.appendChild(tableHead); tableBlock.appendChild(tableBody); document.appendChild(tableBlock); MutableDataSet options = new MutableDataSet(); options.set(Parser.EXTENSIONS, Arrays.asList( StrikethroughExtension.create(), //删除线扩展 TablesExtension.create() //表格扩展 )); HtmlRenderer htmlRenderer = HtmlRenderer.builder(options).build(); String html = htmlRenderer.render(document); System.out.println(html); MutableDataSet options2 = new MutableDataSet(); options2.set(FlexmarkHtmlConverter.SETEXT_HEADINGS, false);//标题是否使用下划线格式 options2.set(FlexmarkHtmlConverter.SKIP_CHAR_ESCAPE, true);//是否跳过转义符 options2.set(FlexmarkHtmlConverter.UNORDERED_LIST_DELIMITER, '-');//无序列表的符号 options2.set(FlexmarkHtmlConverter.TYPOGRAPHIC_SMARTS, false);//是否启用智能排版,它会处理文本中的一些常见的标点符号,并将其转换为更符合排版习惯的字符 options2.set(FlexmarkHtmlConverter.THEMATIC_BREAK, "---"); String markdown = FlexmarkHtmlConverter.builder(options2).build().convert(html); System.out.println(markdown); }
2.4、解析 Markdown 文档
@Test public void deserialize() throws IOException { MutableDataSet options = new MutableDataSet(); options.set(Parser.EXTENSIONS, Arrays.asList( StrikethroughExtension.create(), //删除线扩展 TablesExtension.create() //表格扩展 )); Parser parser = Parser.builder(options).build(); InputStreamReader reader = new InputStreamReader(FlexmarkCase.class.getClassLoader().getResourceAsStream("test.md")); Document document = parser.parseReader(reader); ReversiblePeekingIterator<Node> iterator = document.getChildIterator(); while (iterator.hasNext()) { Node node = iterator.next(); if (node.getClass().equals(Heading.class)) { Heading heading = (Heading) node; System.out.println(heading.getText()); } else if (node.getClass().equals(BulletList.class)) { BulletList bulletList = (BulletList) node; ReversiblePeekingIterator<Node> iterator2 = bulletList.getChildIterator(); while (iterator2.hasNext()) { Node node2 = iterator2.next(); BulletListItem bulletListItem = (BulletListItem) node2; ReversiblePeekingIterator<Node> iterator3 = bulletListItem.getChildIterator(); while (iterator3.hasNext()) { Node node3 = iterator3.next(); if (node3.getClass().equals(BulletList.class)) { System.out.println("二级无序列表"); //TODO: 继续解析 } else { System.out.println(node3.getChars()); } } } } else if (node.getClass().equals(ThematicBreak.class)) { System.out.println(node.getChars()); } //else if ... } }
2.5、完整代码

package com.abc.md; import com.vladsch.flexmark.ast.*; import com.vladsch.flexmark.ext.gfm.strikethrough.Strikethrough; import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension; import com.vladsch.flexmark.ext.tables.*; import com.vladsch.flexmark.html.HtmlRenderer; import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter; 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.collection.iteration.ReversiblePeekingIterator; import com.vladsch.flexmark.util.data.MutableDataSet; import com.vladsch.flexmark.util.sequence.BasedSequence; import org.junit.Test; import java.io.IOException; import java.io.InputStreamReader; import java.util.Arrays; public class FlexmarkCase { @Test public void serialize() { Document document = new Document(null, BasedSequence.EMPTY); Heading heading = new Heading(); heading.setLevel(2); heading.appendChild(new Text("1 Markdown语法教程")); document.appendChild(heading); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.1 标题")); document.appendChild(heading); Paragraph paragraph = new Paragraph(); paragraph.appendChild(new Text("不同数量的")); Code code = new Code(); code.setText(BasedSequence.of("#")); paragraph.appendChild(code); paragraph.appendChild(new Text("可以完成不同的标题,如下:")); document.appendChild(paragraph); heading = new Heading(); heading.setLevel(1); heading.appendChild(new Text("一级标题")); document.appendChild(heading); heading = new Heading(); heading.setLevel(2); heading.appendChild(new Text("二级标题")); document.appendChild(heading); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("三级标题")); document.appendChild(heading); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.2 字体")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("粗体、斜体、粗体和斜体,删除线,需要在文字前后加不同的标记符号。如下:")); document.appendChild(paragraph); paragraph = new Paragraph(); StrongEmphasis strongEmphasis = new StrongEmphasis(); strongEmphasis.appendChild(new Text("这个是粗体")); paragraph.appendChild(strongEmphasis); document.appendChild(paragraph); paragraph = new Paragraph(); Emphasis emphasis = new Emphasis(); emphasis.appendChild(new Text("这个是斜体")); paragraph.appendChild(emphasis); document.appendChild(paragraph); paragraph = new Paragraph(); strongEmphasis = new StrongEmphasis(); emphasis = new Emphasis(); emphasis.appendChild(new Text("这个是粗体加斜体")); strongEmphasis.appendChild(emphasis); paragraph.appendChild(strongEmphasis); document.appendChild(paragraph); Strikethrough strikethrough = new Strikethrough(); strikethrough.appendChild(new Text("这里是删除线")); paragraph = new Paragraph(); paragraph.appendChild(strikethrough); document.appendChild(paragraph); paragraph = new Paragraph(); paragraph.appendChild(new Text("注:如果想给字体换颜色、字体或者居中显示,需要使用内嵌HTML来实现。")); document.appendChild(paragraph); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.3 无序列表")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("无序列表的使用,在符号")); code = new Code(); code.setText(BasedSequence.of("-")); paragraph.appendChild(code); paragraph.appendChild(new Text("后加空格使用。如下:")); document.appendChild(paragraph); BulletList bulletList = new BulletList(); BulletListItem bulletListItem = new BulletListItem(); bulletListItem.appendChild(new Text("无序列表 1")); bulletList.appendChild(bulletListItem); bulletListItem = new BulletListItem(); bulletListItem.appendChild(new Text("无序列表 2")); bulletList.appendChild(bulletListItem); bulletListItem = new BulletListItem(); bulletListItem.appendChild(new Text("无序列表 3")); bulletList.appendChild(bulletListItem); document.appendChild(bulletList); paragraph = new Paragraph(); paragraph.appendChild(new Text("如果要控制列表的层级,则需要在符号")); code = new Code(); code.setText(BasedSequence.of("-")); paragraph.appendChild(code); paragraph.appendChild(new Text("前使用空格。如下:")); document.appendChild(paragraph); bulletList = new BulletList(); bulletListItem = new BulletListItem(); bulletListItem.appendChild(new Text("无序列表 1")); bulletList.appendChild(bulletListItem); bulletListItem = new BulletListItem(); bulletListItem.appendChild(new Text("无序列表 2")); bulletList.appendChild(bulletListItem); BulletList bulletList2 = new BulletList(); bulletListItem = new BulletListItem(); bulletListItem.appendChild(new Text("无序列表 2.1")); bulletList2.appendChild(bulletListItem); bulletListItem = new BulletListItem(); bulletListItem.appendChild(new Text("无序列表 2.2")); bulletList2.appendChild(bulletListItem); bulletList.appendChild(bulletList2); document.appendChild(bulletList); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.4 有序列表")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("有序列表的使用,在数字及符号")); code = new Code(); code.setText(BasedSequence.of(".")); paragraph.appendChild(code); paragraph.appendChild(new Text("后加空格后输入内容,如下:")); document.appendChild(paragraph); OrderedList orderedList = new OrderedList(); orderedList.setStartNumber(1); OrderedListItem orderedListItem = new OrderedListItem(); orderedListItem.appendChild(new Text("有序列表 1")); orderedList.appendChild(orderedListItem); orderedListItem = new OrderedListItem(); orderedListItem.appendChild(new Text("有序列表 2")); orderedList.appendChild(orderedListItem); orderedListItem = new OrderedListItem(); orderedListItem.appendChild(new Text("有序列表 3")); orderedList.appendChild(orderedListItem); document.appendChild(orderedList); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.5 引用")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("引用的格式是在符号")); code = new Code(); code.setText(BasedSequence.of(">")); paragraph.appendChild(code); paragraph.appendChild(new Text("后面书写文字。如下:")); document.appendChild(paragraph); paragraph = new Paragraph(); paragraph.appendChild(new Text(" ")); document.appendChild(paragraph); BlockQuote blockQuote = new BlockQuote(); paragraph = new Paragraph(); paragraph.appendChild(new Text("读一本好书,就是在和高尚的人谈话。 ——歌德")); blockQuote.appendChild(paragraph); document.appendChild(blockQuote); paragraph = new Paragraph(); paragraph.appendChild(new Text(" ")); document.appendChild(paragraph); blockQuote = new BlockQuote(); paragraph = new Paragraph(); paragraph.appendChild(new Text("雇用制度对工人不利,但工人根本无力摆脱这个制度。 ——阮一峰")); blockQuote.appendChild(paragraph); document.appendChild(blockQuote); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.7 链接")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("微信公众号仅支持公众号文章链接,即域名为")); code = new Code(); code.setText(BasedSequence.of("https://mp.weixin.qq.com/")); paragraph.appendChild(code); paragraph.appendChild(new Text("的合法链接。使用方法如下所示:")); document.appendChild(paragraph); paragraph = new Paragraph(); Link link = new Link(); link.setUrl(BasedSequence.of("https://mp.weixin.qq.com/s/s5IhxV2ooX3JN_X416nidA")); link.appendChild(new Text("你是《未来世界的幸存者》么?")); paragraph.appendChild(new Text("对于该论述,欢迎读者查阅之前发过的文章,")); paragraph.appendChild(link); document.appendChild(paragraph); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.8 图片")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("插入图片,格式如下:")); document.appendChild(paragraph); paragraph = new Paragraph(); Image image = new Image(); image.setUrl(BasedSequence.of("https://markdown.com.cn/images/qrcode_for_gh_82cf87d482f0_258.jpg")); image.appendChild(new Text("这里写图片描述")); paragraph.appendChild(image); document.appendChild(paragraph); paragraph = new Paragraph(); paragraph.appendChild(new Text("支持 jpg、png、gif、svg 等图片格式,")); strongEmphasis = new StrongEmphasis(); strongEmphasis.appendChild(new Text("其中 svg 文件仅可在微信公众平台中使用")); paragraph.appendChild(strongEmphasis); paragraph.appendChild(new Text(",svg 文件示例如下:")); document.appendChild(paragraph); paragraph = new Paragraph(); image = new Image(); image.setUrl(BasedSequence.of("https://markdown.com.cn/images/i-am-svg.svg")); paragraph.appendChild(image); document.appendChild(paragraph); paragraph = new Paragraph(); paragraph.appendChild(new Text("支持图片")); strongEmphasis = new StrongEmphasis(); strongEmphasis.appendChild(new Text("拖拽和截图粘贴")); paragraph.appendChild(strongEmphasis); paragraph.appendChild(new Text("到编辑器中。")); document.appendChild(paragraph); paragraph = new Paragraph(); paragraph.appendChild(new Text("注:支持图片 ")); strongEmphasis = new StrongEmphasis(); emphasis = new Emphasis(); emphasis.appendChild(new Text("拖拽和截图粘贴")); strongEmphasis.appendChild(emphasis); paragraph.appendChild(strongEmphasis); paragraph.appendChild(new Text(" 到编辑器中,仅支持 https 的图片,图片粘贴到微信时会自动上传微信服务器。")); document.appendChild(paragraph); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.9 分割线")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("可以在一行中用三个以上的减号来建立一个分隔线,同时需要在分隔线的上面空一行。如下:")); document.appendChild(paragraph); ThematicBreak thematicBreak = new ThematicBreak(); document.appendChild(thematicBreak); heading = new Heading(); heading.setLevel(3); heading.appendChild(new Text("1.10 表格")); document.appendChild(heading); paragraph = new Paragraph(); paragraph.appendChild(new Text("可以使用冒号来定义表格的对齐方式,如下:")); document.appendChild(paragraph); TableBlock tableBlock = new TableBlock(); TableHead tableHead = new TableHead(); TableRow tableRow = new TableRow(); TableCell tableCell = new TableCell(); tableCell.appendChild(new Text("姓名")); tableCell.setAlignment(TableCell.Alignment.LEFT); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("年龄")); tableCell.setAlignment(TableCell.Alignment.CENTER); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("工作")); tableCell.setAlignment(TableCell.Alignment.RIGHT); tableRow.appendChild(tableCell); tableHead.appendChild(tableRow); TableBody tableBody = new TableBody(); tableRow = new TableRow(); tableCell = new TableCell(); tableCell.appendChild(new Text("小可爱")); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("18")); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("吃可爱多")); tableRow.appendChild(tableCell); tableBody.appendChild(tableRow); tableRow = new TableRow(); tableCell = new TableCell(); tableCell.appendChild(new Text("小小勇敢")); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("20")); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("爬棵勇敢树")); tableRow.appendChild(tableCell); tableBody.appendChild(tableRow); tableRow = new TableRow(); tableCell = new TableCell(); tableCell.appendChild(new Text("小小小机智")); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("22")); tableRow.appendChild(tableCell); tableCell = new TableCell(); tableCell.appendChild(new Text("看一本机智书")); tableRow.appendChild(tableCell); tableBody.appendChild(tableRow); tableBlock.appendChild(tableHead); tableBlock.appendChild(tableBody); document.appendChild(tableBlock); MutableDataSet options = new MutableDataSet(); options.set(Parser.EXTENSIONS, Arrays.asList( StrikethroughExtension.create(), //删除线扩展 TablesExtension.create() //表格扩展 )); HtmlRenderer htmlRenderer = HtmlRenderer.builder(options).build(); String html = htmlRenderer.render(document); System.out.println(html); MutableDataSet options2 = new MutableDataSet(); options2.set(FlexmarkHtmlConverter.SETEXT_HEADINGS, false);//标题是否使用下划线格式 options2.set(FlexmarkHtmlConverter.SKIP_CHAR_ESCAPE, true);//是否跳过转义符 options2.set(FlexmarkHtmlConverter.UNORDERED_LIST_DELIMITER, '-');//无序列表的符号 options2.set(FlexmarkHtmlConverter.TYPOGRAPHIC_SMARTS, false);//是否启用智能排版,它会处理文本中的一些常见的标点符号,并将其转换为更符合排版习惯的字符 options2.set(FlexmarkHtmlConverter.THEMATIC_BREAK, "---"); String markdown = FlexmarkHtmlConverter.builder(options2).build().convert(html); System.out.println(markdown); } @Test public void deserialize() throws IOException { MutableDataSet options = new MutableDataSet(); options.set(Parser.EXTENSIONS, Arrays.asList( StrikethroughExtension.create(), //删除线扩展 TablesExtension.create() //表格扩展 )); Parser parser = Parser.builder(options).build(); InputStreamReader reader = new InputStreamReader(FlexmarkCase.class.getClassLoader().getResourceAsStream("test.md")); Document document = parser.parseReader(reader); ReversiblePeekingIterator<Node> iterator = document.getChildIterator(); while (iterator.hasNext()) { Node node = iterator.next(); if (node.getClass().equals(Heading.class)) { Heading heading = (Heading) node; System.out.println(heading.getText()); } else if (node.getClass().equals(BulletList.class)) { BulletList bulletList = (BulletList) node; ReversiblePeekingIterator<Node> iterator2 = bulletList.getChildIterator(); while (iterator2.hasNext()) { Node node2 = iterator2.next(); BulletListItem bulletListItem = (BulletListItem) node2; ReversiblePeekingIterator<Node> iterator3 = bulletListItem.getChildIterator(); while (iterator3.hasNext()) { Node node3 = iterator3.next(); if (node3.getClass().equals(BulletList.class)) { System.out.println("二级无序列表"); //TODO: 继续解析 } else { System.out.println(node3.getChars()); } } } } else if (node.getClass().equals(ThematicBreak.class)) { System.out.println(node.getChars()); } //else if ... } } }
FlexmarkCase.java
参考:https://github.com/vsch/flexmark-java。
文章摘自:https://www.cnblogs.com/wuyongyin/p/19106805
