Ohhnews

分类导航

$ cd ..
Baeldung原文

使用 commonmark-java 进行 Markdown 渲染

#markdown#commonmark-java#java#html渲染#文档解析

1. 概述

处理 Markdown 内容是常见的编程任务。CommonMark 是一个 Java 库,可简化 Markdown 文档的处理。

在本教程中,我们将学习如何使用该库操作 Markdown 内容。我们将了解如何将 Markdown 解析为 HTML,以及如何将 HTML 转换回 Markdown。最后,我们将探索如何自定义节点以实现高级处理。

2. commonmark-java

commonmark-java 库提供了基于 CommonMark 规范处理 Markdown 内容的类和接口。它允许我们将 Markdown 解析为 HTML,并将 HTML 转换回 Markdown。此外,它还提供对抽象语法树(AST)的访问,从而实现进一步的自定义和处理。

要使用 commonmark-java 库,我们将 commonmark 依赖添加到 pom.xml 中:

$ xml
<dependency>
    <groupId>org.commonmark</groupId>
    <artifactId>commonmark</artifactId>
    <version>0.28.0</version>
</dependency>

commonmark 依赖提供了用于 Markdown 处理和渲染的类,例如 ParserHtmlRendererMarkdownRenderer

此外,CommonMark 还提供扩展依赖以实现更高级的处理功能,例如用于 GitHub 风格的 Markdown 表格的 commonmark-ext-gfm-tables 和用于警报块的 commonmark-ext-gfm-alerts

3. 将 Markdown 解析并渲染为 HTML

接下来,我们看一下该库最常见的用途之一:解析 Markdown 并将其渲染为 HTML。

首先,定义一个名为 markDownToHtml() 的方法:

$ java
public static String markDownToHtml(String markdown) {
    Parser parser = Parser.builder().build();
    Node document = parser.parse(markdown);
    HtmlRenderer renderer = HtmlRenderer.builder().build();
    return renderer.render(document);
}

这里,我们创建了一个 Parser 实例来将 Markdown 输入解析为文档节点。接着,创建 HtmlRenderer 实例将解析后的节点渲染为 HTML

Node 表示解析后的 Markdown 文档树中的一个元素。

然后,编写一个单元测试来验证结果:

$ java
@Test
void givenMarkdownInput_whenConvertingToHtml_thenReturnRenderedHtml() {
    String html = markDownToHtml("Welcome to *Baeldung*");
    assertEquals("<p>Welcome to <em>Baeldung</em></p>\n", html);
}

在上面的代码中,我们将包含 Markdown 语法的字符串传递给 markDownToHtml() 方法。由于 Baeldung 被星号 (*) 包裹,Markdown 解析器将其解释为强调文本。因此,渲染器将其转换为 HTML 的 <em> 元素。

4. 处理解析后的节点

此外,我们可以使用访问者模式来进一步处理解析后的文档树中的节点。该库允许我们扩展 AbstractVisitor 类来处理节点。

让我们创建一个访问者类,统计句子中的每个单词数量:

$ java
class WordCountVisitor extends AbstractVisitor {
    int wordCount = 0;
    @Override
    public void visit(Text text) {
        wordCount += text.getLiteral().split("\\w+").length;
        visitChildren(text);
    }
}

接下来,编写一个使用该访问者的方法:

$ java
public static int processParsedNode(String markdown) {
    Parser parser = Parser.builder().build();
    Node node = parser.parse(markdown);
    WordCountVisitor visitor = new WordCountVisitor();
    node.accept(visitor);
    return visitor.wordCount;
}

在上面的方法中,我们创建了一个 WordCountVisitor 对象并将其传递给解析后的节点进行处理。

编写一个单元测试来确认该方法:

$ java
@Test
void givenMarkdownInput_whenProcessingParsedNode_thenReturnWordCount() {
    int wordCount = processParsedNode("Welcome to *Baeldung*");
    assertEquals(3, wordCount);
}

这里,我们验证预期的单词计数与实际单词计数相等。

5. 将 HTML 渲染为 Markdown

此外,CommonMark 还提供了将类似 HTML 的文档结构渲染为 Markdown 格式的类,使其成为一个同时用于 HTML 和 Markdown 处理的库。

让我们通过编写将 HTML 标题转换为 Markdown 格式的代码来展示这一点:

$ java
public static String htmlToMarkDown(String htmlHeading) {
    Heading heading = new Heading();
    heading.setLevel(2);
    heading.appendChild(new Text(htmlHeading));
    Document document = new Document();
    document.appendChild(heading);
    MarkdownRenderer renderer = MarkdownRenderer.builder()
      .build();
    return renderer.render(document);
}

在上面的代码中,我们创建了一个 Heading 对象,并将其级别设置为 2,表示 H2 标题。接着,我们追加一个包含标题内容的 Text 对象。最后,使用 MarkdownRenderer 构建器将文档渲染为 Markdown

