前言
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>
结尾
目前snow的 shortcode 功能就是根据第二种方式实现的,具体可以参考 hooks/shortcode/shortcode.go, 利用此特性甚至还可以实现类似 Hugo 中的 Markdown Render Hook 功能,用于自定义 img, a 等标签
知识共享署名-非商业性使用-相同方式共享4.0国际许可协议