C#實現生成Markdown文檔目錄樹

前言之前我寫了一篇關于C#處理Markdown文檔的文章:C#解析Markdown文檔 , 實現替換圖片鏈接操作
算是第一次嘗試使用C#處理Markdown文檔,然后最近又把博客網站的前臺改了一下 , 目前文章渲染使用Editor.md組件在前端渲染,但這個插件生成的目錄樹很丑,我魔改了一下換成bootstrap5-treeview組件,好看多了 。詳見這篇文章:魔改editormd組件,優化ToC渲染效果
此前我一直想用后端來渲染markdown文章而不得 , 經過這個操作 , 思路就打開了,也就有了本文的C#實現 。
準備工作依然是使用Markdig庫
這個庫雖然基本沒有文檔 , 使用全靠猜,但目前沒有好的選擇,只能暫時選這個 , 我甚至一度萌生了想要重新造輪子的想法,不過由于之前沒做過類似的工作加上最近空閑時間嚴重不足,所以暫時把這個想法打消了 。
(或許以后有空真得來重新造個輪子,這Markdig庫沒文檔用得太惡心了)
markdown文章結構是這樣的,篇幅關系只把標題展示出來
## DjangoAdmin### 一些參考資料## 界面主題### SimpleUI#### 一些相關的參考資料### django-jazzmin## 定制案例### 添加自定義列#### 效果圖#### 實現過程#### 擴展:添加鏈接### 顯示進度條#### 效果圖#### 實現過程### 頁面上顯示合計數額#### 效果圖#### 實現過程##### admin.py##### template#### 參考資料### 分權限的軟刪除#### 實現過程##### models.py##### admin.py## 擴展工具### Django AdminPlus### django-adminactionsMarkdig庫先讀取
var md = File.ReadAllText(filepath);var document = Markdown.Parse(md);得到document對象之后,就可以對里面的元素進行遍歷,Markdig把markdown文檔處理成一個一個的block,通過這樣遍歷就可以處理每一個block
foreach (var block in document.AsEnumerable()) {// ...}不同的block類型在 Markdig.Syntax 命名空間下,通過 Assemblies 瀏覽器可以看到,根據字面意思,我找到了 HeadingBlock ,試了一下,確實就是代表標題的 block 。
那么判斷一下 , 把無關的block去掉
foreach (var block in document.AsEnumerable()) { if (block is not HeadingBlock heading) continue;// ...}這一步就搞定了
定義結構需要倆class
第一個是代表一個標題元素,父子關系的標題使用 idpid 關聯
class Heading {public int Id { get; set; }public int Pid { get; set; } = -1;public string? Text { get; set; }public int Level { get; set; }}【C#實現生成Markdown文檔目錄樹】第二個是代表一個樹節點,類似鏈表結構
public class TocNode {public string? Text { get; set; }public string? Href { get; set; }public List<string>? Tags { get; set; }public List<TocNode>? Nodes { get; set; }}準備工作搞定,開始寫核心代碼
關鍵代碼邏輯跟我前面那篇用JS實現的文章是一樣的
遍歷標題block , 添加到一個列表中
foreach (var block in document.AsEnumerable()) {if (block is not HeadingBlock heading) continue;var item = new Heading {Level = heading.Level, Text = heading.Inline?.FirstChild?.ToString()};headings.Add(item);Console.WriteLine($"{new string('#', item.Level)} {item.Text}");}根據不同block的位置、level關系,推出父子關系,使用idpid 關聯
for (var i = 0; i < headings.Count; i++) {var item = headings[i];item.Id = i;for (var j = i; j >= 0; j--) {var preItem = headings[j];if (item.Level == preItem.Level + 1) {item.Pid = j;break;}}}最后用遞歸生成樹結構
List<TocNode>? GetNodes(int pid = -1) {var nodes = headings.Where(a => a.Pid == pid).ToList();return nodes.Count == 0 ? null: nodes.Select(a => new TocNode {Text = a.Text, Href = https://www.huyubaike.com/biancheng/$"#{a.Text}", Nodes = GetNodes(a.Id)}).ToList();}搞定 。
實現效果把生成的樹結構打印一下
[{"Text": "DjangoAdmin","Href": "#DjangoAdmin","Tags": null,"Nodes": [{"Text": "一些參考資料","Href": "#一些參考資料","Tags": null,"Nodes": null}]},{"Text": "界面主題","Href": "#界面主題","Tags": null,"Nodes": [{"Text": "SimpleUI","Href": "#SimpleUI","Tags": null,"Nodes": [{"Text": "一些相關的參考資料","Href": "#一些相關的參考資料","Tags": null,"Nodes": null}]},{"Text": "django-jazzmin","Href": "#django-jazzmin","Tags": null,"Nodes": null}]},{"Text": "定制案例","Href": "#定制案例","Tags": null,"Nodes": [{"Text": "添加自定義列","Href": "#添加自定義列","Tags": null,"Nodes": [{"Text": "效果圖","Href": "#效果圖","Tags": null,"Nodes": null},{"Text": "實現過程","Href": "#實現過程","Tags": null,"Nodes": null},{"Text": "擴展:添加鏈接","Href": "#擴展:添加鏈接","Tags": null,"Nodes": null}]},{"Text": "顯示進度條","Href": "#顯示進度條","Tags": null,"Nodes": [{"Text": "效果圖","Href": "#效果圖","Tags": null,"Nodes": null},{"Text": "實現過程","Href": "#實現過程","Tags": null,"Nodes": null}]},{"Text": "頁面上顯示合計數額","Href": "#頁面上顯示合計數額","Tags": null,"Nodes": [{"Text": "效果圖","Href": "#效果圖","Tags": null,"Nodes": null},{"Text": "實現過程","Href": "#實現過程","Tags": null,"Nodes": [{"Text": "admin.py","Href": "#admin.py","Tags": null,"Nodes": null},{"Text": "template","Href": "#template","Tags": null,"Nodes": null}]},{"Text": "參考資料","Href": "#參考資料","Tags": null,"Nodes": null}]},{"Text": "分權限的軟刪除","Href": "#分權限的軟刪除","Tags": null,"Nodes": [{"Text": "實現過程","Href": "#實現過程","Tags": null,"Nodes": [{"Text": "models.py","Href": "#models.py","Tags": null,"Nodes": null},{"Text": "admin.py","Href": "#admin.py","Tags": null,"Nodes": null}]}]}]},{"Text": "擴展工具","Href": "#擴展工具","Tags": null,"Nodes": [{"Text": "Django AdminPlus","Href": "#Django AdminPlus","Tags": null,"Nodes": null},{"Text": "django-adminactions","Href": "#django-adminactions","Tags": null,"Nodes": null}]}]

推薦閱讀