接下来,编写一个单元测试来确认输出:

$ java
@Test
void givenHeadingText_whenConvertingToMarkdown_thenReturnMarkdownHeading() {
    String markdown = htmlToMarkDown("Java Tutorial");
    assertEquals("## Java Tutorial\n", markdown);
}

在上面的代码中,我们验证了渲染器正确地将文档结构转换为有效的 Markdown 输出。

6. 自定义 HTML 渲染

此外,CommonMark 允许我们使用 AttributeProvider 接口自定义渲染的 HTML 属性。

让我们通过实现一个自定义图片属性提供者类来展示这一点:

$ java
public class ImageAttributeProvider implements AttributeProvider {
    @Override
    public void setAttributes(Node node, String tagName, Map<String, String> attributes) {
        if (node instanceof Image) {
            attributes.put("class", "border");
        }
    }
}

在上面的代码中,我们创建了一个名为 ImageAttributeProvider 的类,实现了 AttributeProvider 接口。在 setAttributes() 方法中,我们检查当前节点是否为 Image 的实例。如果是,则添加一个值为 "border"class 属性。

接下来,编写一个方法,在 HTML 渲染期间应用自定义属性提供者:

$ java
public static String changingHtmlAttribute(String source) {
    Parser parser = Parser.builder()
      .build();
    Node node = parser.parse(source);
    HtmlRenderer renderer = HtmlRenderer.builder()
      .attributeProviderFactory(context -> new ImageAttributeProvider())
      .build();
    return renderer.render(node);
}

在上面的代码中,我们使用 attributeProviderFactory() 方法自定义 HtmlRenderer,传入自定义的属性提供者。通过lambda 表达式,我们为每个节点调用我们的提供者,从而可以自定义生成的 HTML 属性。每个渲染的图片元素都会获得一个 class="border" 属性。

最后,编写一个单元测试来验证自定义输出:

$ java
@Test
void givenImageMarkdown_whenRenderingHtml_thenAddCustomClassAttribute() {
    String html = changingHtmlAttribute("![text](/url.png)");
    assertEquals("<p><img src=\"/url.png\" alt=\"text\" class=\"border\" /></p>\n", html);
}

在上面的测试中,我们验证了自定义属性提供者成功地将 border CSS 类添加到了渲染的图片元素中。

7. 自定义渲染节点

此外,该库允许我们通过实现 NodeRenderer 接口来自定义特定节点的渲染方式。

让我们为 IndentedCodeBlock 节点创建一个自定义渲染器:

$ java
public class IndentedCodeBlockNodeRenderer implements NodeRenderer {
    private final HtmlWriter html;
    public IndentedCodeBlockNodeRenderer(HtmlNodeRendererContext context) {
        this.html = context.getWriter();
    }
    @Override
    public Set<Class<? extends Node>> getNodeTypes() {
        return Set.of(IndentedCodeBlock.class);
    }
    @Override
    public void render(Node node) {
        IndentedCodeBlock codeBlock = (IndentedCodeBlock) node;
        html.line();
        html.tag("pre");
        html.text(codeBlock.getLiteral());
        html.tag("/pre");
        html.line();
    }
}

在上面的代码中,我们实现了 NodeRenderer 接口,并通过 getNodeTypes() 方法指定该渲染器处理 IndentedCodeBlock 节点。

然后,在 render() 方法中,我们使用 HtmlWriter 手动生成代码块的 HTML 输出。

接下来,将自定义渲染器注册到 HtmlRenderer

$ java
public static String customizingHtmlRendering(String source) {
    Parser parser = Parser.builder()
      .build();
    Node node = parser.parse(source);
    HtmlRenderer renderer = HtmlRenderer.builder()
      .nodeRendererFactory(IndentedCodeBlockNodeRenderer::new)
      .build();
    return renderer.render(node);
}

这里,我们使用 nodeRendererFactory() 方法自定义渲染器。通过方法引用调用自定义节点。这允许渲染器在 HTML 生成期间将匹配的节点委托给我们的自定义实现。

编写一个单元测试来验证 customizingHtmlRendering() 方法:

$ java
@Test
void givenIndentedCodeBlock_whenRenderingHtml_thenUseCustomNodeRenderer() {
    String html = customizingHtmlRendering("Example:\n\n    code");
    assertEquals("<p>Example:</p>\n<pre>code\n</pre>\n", html);
}

这里,我们验证了缩进代码块使用了我们的自定义渲染器实现来渲染。

8. 结论

在本文中,我们学习了如何使用 CommonMark 库将 Markdown 解析为 HTML,以及将 HTML 转换回 Markdown。此外,我们还了解了如何自定义 HTML 属性和节点渲染以实现更高级的处理场景。

与往常一样,示例代码的源代码可在 GitHub 上获取。