2021-06-16 04:04:01 +00:00
|
|
|
package parsing
|
|
|
|
|
|
|
|
import (
|
2021-06-20 20:46:42 +00:00
|
|
|
"regexp"
|
2021-06-16 04:04:01 +00:00
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
"github.com/frustra/bbcode"
|
2021-06-16 04:04:01 +00:00
|
|
|
"github.com/yuin/goldmark"
|
|
|
|
gast "github.com/yuin/goldmark/ast"
|
|
|
|
"github.com/yuin/goldmark/parser"
|
2021-06-20 20:46:42 +00:00
|
|
|
"github.com/yuin/goldmark/renderer"
|
|
|
|
"github.com/yuin/goldmark/renderer/html"
|
2021-06-16 04:04:01 +00:00
|
|
|
"github.com/yuin/goldmark/text"
|
|
|
|
"github.com/yuin/goldmark/util"
|
|
|
|
)
|
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
var BBCodePriority = 1 // TODO: This is maybe too high a priority?
|
2021-06-16 04:04:01 +00:00
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
var reTag = regexp.MustCompile(`(?P<open>\[\s*(?P<opentagname>[a-zA-Z]+))|(?P<close>\[\s*\/\s*(?P<closetagname>[a-zA-Z]+)\s*\])`)
|
2021-06-16 04:04:01 +00:00
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
var bbcodeCompiler = bbcode.NewCompiler(false, false)
|
2021-06-16 04:04:01 +00:00
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
// ----------------------
|
|
|
|
// Parser and delimiters
|
|
|
|
// ----------------------
|
|
|
|
|
|
|
|
type bbcodeParser struct{}
|
|
|
|
|
|
|
|
func NewBBCodeParser() parser.InlineParser {
|
|
|
|
return bbcodeParser{}
|
2021-06-16 04:04:01 +00:00
|
|
|
}
|
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
func (s bbcodeParser) Trigger() []byte {
|
|
|
|
return []byte{'['}
|
|
|
|
}
|
2021-06-16 04:04:01 +00:00
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
func (s bbcodeParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
|
|
|
|
_, pos := block.Position()
|
|
|
|
restOfSource := block.Source()[pos.Start:]
|
2021-06-16 04:04:01 +00:00
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
matches := reTag.FindAllSubmatchIndex(restOfSource, -1)
|
|
|
|
if matches == nil {
|
|
|
|
// No tags anywhere
|
|
|
|
return nil
|
|
|
|
}
|
2021-06-16 04:04:01 +00:00
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
otIndex := reTag.SubexpIndex("opentagname")
|
|
|
|
ctIndex := reTag.SubexpIndex("closetagname")
|
2021-06-16 04:04:01 +00:00
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
tagName := extractStringBySubmatchIndices(restOfSource, matches[0], otIndex)
|
|
|
|
if tagName == "" {
|
|
|
|
// Not an opening tag
|
2021-06-16 04:04:01 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
depth := 0
|
|
|
|
endIndex := -1
|
|
|
|
for _, m := range matches {
|
|
|
|
if openName := extractStringBySubmatchIndices(restOfSource, m, otIndex); openName != "" {
|
|
|
|
if openName == tagName {
|
|
|
|
depth++
|
|
|
|
}
|
|
|
|
} else if closeName := extractStringBySubmatchIndices(restOfSource, m, ctIndex); closeName != "" {
|
|
|
|
if closeName == tagName {
|
|
|
|
depth--
|
|
|
|
if depth == 0 {
|
|
|
|
// We have balanced out!
|
|
|
|
endIndex = m[1] // the end index of this closing tag (exclusive)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if endIndex < 0 {
|
|
|
|
// Unbalanced, too many opening tags
|
2021-06-16 04:04:01 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
unparsedBBCode := restOfSource[:endIndex]
|
|
|
|
block.Advance(len(unparsedBBCode))
|
2021-06-16 04:04:01 +00:00
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
return NewBBCode(bbcodeCompiler.Compile(string(unparsedBBCode)))
|
2021-06-16 04:04:01 +00:00
|
|
|
}
|
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
func extractStringBySubmatchIndices(src []byte, m []int, subexpIndex int) string {
|
|
|
|
srcIndices := m[2*subexpIndex : 2*subexpIndex+1+1]
|
|
|
|
if srcIndices[0] < 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return string(src[srcIndices[0]:srcIndices[1]])
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------
|
|
|
|
// AST node
|
|
|
|
// ----------------------
|
|
|
|
|
|
|
|
type BBCodeNode struct {
|
|
|
|
gast.BaseInline
|
|
|
|
HTML string
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ gast.Node = &BBCodeNode{}
|
|
|
|
|
|
|
|
func (n *BBCodeNode) Dump(source []byte, level int) {
|
|
|
|
gast.DumpHelper(n, source, level, nil, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
var KindBBCode = gast.NewNodeKind("BBCode")
|
|
|
|
|
|
|
|
func (n *BBCodeNode) Kind() gast.NodeKind {
|
|
|
|
return KindBBCode
|
|
|
|
}
|
2021-06-16 04:04:01 +00:00
|
|
|
|
2021-06-20 20:46:42 +00:00
|
|
|
func NewBBCode(html string) gast.Node {
|
|
|
|
return &BBCodeNode{
|
|
|
|
HTML: html,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------
|
|
|
|
// Renderer
|
|
|
|
// ----------------------
|
|
|
|
|
|
|
|
type BBCodeHTMLRenderer struct {
|
|
|
|
html.Config
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewBBCodeHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
|
|
|
|
r := &BBCodeHTMLRenderer{
|
|
|
|
Config: html.NewConfig(),
|
|
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
|
|
opt.SetHTMLOption(&r.Config)
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *BBCodeHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
|
|
|
reg.Register(KindBBCode, r.renderBBCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *BBCodeHTMLRenderer) renderBBCode(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
|
|
|
if entering {
|
|
|
|
w.WriteString(n.(*BBCodeNode).HTML)
|
|
|
|
}
|
|
|
|
return gast.WalkContinue, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------
|
|
|
|
// Extension
|
|
|
|
// ----------------------
|
|
|
|
|
|
|
|
type BBCodeExtension struct{}
|
|
|
|
|
|
|
|
func (e BBCodeExtension) Extend(m goldmark.Markdown) {
|
2021-06-16 04:04:01 +00:00
|
|
|
m.Parser().AddOptions(parser.WithInlineParsers(
|
2021-06-20 20:46:42 +00:00
|
|
|
util.Prioritized(NewBBCodeParser(), BBCodePriority),
|
|
|
|
))
|
|
|
|
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
|
|
|
util.Prioritized(NewBBCodeHTMLRenderer(), BBCodePriority),
|
2021-06-16 04:04:01 +00:00
|
|
|
))
|
|
|
|
}
|