markdown文本编辑器--核心功能(解析和渲染)

🙌开源项目地址

🌍 GitHub 开源地址(YtyMark-java)

欢迎提交 PR、Issue、Star ⭐️!

1. 简述

YtyMark-java项目分为两大模块:

  • UI界面(ytyedit-mark)

  • markdown文本解析和渲染(ytymark)

本文主要内容为核心模块--markdown文本解析和渲染

关于markdown文本解析器怎么设计,渲染器怎么实现,怎么解耦解析和渲染。在这整个流程中,如果通过设计模式实现高内聚低耦合,可重用,易于阅读,易于扩展,易于维护等。

该模块的主要目录结构:

复制代码
YtyMark-java
├── ytymark/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ ├── annotation/ # 自定义注解
│ │ │ │ ├── enums/ # 枚举值
│ │ │ │ ├── node/ # 树节点(块级和行级节点)
│ │ │ │ ├── parser/ # 解析器(块级和行级元素)
│ │ │ │ ├── renderer/ # 渲染器(块级和行级元素)
│ │ │ └── resources/
│ ├── README.md
│ └──pom.xml

2.解析器

目标:将 Markdown 文本解析为节点树。

使用到的设计模式

  • 构建者模式 :创建复杂解析器渲染器

  • 状态模式:对markdown文本不同语法做一些前置处理,裁剪成块级元素。

  • 责任链模式 :按优先级匹配不同,处理复杂的块级元素解析及嵌套解析。

  • 策略模式 :动态选择解析器完成行内元素的解析

  • 组合模式:表示 Markdown 语法结构(如段落、标题、列表)之间的树形结构。

  • 迭代器模式 :通过迭代器结合递归来遍历节点树,遍历块级元素进行行内元素解析。

根据使用顺序逐一讲述。

2.1. 构建者模式

通过构建者模式来创建复杂的解析器渲染器 ,包括自定义解析器的加入。

最简单的解析器(默认支持的语法解析器)和HTML渲染器

java 复制代码
// 构建解析器
Parser parser = ParserBuilder.builder().build();
// 构建渲染器
Renderer renderer = RendererBuilder.builder().build(HtmlRenderer.class);

加入自定义块级元素解析器或者行级元素的解析器:

java 复制代码
Parser parser = ParserBuilder.builder()
                .addDelimiter("_")
                .addBlockParser(new ParagraphParserHandler())
                .addInlineParser("_", new ItalicParser())
                .build();

除此之外,程序会在启动时,扫描org.ytymark.parser包中带有注解BlockParserHandlerType的类,所以还可以通过注解加入新的块级元素解析器。

比如表格解析器:只需要在块解析器类上面加入这个注解和对应的枚举类即可

