1前言
(昨天那篇排版有點問題,不能忍,今天重發!)
之前我寫了一篇關于C#處理Markdown文檔的文章:C#解析Markdown文檔,實現替換圖片鏈接操作
算是第一次嘗試使用C#處理Markdown文檔,然后最近又把博客網站的前臺改了一下,目前文章渲染使用Editor.md組件在前端渲染,但這個插件生成的目錄樹很丑,我魔改了一下換成bootstrap5-treeview組件,好看多了。詳見這篇文章:魔改editormd組件,優化ToC渲染效果
此前我一直想用后端來渲染markdown文章而不得,經過這個操作,思路就打開了,也就有了本文的C#實現。
2準備工作
依然是使用Markdig庫
這個庫雖然基本沒有文檔,使用全靠猜,但目前沒有好的選擇,只能暫時選這個,我甚至一度萌生了想要重新造輪子的想法,不過由于之前沒做過類似的工作加上最近空閑時間嚴重不足,所以暫時把這個想法打消了。
(或許以后有空真得來重新造個輪子,這Markdig庫沒文檔用得太惡心了)
markdown
文章結構是這樣的,篇幅關系只把標題展示出來
##?DjangoAdmin
###?一些參考資料
##?界面主題
###?SimpleUI
####?一些相關的參考資料
###?django-jazzmin
##?定制案例
###?添加自定義列
####?效果圖
####?實現過程
####?擴展:添加鏈接
###?顯示進度條
####?效果圖
####?實現過程
###?頁面上顯示合計數額
####?效果圖
####?實現過程
#####?admin.py
#####?template
####?參考資料
###?分權限的軟刪除
####?實現過程
#####?models.py
#####?admin.py
##?擴展工具
###?Django?AdminPlus
###?django-adminactions
Markdig庫
先讀取
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
第一個是代表一個標題元素,父子關系的標題使用 id
和 pid
關聯
class?Heading?{public?int?Id?{?get;?set;?}public?int?Pid?{?get;?set;?}?=?-1;public?string??Text?{?get;?set;?}public?int?Level?{?get;?set;?}
}
第二個是代表一個樹節點,類似鏈表結構
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;?}
}
準備工作搞定,開始寫核心代碼
3關鍵代碼
邏輯跟我前面那篇用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關系,推出父子關系,使用 ?id
和 pid
關聯
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?=?$"#{a.Text}",?Nodes?=?GetNodes(a.Id)}).ToList();
}
搞定。
4實現效果
把生成的樹結構打印一下
[{"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}]}
]
5完整代碼
我把這個功能封裝成一個方法,方便調用。
直接上GitHub Gist:https://gist.github.com/Deali-Axy/436589aaac7c12c91e31fdeb851201bf
接下來可以嘗試使用后端來渲染Markdown文章了~