静态博客中的shortcode实现


前言

shortcode,直译过来就是 短代码。 使用过 hugo 的人对此也一定有所了解,shortcode 允许用户使用部分关键参数就可以在文章内部插入设置好的HTML模版内容,而不用写大段的HTML代码,比如插入指定视频或者音乐,使用 shortcode 之前需要使用 iframe

1<iframe frameborder="no" border="0"
2        marginwidth="0" marginheight="0"
3        width=330 height=86
4        src="//music.163.com/outchain/player?type=2&id=xxx&auto=0&height=66"></iframe>

而使用 shortcode 后就可以只用指定关键的 id 参数

1{{<163music xxx>}}

当网易云音乐的分享代码更新后,可以直接更新 shortcode 代码, 而不是搜索替换每一篇包括该iframe代码的文章, 同时 shortcode 也可以扩展 Markdown 的功能,比如文本的居中,居左,居右

1{{<align left>}}
2  文本内容
3  {{</align>}}

实现方式

而想要在静态博客中实现 shortcode 功能, 目前有两种方式

  • 方式一: 修改 Markdown 解析器, 在静态生成器解析文章内容时就可以对 shortcode 进行解析,并将 shortcode 替换成写好的模版,至少,我看到 hugo 就是这么实现的

  • 方式二: 如果不想修改 Markdown 解析器,还可以直接修改生成好的 HTML 内容,这种方式实现原理很简单,Markdown允许插入HTML代码, 那么我们就可以直接在文章中写一个

    1<shortcode _name="163music" id="xxx" />

    然后在现有的 Markdown 解析库解析文章并生成 HTML 后再次对HTML内容进行解析,目前主流语言都有现成的库很方便地解析HTML内容, 下面以 Go 为例

Markdown渲染

比如有一篇文章,我想要添加网易云音乐的某首歌到文章内部,当使用现有的Markdown库对内容进行渲染

 1package main
 2
 3import (
 4    "github.com/russross/blackfriday/v2"
 5)
 6
 7func main() {
 8    content := `# Hello World
 9<shortcode _name="163music" id="xxx" />
10This is one of my favorite **music**.
11  `
12    d := blackfriday.Run(content)
13    fmt.Println(string(d))
14}

输出结果

1<h1>Hello World</h1>
2
3<p><shortcode _name="163music" id="xxx" />
4  This is one of my favorite <strong>music</strong>.</p>

查找HTML标签

可以使用 golang.org/x/net/html 对HTML内容进行解析, 并查找到 shortcode

 1package main
 2
 3import (
 4    "bytes"
 5    "fmt"
 6    "github.com/russross/blackfriday/v2"
 7    "golang.org/x/net/html"
 8    "text/template"
 9)
10
11func main() {
12    // ...
13    var (
14        w bytes.Buffer
15        z = html.NewTokenizer(bytes.NewReader(d))
16    )
17    for {
18        next := z.Next()
19        if next == html.ErrorToken {
20            break
21        }
22
23        token := z.Token()
24        switch next {
25        case html.StartTagToken, html.SelfClosingTagToken:
26            // 找到shortcode标签
27            if token.Data == "shortcode" {
28
29            }
30        }
31        w.WriteString(token.String())
32    }
33    fmt.Println(w.String())
34}

替换HTML

首先把需要替换的 iframe 转换成模版

 1replace := `<iframe
 2frameborder="no" border="0"
 3marginwidth="0" marginheight="0"
 4width=330 height=86
 5src="//music.163.com/outchain/player?type=2&id={{ .id }}&auto=0&height=66"></iframe>`
 6
 7tmpl, err := template.New("test").Parse(replace)
 8if err != nil {
 9    panic(err)
10}

然后根据标签参数替换原来的HTML

 1// 获取该标签的参数
 2attrs := make(map[string]string)
 3for _, attr := range token.Attr {
 4    attrs[attr.Key] = attr.Val
 5}
 6// 确认是163music
 7if attrs["_name"] == "163music" {
 8    // 获取替换的内容
 9    var buf bytes.Buffer
10    err := tmpl.Execute(&buf, attrs)
11    if err != nil {
12        panic(err)
13    }
14    w.WriteString(buf.String())
15    continue
16}

最后的输出结果

1<h1>Hello World</h1>
2
3<p><iframe
4     frameborder="no" border="0"
5     marginwidth="0" marginheight="0"
6     width=330 height=86
7     src="//music.163.com/outchain/player?type=2&id=xxx&auto=0&height=66"></iframe>
8  This is one of my favorite <strong>music</strong>.</p>

结尾

目前snowshortcode 功能就是根据第二种方式实现的,具体可以参考 hooks/shortcode/shortcode.go, 利用此特性甚至还可以实现类似 Hugo 中的 Markdown Render Hook 功能,用于自定义 img, a 等标签

作者: honmaple
链接: https://honmaple.me/articles/2023/02/静态博客中的shortcode实现.html
版权: CC BY-NC-SA 4.0 知识共享署名-非商业性使用-相同方式共享4.0国际许可协议
wechat
alipay

加载评论