首页 > 其他分享 >从 markdown 中生成目录

从 markdown 中生成目录

时间:2023-01-02 16:22:36浏览次数:30  
标签:markdown subtitle utf8 number 目录 标题 生成 id

目录

为 markdown 写的文章生成目录,使其在博客园上可用。

完整代码

use v5.12;
use utf8;
use open ':utf8';
use open ':std', ':utf8';

my @subtitle_number;
say "# 目录";
while (<>) {
    next if /^```/ ... /^```/;
    if (/^(#+)\s*(.*?)\s*$/) {
        my ($level, $title) = (length($1), $2);

        my $indent = "  " x $level;

        my $id = $title;
        $id =~ s/[^_[:^punct:]]//g;
        $id =~ s/[[:space:]]/-/g;
        $id = lc $id;

        @subtitle_number = splice @subtitle_number, 0, $level;
        $subtitle_number[$level - 1] += 1;
        my $subtitle_number = join ".", @subtitle_number;

        say "$indent+ $subtitle_number [$title](#$id)";
    }
}
say "";

这是一个 Perl 脚本,从 stdin 或者参数中读取文章,输出一份 markdown 代码,是文章的目录。可以直接复制粘贴使用,也可以和其他工具集成使用。

使用示例

使用这样的命令:

$ perl toc.pl main.md

可以得到这样的结果:

# 目录
  + 1 [完整代码](#完整代码)
  + 2 [使用示例](#使用示例)
  + 3 [原理](#原理)
    + 3.1 [HTML 的链接语法](#html-的链接语法)
    + 3.2 [markdown 列表缩进](#markdown-列表缩进)
  + 4 [代码详解](#代码详解)
    + 4.1 [使用「现代」Perl](#使用现代perl)
    + 4.2 [支持 utf8 编码](#支持-utf8-编码)
    + 4.3 [主循环](#主循环)
      + 4.3.1 [跳过 markdown 的代码片段](#跳过-markdown-的代码片段)
      + 4.3.2 [匹配标题](#匹配标题)
      + 4.3.3 [设置缩进](#设置缩进)
      + 4.3.4 [从标题名字中获得其 id](#从标题名字中获得其-id)
      + 4.3.5 [获取标题的编号](#获取标题的编号)

原理

HTML 的链接语法

在大多数网页上,markdown 的链接语法会被编译成 HTML 的 <a> 标签。通常 <a> 标签会有 href 属性,内容是点击标签时跳转的目的地址。

有些页内元素带有 id 属性,比如这个例子:

<h3 id="interactive-shell">interactive shell</h3>

在这个例子里 <h3> 标签有 id 属性,值是 interactive-shell。这个值同样可以用作 <a> 标签的目的地址。

当目的地址是页内元素的 id 时,点击 <a> 标签时便会跳转到该元素的位置。博客园给每个标题都自动分配了一个 id,利用这几点,就可以实现「点击目录项目跳转到对应章节」的功能。

markdown 列表缩进

在 markdown 中,列表以 + -* 开头。如果这些符号前面有空白字符,那么这些空白字符会被当成缩进,最终会体现在列表展示结果上,缩进越多的列表项目展示时会越靠右,缩进相同的列表项目会左对齐。利用这一点,可以实现目录的层次结构。

代码详解

使用「现代」Perl

use v5.12;

Perl 是个老古董语言,为了保持兼容性,有许多好玩/有用的特性默认没有打开。不过我们可以使用 use vX.YY 的 pragma 来指定自己想使用的 Perl 的版本号,从而开启这些好玩的特性。

支持 utf8 编码

use utf8;
use open ':utf8';
use open ':std', ':utf8';

同上,因为 Perl 是个老古董语言,所以默认全世界都用 ASCII 编码。我们要开启它对 utf8 的支持。

这里第一行是让 Perl 用 utf8 的方式来解释这份源代码(有点像 python2 里面的 # -*- coding: utf-8 -*- 的 pragma)。

第二行是让 Perl 读所有文件时,读后解码 utf8;写所有文件时,写前编码 utf8。Perl 中为了方便数据处理,存在 IO Layer 的概念。layer 可以看做数据的转换器,数据在进行输入/输出时,会经过这些 layer 逐层处理。常用的 layer 有 :crlf(读时将 CR-LF 序列转换成 CR,写时反过来,用来对付 Windows 系统)和 :encoding(用来编解码文件)。还有些邪恶的 layer 可以实现自动压缩解压、base16 编码等功能。所以有时遇到输出到 stdout 和输出到文件中,内容不一致的情况,可以检查一下是不是用的 layer 不同造成的。

第三行是在设置 stdio 的 layer。因为 stdio 在 Perl 程序运行前就已经打开了,所以需要单独设置一下。

主循环

就是那个巨大的 while 循环。它每次会从输入中读取一行数据并放到 $_ 里面,直到读到文件结束。

while (<>) {

可以发现我们并没有处理命令行参数,这是因为 <> 这个操作符会替我们完成这项工作。<> 操作符的意思是,如果有命令行参数,那么就把命令行参数当做文件名打开文件,并且将文件内容作为输入;否则就把 stdin 作为输入。每调用一次 <> 操作符会读取一行,返回这一行的内容。如果没有变量来接收 <> 操作符的返回值,那么 <> 操作符会把返回值存在特殊变量 $_ 中。

跳过 markdown 的代码片段

    next if /^```/ ... /^```/;

这一行用来跳过 markdown 的代码片断。是一种被称为 flip-flop 的语法。上面代码的意思是,「如果在两个代码标记之间,那么执行 next 语句」。大概和下面的东西等价:

# 这句在循环外头
my $in_codeblock = 0;

# 这下面的在循环里头
if ($in_codeblock) {
    next;
}

if (/^```/ && $in_codeblock == 0) {
    $in_codeblock = 1;
}

if (/^```/ && $in_codeblock == 1) {
    $in_codeblock = 0;
}

flip-flop 是一种很方便的语法,可以让人少写很多代码。最重要的是不需要对那一堆烦人的标志变量命名了。

匹配标题

用一个正则表达式来匹配标题并且获得需要的信息:

    if (/^(#+)\s*(.*?)\s*$/) {
        my ($level, $title) = (length($1), $2);

这个意思是,如果遇到「开头是若干个 #,中间有一堆字符」这种模式,就认为匹配到标题了。$level$title 分别是标题的层级和名称。因为正则表达式在 Perl 中用的特别多,所以直接做进语言里面去了,可以随手写,不需要另外调库。

设置缩进

        my $indent = "  " x $level;

$level 总是个整数。这里用字符串重复操作符 x,来获得与 $level 成正比的缩进长度。

从标题名字中获得其 id

        my $id = $title;
        $id =~ s/[^_[:^punct:]]//g;
        $id =~ s/[[:space:]]/-/g;
        $id = lc $id;

博客园会根据标题名称来设置其 HTML 标签的 id。有人托梦告诉我说,id 就是标题去掉所有标点符号但是保留下划线 _,把空白字符换成连字符 -,并且把所有字母变为小写之后的结果。所以用正则表达式写了一个。

获取标题的编号

        @subtitle_number = splice @subtitle_number, 0, $level;
        $subtitle_number[$level - 1] += 1;
        my $subtitle_number = join ".", @subtitle_number;

生成的目录里面会有类似 X.Y.Z.W 这样的标题编号。这一部分代码就用来处理标题编号的生成问题。懒得写了……

标签:markdown,subtitle,utf8,number,目录,标题,生成,id
From: https://www.cnblogs.com/jyi2ya/p/17020058.html

相关文章

  • 生成对抗网络GANs的用途
    简介如果说目前深度学习最火,应用最多的领域,莫过于GAN--GenerativeAdversarialNetwork,翻译过来就是生成对抗网络,单单从名字上看,你会觉得它就是一个生成模型,看起来就是用于......
  • 分布式 id 生成器(雪花算法)
    分布式id生成器(雪花算法)有时我们需要能够生成类似MySQL自增ID这样不断增大,同时又不会重复的id。以支持业务中的高并发场景。比较典型的,电商促销时,短时间内会有大量的订......
  • C语言学生成绩管理程序[2023-01-02]
    C语言学生成绩管理程序[2023-01-02]题目一、学生成绩管理程序(学号后三位139-390的选做)任务:利用C语言中相关知识(包括文件,结构体数组等)设计学生成绩管理程序,要求如下:任意......
  • odoo10如何自定义自动生成单据编号
    1.在已有的model中穿件一个字段nameclassqingjiadan(models.Model):_name='qingjia.qingjiadan'name=fields.Char(string='编号',readonly=True)2.创建qingjia_app......
  • Java中动态代理技术生成的类与原始类的区别 (good)
    用动态代理的时候,对它新生成的类长什么样子感到好奇.有幸通过一些资料消除了心里的疑惑.平时工作使用的Spring框架里面有一个AOP(面向切面)的机制,只知道它是把类......
  • 包机制及java生成文档
    包机制为了更好地组织类,Java提供了包机制,用于区别类名的命名空间。包机制的语法格式为:packagepkg1[.pkg2[.pkg3...]];$\color{red}{一般利用公司域名倒置作......
  • 【博学谷学习记录】超强总结,用心分享|狂野架构spring包目录结构以及模块划分
    1.spring核心思想1.ioc​ 将对象的控制交给spring容器管理2.aop​ 面向切面编程,通过切面扩展对象原本不具备的功能。2.spring包目录结构以及模块划分​ Spring......
  • mt19937随机数生成_学习笔记
    好文传送门1好文传送门2使用模板:#include<bits/stdc++.h>usingnamespacestd;mt19937rnd(std::random_device{}());intmain(){for(inti=1;i<=10;i++)......
  • Linux 中 java 访问 windows共享目录
    有两个方案(本文介绍方案1的使用)1、将windows共享目录,挂载到linux系统下,通过使用本地目录访问windows共享目录2、通过samba的java实现包,不过需要开个windows共享目录的账......
  • 使用 Link Cut Tree 维护最小生成树
    简介本文将简单介绍如何使用LinkCutTree维护动态图最小生成树。思路最小生成树的性质:一个基环树的最小生成树,为将环上边权最大的边删除后所组成的树。Proof:如果删......