動態

詳情 返回 返回

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渲染器

// 構建解析器
Parser parser = ParserBuilder.builder().build();
// 構建渲染器
Renderer renderer = RendererBuilder.builder().build(HtmlRenderer.class);

加入自定義塊級元素解析器或者行級元素的解析器:

Parser parser = ParserBuilder.builder()
                .addDelimiter("_")
                .addBlockParser(new ParagraphParserHandler())
                .addInlineParser("_", new ItalicParser())
                .build();

除此之外,程序會在啓動時,掃描org.ytymark.parser包中帶有註解BlockParserHandlerType的類,所以還可以通過註解加入新的塊級元素解析器。

比如表格解析器:只需要在塊解析器類上面加入這個註解和對應的枚舉類即可

// 枚舉類
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包。

image

2.3. 責任鏈模式

按優先級匹配不同,處理複雜的塊級元素解析及嵌套解析。

在正式進行塊級元素解析前,狀態模式將元素文本處理成塊數據集合,這就像流水線上簡單的打了包,但並不區分包裹裏面是什麼內容。接着將這些包裹丟上流水線(責任鏈)上,責任鏈根據程序初始化時定好的順序,逐一檢測包裹裏的內容是什麼,匹配得上的就直接丟給機器處理(解析),最終給包裹打上標籤(包裝成節點對象)。對應包裹裏還有包裹的,便繼續丟迴流水線上進行打標籤。

整個處理流程,如圖:

image

塊解析的代碼

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

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

    }

2.4. 策略模式

動態選擇解析器完成行級元素的解析

塊級元素解析完成後,會形成塊節點的節點樹,再進行行級元素解析

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;
    }

行級元素並不是所有塊元素都需要進行處理,目前只對標題和段落塊節點進行解析,因為其它塊級元素的內容最終會通過段落節點進行保存。

根據語法特點,動態選擇解析器完成行級元素的解析,關鍵代碼如下:

// 檢查字符對或單個字符,選擇對應的解析器
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包。

通過迭代器結合遞歸來遍歷節點樹,在解析階段,用於遍歷塊級元素進行行內元素解析。

使用迭代器完成兄弟節點的遍歷(廣度遍歷),再結合遞歸完成子節點遍歷(深度遍歷),具體源碼如下:

/**
 * 行級元素解析
 * @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節點樹,解耦解析器和渲染器,使得一次解析可以靈活地渲染成不同的文檔。為了將低耦合,常常會在兩者間多加一層。

image

3.2. 迭代器模式

通過迭代器結合遞歸來遍歷節點樹,在渲染階段,遍歷節點樹完成渲染操作。

/**
 * 循環渲染兄弟節點
 *    在實現這個抽象類的渲染器中,如果存在子節點行為就需要調用這個方法實現遞歸遍歷子節點
 */
protected void renderChildren(Node parent) {
    Iterator<Node> iterator = parent.createIterator();
    while (iterator.hasNext()) {
        // 獲取下一個兄弟節點
        Node next = iterator.next();
        // 渲染節點
        next.render(this);
    }
}

3.3. 訪問者模式

負責分離Node節點數據與渲染操作行為,提高渲染的擴展性。在每個節點類中,實現渲染邏輯時只需要編寫以下代碼:

@Override
public void render(Renderer renderer) {
    renderer.render(this);
}

將渲染邏輯抽離出來,由渲染器接口實現類來實現具體的渲染邏輯,不同的實現類對應不同的渲染行為,目前只實現了HTML的渲染。
在構建器中選擇渲染器類型:

Renderer renderer = RendererBuilder.builder().build(HtmlRenderer.class);

解決渲染的擴展性(多樣性)問題

如果需要將markdown文本渲染成普通文本,則只需要繼承AbstractRenderer 抽象類,實現Renderer接口中所有方法即可。並且實現渲染邏輯非常簡單,只需要關注當前節點要做的事情即可。

比如,表格最外層的渲染源碼

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

而兄弟節點(廣度遍歷)和嵌套子節點(深度遍歷),只需要調用抽象類AbstractRenderer的 renderChildren(Node parent)方法即可完成渲染,使得渲染邏輯只需要關注當前節點的行為即可。比如上面的表格渲染代碼renderChildren(tableNode);

image

🚀4. 項目亮點

  • 💡 高度模塊化,任何 Markdown 語法都能獨立添加/修改。

  • 🧠 設計模式實戰,適合做設計模式學習的項目。

  • 🖥️ 可按需獲取,用户界面和文本解析渲染分為兩個模塊

    • 只使用用户界面源碼,然後輕鬆切換成熟的解析器依賴,開發一個完整的markdown文本編輯器;

    • 僅學習文本解析渲染模塊源碼,不用關注用户界面源碼。

  • 🧪 解析性能毫秒級,確保解析效率。

  • 🎯 輕鬆上手,使用JDK8 自帶JavaFX模塊,無需做額外處理。

  • 📦 開源項目,文檔完善,方便學習和貢獻。

✏️5. 總結

markdown 文本解析和渲染將多種設計模式融入到實際應用中,是一次系統性的 設計模式實踐架構設計實踐

更多詳細內容可以前往筆者微信公眾號回覆:設計模式,來獲取,後續有關設計模式的新資料都可以從這個入口獲取到。

  • 秘籍1設計模式手冊:《掌握設計模式:23種經典模式實踐、選擇、價值與思想》

  • 秘籍2練手項目:設計模式實戰項目--markdown文本編輯器軟件開發(已開源

圖片

查看往期設計模式文章的:設計模式

超實用的SpringAOP實戰之日誌記錄

2023年下半年軟考考試重磅消息

通過軟考後卻領取不到實體證書?

計算機算法設計與分析(第5版)

Java全棧學習路線、學習資源和麪試題一條龍

軟考證書=職稱證書?

軟考中級--軟件設計師毫無保留的備考分享

三連支持!!!

user avatar free_like_bird 頭像 vksfeng 頭像
點贊 2 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.