java 复制代码
// 枚举类
public enum BlockParserHandlerEnum {
    TABLE("TABLE",6),
// 通过注解加入块级元素解析器
@BlockParserHandlerType(type = BlockParserHandlerEnum.TABLE)
public class TableParserHandler extends AbstractBlockParser implements ParserHandler {

2.2. 状态模式

对markdown文本不同语法做一些前置处理,裁剪成块级元素。

在正式进行块级元素解析前,对原始markdown文本进行分割,分割成一块一块的。

由于markdown语法很多,不进行一些设计,那将是一坨难以阅读理解、难以维护和扩展的代码。

通过状态模式实现类似状态机的机制,当状态(语法)匹配时,自动流转到专门处理这个语法的程序,处理完之后分割成一个"块"(这个块就是一个块元素),再回到默认状态,然后继续处理后续的文本。具体代码位于:org.ytymark.parser.block.state包。

2.3. 责任链模式

按优先级匹配不同,处理复杂的块级元素解析及嵌套解析。

在正式进行块级元素解析前,状态模式将元素文本处理成块数据集合,这就像流水线上简单的打了包,但并不区分包裹里面是什么内容。接着将这些包裹丢上流水线(责任链)上,责任链根据程序初始化时定好的顺序,逐一检测包裹里的内容是什么,匹配得上的就直接丢给机器处理(解析),最终给包裹打上标签(包装成节点对象)。对应包裹里还有包裹的,便继续丢回流水线上进行打标签。

整个处理流程,如图:

块解析的代码

java 复制代码
    public void parser(String text, Node node) {
        List<String> blocks = this.splitBlock(text);

        // 逐块处理文本
        for (String block : blocks) {
            blockParserChain.parser(block, node);
        }

    }

2.4. 策略模式

动态选择解析器完成行级元素的解析

块级元素解析完成后,会形成块节点的节点树,再进行行级元素解析

java 复制代码
public Node parse(String markdownText) {
        Node root = new DocumentNode();
        // 统一换行符,替换所有 \r\n 或 \r 为 \n
        String normalizedText = markdownText.replaceAll("\r\n|\r", "\n");

        // 块级元素解析
        blockParserContext.parser(normalizedText, root);

        // 行级元素解析
        this.parseInlines(root);

        return root;
    }

行级元素并不是所有块元素都需要进行处理,目前只对标题和段落块节点进行解析,因为其它块级元素的内容最终会通过段落节点进行保存。

根据语法特点,动态选择解析器完成行级元素的解析,关键代码如下:

java 复制代码
// 检查字符对或单个字符,选择对应的解析器
String possibleDelimiter = this.getPossibleDelimiter(line, i);
InlineParser inlineParser = inlineParserMap.get(possibleDelimiter);

if (inlineParser != null) {
    // 找到合适地解析器,尝试解析
    InlineNode inlineNode = inlineParser.parser(sourceLine, this);
    if(inlineNode!=null){
        node.addChildNode(inlineNode);
    }
}

2.5. 组合模式和迭代器模式

表示 Markdown 语法结构(如段落、标题、列表)之间的树形结构,每个语法对应一个Node节点,在块级元素和行级元素的解析过程,最终组合成节点树。节点和迭代器源码位于org.ytymark.node包。

通过迭代器结合递归来遍历节点树,在解析阶段,用于遍历块级元素进行行内元素解析。

使用迭代器完成兄弟节点的遍历(广度遍历),再结合递归完成子节点遍历(深度遍历),具体源码如下:

java 复制代码
/**
 * 行级元素解析
 * @param parent 父节点
 */
@Override
public void parseInlines(Node parent) {
    Iterator<Node> iterator = parent.createIterator();
    while (iterator.hasNext()) {
        // 获取下一个兄弟节点
        Node node = iterator.next();
        // 解析子节点行
        if(node instanceof ParagraphNode){
            inlineParserContext.parser(((ParagraphNode) node).getText(),node);
        }
        if(node instanceof HeadingNode) {
            inlineParserContext.parser(((HeadingNode) node).getText(), node);
        }

        if(node.getFirstChild()!=null)
            parseInlines(node);
    }
}

3. 渲染器

目标:将 AST 语法树渲染为 HTML 文本预览。

使用到的设计模式

  • 中介者模式思想 :加入AST节点树解耦 解析器和渲染器,使其灵活地渲染成不同的文档。

  • 迭代器模式 :通过迭代器结合递归来遍历节点树,比如遍历节点树完成渲染操作。

