之前我寫了一篇關於C#處理Markdown檔案的文章:C#解析Markdown檔案,實現替換圖片連結操作
算是第一次嘗試使用C#處理Markdown檔案,然後最近又把部落格網站的前臺改了一下,目前文章渲染使用Editor.md元件在前端渲染,但這個外掛生成的目錄樹很醜,我魔改了一下換成bootstrap5-treeview元件,好看多了。詳見這篇文章:魔改editormd元件,優化ToC渲染效果
此前我一直想用後端來渲染markdown文章而不得,經過這個操作,思路就開啟了,也就有了本文的C#實現。
依然是使用Markdig庫
這個庫雖然基本沒有檔案,使用全靠猜,但目前沒有好的選擇,只能暫時選這個,我甚至一度萌生了想要重新造輪子的想法,不過由於之前沒做過類似的工作加上最近空閒時間嚴重不足,所以暫時把這個想法打消了。
(或許以後有空真得來重新造個輪子,這Markdig庫沒檔案用得太噁心了)
文章結構是這樣的,篇幅關係只把標題展示出來
## DjangoAdmin
### 一些參考資料
## 介面主題
### SimpleUI
#### 一些相關的參考資料
### django-jazzmin
## 客製化案例
### 新增自定義列
#### 效果圖
#### 實現過程
#### 擴充套件:新增連結
### 顯示進度條
#### 效果圖
#### 實現過程
### 頁面上顯示合計數額
#### 效果圖
#### 實現過程
##### admin.py
##### template
#### 參考資料
### 分許可權的軟刪除
#### 實現過程
##### models.py
##### admin.py
## 擴充套件工具
### Django AdminPlus
### django-adminactions
先讀取
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; }
}
準備工作搞定,開始寫核心程式碼
邏輯跟我前面那篇用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();
}
搞定。
把生成的樹結構列印一下
[
{
"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
}
]
}
]
我把這個功能封裝成一個方法,方便呼叫。
直接上GitHub Gist:https://gist.github.com/Deali-Axy/436589aaac7c12c91e31fdeb851201bf
接下來可以嘗試使用後端來渲染Markdown文章了~