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

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文档目录树” 的相关文章

同样是系统为什么有的叫OS有的叫UI

同样是系统为什么有的叫OS有的叫UI

大家有没有注意到现在手机系统虽然主要分iOS和安卓两大阵营,但如果从叫法的后缀上来区分的话其实是分为OS和UI。那么,你有没有想过为什么有些手机的系统后缀是OS?有些系统的后缀是UI呢?他们之间的区别在于哪些呢?同样是系统为什么有的叫OS有的叫UI1、字面意思不同OS的英文全称为Operation...

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

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

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

人的寿命?科学家找到了答案,竟和走路速度有关?

人的寿命?科学家找到了答案,竟和走路速度有关?

关于走路每个人都有不同的说法,有些人觉得适度走路能走掉很多疾病,有些人觉得坚持走路会把膝盖走坏,有些人在走路中获得了益处,但有些人却走出了问题。还有人说走路和寿命之间存在联系,那么这种说法是否准确呢?不妨让我们一起来看看科学家的答案。人的寿命竟然跟走路速度有关?科学家找到答案,你走路够快吗?美国匹兹...

“双十一”部分手机价格先涨价再降价?券后价格反而比平时贵

“双十一”部分手机价格先涨价再降价?券后价格反而比平时贵

记者 宗智10月24日晚上八点,“双十一”的“先锋号角”已然奏响。从“光棍节”到全民参与的消费狂欢日,从最初的27个商家到现在来自全球各地的29万个品牌,互联网电商一路高歌猛进,其规模与影响力不断扩大,连续创造了13年“神话”。然而,随着消费者生活水平的提升,平台补贴活动走向日常化,低价早已不再是“...

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

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

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

现在负债的人多吗?负债的朋友有何感受?

怎么说呢,我负债30,之前买股票,赔了,炒原油,赔了,后来开窗口,,也赔了,再后来,买YBK,也赔了,再后来买BTC,做了合约,去年313,爆仓了,彻底崩盘了,总之吧,所有的投资没挣钱,所有的本金,都是刷卡,借呗,微粒贷这么多年,工资也不高,一个月几千块钱除了还房贷,剩余都还贷款了,所以一直滚动,还...

发表评论

访客

看不清,换一张

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