  • 访问者模式 :负责分离节点数据与渲染操作,提高渲染的扩展性。

3.1. 中介者模式思想

在解析和渲染中间加入AST节点树,解耦 解析器和渲染器,使得一次解析可以灵活地渲染成不同的文档。为了将低耦合,常常会在两者间多加一层。

3.2. 迭代器模式

通过迭代器结合递归来遍历节点树,在渲染阶段,遍历节点树完成渲染操作。

java 复制代码
/**
 * 循环渲染兄弟节点
 *    在实现这个抽象类的渲染器中,如果存在子节点行为就需要调用这个方法实现递归遍历子节点
 */
protected void renderChildren(Node parent) {
    Iterator<Node> iterator = parent.createIterator();
    while (iterator.hasNext()) {
        // 获取下一个兄弟节点
        Node next = iterator.next();
        // 渲染节点
        next.render(this);
    }
}

3.3. 访问者模式

负责分离Node节点数据与渲染操作行为,提高渲染的扩展性。在每个节点类中,实现渲染逻辑时只需要编写以下代码:

java 复制代码
@Override
public void render(Renderer renderer) {
    renderer.render(this);
}

将渲染逻辑抽离出来,由渲染器接口实现类来实现具体的渲染逻辑 ,不同的实现类对应不同的渲染行为,目前只实现了HTML的渲染。

在构建器中选择渲染器类型:

java 复制代码
Renderer renderer = RendererBuilder.builder().build(HtmlRenderer.class);

解决渲染的扩展性(多样性)问题

如果需要将markdown文本渲染成普通文本 ,则只需要继承AbstractRenderer 抽象类,实现Renderer接口中所有方法即可。并且实现渲染逻辑非常简单,只需要关注当前节点要做的事情即可。

比如,表格最外层的渲染源码

java 复制代码
@Override
public void render(TableNode tableNode) {
    sbHTML.append("<table>\n");
    renderChildren(tableNode);
    sbHTML.append("</table>\n");
}

而兄弟节点(广度遍历)和嵌套子节点(深度遍历),只需要调用抽象类AbstractRendererrenderChildren(Node parent)方法即可完成渲染,使得渲染逻辑只需要关注当前节点的行为即可。比如上面的表格渲染代码renderChildren(tableNode);

🚀4. 项目亮点

  • 💡 高度模块化,任何 Markdown 语法都能独立添加/修改。

  • 🧠 设计模式实战,适合做设计模式学习的项目。

  • 🖥️ 可按需获取,用户界面和文本解析渲染分为两个模块

    • 只使用用户界面源码,然后轻松切换成熟的解析器依赖,开发一个完整的markdown文本编辑器;

    • 仅学习文本解析渲染模块源码,不用关注用户界面源码。

  • 🧪 解析性能毫秒级,确保解析效率。

  • 🎯 轻松上手,使用JDK8 自带JavaFX模块,无需做额外处理。

  • 📦 开源项目,文档完善,方便学习和贡献。

✏️5. 总结

markdown 文本解析和渲染将多种设计模式融入到实际应用中,是一次系统性的 设计模式实践架构设计实践

更多详细内容可以前往笔者微信公众号回复:设计模式,来获取,后续有关设计模式的新资料都可以从这个入口获取到。

  • 秘籍1设计模式手册:《掌握设计模式:23种经典模式实践、选择、价值与思想》

  • 秘籍2 练手项目:设计模式实战项目--markdown文本编辑器软件开发(已开源

查看往期设计模式文章的:设计模式

超实用的SpringAOP实战之日志记录

2023年下半年软考考试重磅消息

通过软考后却领取不到实体证书?

计算机算法设计与分析(第5版)

Java全栈学习路线、学习资源和面试题一条龙

软考证书=职称证书?

软考中级--软件设计师毫无保留的备考分享

三连支持!!!

相关推荐
东阳马生架构2 分钟前
商品中心—1.B端建品和C端缓存的技术文档
java
Chan165 分钟前
【 SpringCloud | 微服务 MQ基础 】
java·spring·spring cloud·微服务·云原生·rabbitmq
LucianaiB8 分钟前
如何做好一份优秀的技术文档:专业指南与最佳实践
android·java·数据库
面朝大海,春不暖,花不开32 分钟前
自定义Spring Boot Starter的全面指南
java·spring boot·后端
得过且过的勇者y32 分钟前
Java安全点safepoint
java
夜晚回家1 小时前
「Java基本语法」代码格式与注释规范
java·开发语言
斯普信云原生组1 小时前
Docker构建自定义的镜像
java·spring cloud·docker
wangjinjin1801 小时前
使用 IntelliJ IDEA 安装通义灵码(TONGYI Lingma)插件,进行后端 Java Spring Boot 项目的用户用例生成及常见问题处理
java·spring boot·intellij-idea
wtg44521 小时前
使用 Rest-Assured 和 TestNG 进行购物车功能的 API 自动化测试
java
白宇横流学长2 小时前
基于SpringBoot实现的大创管理系统设计与实现【源码+文档】
java·spring boot·后端