C#实现生成Markdown文档目录树

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#实现生成Markdown文档目录树相关的知识,希望对你有一定的参考价值。

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

第一个是代表一个标题元素,父子关系的标题使用 idpid 关联

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关系,推出父子关系,使用  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 = $"#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文章了~

以上是关于C#实现生成Markdown文档目录树的主要内容,如果未能解决你的问题,请参考以下文章

markdown写的文档怎么生成左侧目录

Markdown 专用 Web 版目录树生成

docsify 建立目录树脚本

php markdown 接口文档生成工具

Markdown写的文档怎么生成左侧目录

markdown文件生成目录的方式