当前位置:首页 > 文章 > 正文内容

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

廖万里3年前 (2022-10-27)文章57280

前言

之前我写了一篇关于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-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; }
}

准备工作搞定,开始写核心代码

关键代码

逻辑跟我前面那篇用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文章了~前言准备工作    markdown    Markdig库    定义结构关键代码实现效果完整代码


本文链接:https://www.kkkliao.cn/?id=159 转载需授权!

分享到:

版权声明:本文由廖万里的博客发布,如需转载请注明出处。


“C#实现生成Markdown文档目录树” 的相关文章

开始“反扑”了?中国院士正式宣布,厉害的不只有华为

开始“反扑”了?中国院士正式宣布,厉害的不只有华为

随着中国的这些年的不断发展,中国在各方都取得了很大的成就,现在的中国已经发生了翻天覆地的变化。在生活上。我们进入了电商时代,进入了移动支付时代;在科技上,我们进入了智能互联网时代,即将迎来的是全新的5G时代,还有很多很多中国制造都是值得我们骄傲的。特别值得提及的就是,现在中国制造已经成为了我们的一个...

如何让自己的努力更有效率?

如何让自己的努力更有效率?

收到了某个朋友发来的困惑咨询,抽象出来后整理出如下问题:为何自己很努力但觉得没有成长,做了很多事情却感觉没有核心竞争力,有浑身的精力不知道该往何处发力,应该如何破局?我是一名技术型产品经理,已经工作了3年,但是感觉自己陷入了成长迷茫期。 团队很重视技术,我花了很多时间来弥补技术知识,但是发现干不过研...

圆周率已经算到了62.8万亿位,再算下去有什么意义?

圆周率已经算到了62.8万亿位,再算下去有什么意义?

综述大家知道,圆的周长与直径的比值就是圆周率,用希腊字母表示也就是π。π是一个无限不循环的无理数,在小数点之后存在着无限多个数字,并且不会循环。令人惊讶的是,就算已经证明了π的小数点之后的数字是无穷的,仍然有不少科学家们想要把它继续算下去。最新成果显示,瑞士的一名研究人员利用一台超级计算机,耗时19...

一直瘦的人有什么共同的习惯?

一直瘦的人有什么共同的习惯?

抹拉汪09月06日关注我从170斤减到了118斤,维持了两年多到现在了,心得颇多。1、每天早上上称,数据不会骗人。2、每天固定8杯白开水,保证自己喝到2500ML的水量,促进代谢。3、一天三顿饭,尤其是早饭,一定会好好对待,白煮蛋、牛奶和一个小馒头,再配点两口能吃完的水果。4、尽量每天自己带饭去单位...

每天坚持看书有什么变化?对人有什么影响?

每天坚持看书有什么变化?对人有什么影响?

高中毕业,实际初中水平的我,却有上千本书,这还不是我书最多的时候。现在每天读书2-3个小时.打算这辈子好好研究透,读它十几遍。我饭可以不吃饭,但绝对不能一天不读书。之所以爱书如命,是因为一本书救了我,并且改变了我的后半生。你相信吗?我现在把300多万债务还清,而且能靠写作养活自己,交易开始有点起色,...

Linux中Vi和Vim编辑器用法详解

Linux中Vi和Vim编辑器用法详解

目录一、简介二、安装VIM美化vim三、进入 vi\VIM 编辑器四、工作模式三种工作模式:五、退出 vi 编辑器六、移动光标七、控制命令八、编辑文件撤销命令:重复命令:九、删除字符十、修改文本十一、粘贴复制十二、高级命令十三、文本查找全文件搜索部分特殊字符列表:搜索某行中的单个字符十四、set 命...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。