前言
shortcode,直译过来就是 短代码。 使用过 hugo 的人对此也一定有所了解,shortcode 允许用户使用部分关键参数就可以在文章内部插入设置好的HTML模版内容,而不用写大段的HTML代码,比如插入指定视频或者音乐,使用 shortcode 之前需要使用 iframe
<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=xxx&auto=0&height=66"></iframe>
而使用 shortcode 后就可以只用指定关键的 id 参数
{{<163music xxx>}}
当网易云音乐的分享代码更新后,可以直接更新 shortcode 代码, 而不是搜索替换每一篇包括该iframe代码的文章, 同时 shortcode 也可以扩展 Markdown 的功能,比如文本的居中,居左,居右
{{<align left>}} 文本内容 {{</align>}}
实现方式
而想要在静态博客中实现 shortcode 功能, 目前有两种方式
-
方式一: 修改 Markdown 解析器, 在静态生成器解析文章内容时就可以对 shortcode 进行解析,并将 shortcode 替换成写好的模版,至少,我看到 hugo 就是这么实现的
-
方式二: 如果不想修改 Markdown 解析器,还可以直接修改生成好的 HTML 内容,这种方式实现原理很简单,Markdown允许插入HTML代码, 那么我们就可以直接在文章中写一个
<shortcode _name="163music" id="xxx" />
然后在现有的 Markdown 解析库解析文章并生成 HTML 后再次对HTML内容进行解析,目前主流语言都有现成的库很方便地解析HTML内容, 下面以 Go 为例
Markdown渲染
比如有一篇文章,我想要添加网易云音乐的某首歌到文章内部,当使用现有的Markdown库对内容进行渲染
package main import ( "github.com/russross/blackfriday/v2" ) func main() { content := `# Hello World <shortcode _name="163music" id="xxx" /> This is one of my favorite **music**. ` d := blackfriday.Run(content) fmt.Println(string(d)) }
输出结果
<h1>Hello World</h1> <p><shortcode _name="163music" id="xxx" /> This is one of my favorite <strong>music</strong>.</p>
查找HTML标签
可以使用 golang.org/x/net/html
对HTML内容进行解析, 并查找到 shortcode
package main import ( "bytes" "fmt" "github.com/russross/blackfriday/v2" "golang.org/x/net/html" "text/template" ) func main() { // ... var ( w bytes.Buffer z = html.NewTokenizer(bytes.NewReader(d)) ) for { next := z.Next() if next == html.ErrorToken { break } token := z.Token() switch next { case html.StartTagToken, html.SelfClosingTagToken: // 找到shortcode标签 if token.Data == "shortcode" { } } w.WriteString(token.String()) } fmt.Println(w.String()) }
替换HTML
首先把需要替换的 iframe 转换成模版
replace := `<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id={{ .id }}&auto=0&height=66"></iframe>` tmpl, err := template.New("test").Parse(replace) if err != nil { panic(err) }
然后根据标签参数替换原来的HTML
// 获取该标签的参数 attrs := make(map[string]string) for _, attr := range token.Attr { attrs[attr.Key] = attr.Val } // 确认是163music if attrs["_name"] == "163music" { // 获取替换的内容 var buf bytes.Buffer err := tmpl.Execute(&buf, attrs) if err != nil { panic(err) } w.WriteString(buf.String()) continue }
最后的输出结果
<h1>Hello World</h1> <p><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=xxx&auto=0&height=66"></iframe> This is one of my favorite <strong>music</strong>.</p>
结尾
目前snow的 shortcode 功能就是根据第二种方式实现的,具体可以参考 hooks/shortcode/shortcode.go, 利用此特性甚至还可以实现类似 Hugo 中的 Markdown Render Hook 功能,用于自定义 img
, a
等标签