Get bbcode working in Goldmark
This commit is contained in:
parent
085bd46440
commit
540d1765db
1
go.mod
1
go.mod
|
@ -6,6 +6,7 @@ require (
|
|||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||
github.com/frustra/bbcode v0.0.0-20201127003707-6ef347fbe1c8 // indirect
|
||||
github.com/go-stack/stack v1.8.0
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -49,6 +49,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/frustra/bbcode v0.0.0-20201127003707-6ef347fbe1c8 h1:sdIsYe6Vv7KIWZWp8KqSeTl+XlF17d+wHCC4lbxFcYs=
|
||||
github.com/frustra/bbcode v0.0.0-20201127003707-6ef347fbe1c8/go.mod h1:0QBxkXxN+o4FyZgLI9FHY/oUizheze3+bNY/kgCKL+4=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
|
|
Binary file not shown.
|
@ -1,67 +1,160 @@
|
|||
package parsing
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"regexp"
|
||||
|
||||
"github.com/frustra/bbcode"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
gast "github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/renderer/html"
|
||||
"github.com/yuin/goldmark/text"
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
const BBCodePriority = 1 // TODO: Pick something more reasonable?
|
||||
var BBCodePriority = 1 // TODO: This is maybe too high a priority?
|
||||
|
||||
type bParser struct{}
|
||||
var reTag = regexp.MustCompile(`(?P<open>\[\s*(?P<opentagname>[a-zA-Z]+))|(?P<close>\[\s*\/\s*(?P<closetagname>[a-zA-Z]+)\s*\])`)
|
||||
|
||||
var _ parser.InlineParser = bParser{}
|
||||
var bbcodeCompiler = bbcode.NewCompiler(false, false)
|
||||
|
||||
func (s bParser) Trigger() []byte {
|
||||
// ----------------------
|
||||
// Parser and delimiters
|
||||
// ----------------------
|
||||
|
||||
type bbcodeParser struct{}
|
||||
|
||||
func NewBBCodeParser() parser.InlineParser {
|
||||
return bbcodeParser{}
|
||||
}
|
||||
|
||||
func (s bbcodeParser) Trigger() []byte {
|
||||
return []byte{'['}
|
||||
}
|
||||
|
||||
func (s bParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
|
||||
// _, segment := block.PeekLine()
|
||||
// start := segment.Start
|
||||
func (s bbcodeParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
|
||||
_, pos := block.Position()
|
||||
restOfSource := block.Source()[pos.Start:]
|
||||
|
||||
// block.Advance(3)
|
||||
// n := ast.NewTextSegment(text.NewSegment(start, start+4))
|
||||
// bold := ast.NewText()
|
||||
// bold.Segment
|
||||
// link := ast.NewAutoLink(typ, n)
|
||||
// link.Protocol = protocol
|
||||
// return link
|
||||
|
||||
lineBytes, _ := block.PeekLine()
|
||||
|
||||
line := string(lineBytes)
|
||||
|
||||
if !strings.HasPrefix(line, "[b]") {
|
||||
matches := reTag.FindAllSubmatchIndex(restOfSource, -1)
|
||||
if matches == nil {
|
||||
// No tags anywhere
|
||||
return nil
|
||||
}
|
||||
start := 0
|
||||
|
||||
closingIndex := strings.Index(line, "[/b]")
|
||||
if closingIndex < 0 {
|
||||
otIndex := reTag.SubexpIndex("opentagname")
|
||||
ctIndex := reTag.SubexpIndex("closetagname")
|
||||
|
||||
tagName := extractStringBySubmatchIndices(restOfSource, matches[0], otIndex)
|
||||
if tagName == "" {
|
||||
// Not an opening tag
|
||||
return nil
|
||||
}
|
||||
end := closingIndex + 4
|
||||
|
||||
n := ast.NewEmphasis(2)
|
||||
n.AppendChild(n, ast.NewString([]byte("wow bold text")))
|
||||
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
|
||||
return nil
|
||||
}
|
||||
|
||||
block.Advance(end - start)
|
||||
return n
|
||||
unparsedBBCode := restOfSource[:endIndex]
|
||||
block.Advance(len(unparsedBBCode))
|
||||
|
||||
return NewBBCode(bbcodeCompiler.Compile(string(unparsedBBCode)))
|
||||
}
|
||||
|
||||
type bTag struct{}
|
||||
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]])
|
||||
}
|
||||
|
||||
func (e bTag) Extend(m goldmark.Markdown) {
|
||||
// ----------------------
|
||||
// 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
|
||||
}
|
||||
|
||||
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) {
|
||||
m.Parser().AddOptions(parser.WithInlineParsers(
|
||||
util.Prioritized(bParser{}, BBCodePriority),
|
||||
util.Prioritized(NewBBCodeParser(), BBCodePriority),
|
||||
))
|
||||
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||
util.Prioritized(NewBBCodeHTMLRenderer(), BBCodePriority),
|
||||
))
|
||||
// m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||
// util.Prioritized(NewStrikethroughHTMLRenderer(), 500),
|
||||
// ))
|
||||
}
|
||||
|
|
|
@ -2,94 +2,11 @@ package parsing
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"sort"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
)
|
||||
|
||||
func StripNames(regexStr string) string {
|
||||
return regexp.MustCompile(`\(\?P<[a-zA-Z0-9_]+>`).ReplaceAllString(regexStr, `(?:`)
|
||||
}
|
||||
|
||||
var reArgStr = `(?P<name>[a-zA-Z0-9]+)(?:\s*=\s*(?:'(?P<single_quoted_val>.*?)'|"(?P<double_quoted_val>.*?)"|(?P<bare_val>[^\s\]]+)))?`
|
||||
var reTagOpenStr = `\[\s*(?P<args>(?:` + StripNames(reArgStr) + `)(?:\s+(?:` + StripNames(reArgStr) + `))*)\s*\]`
|
||||
var reTagCloseStr = `\[/\s*(?P<name>[a-zA-Z0-9]+)?\s*\]`
|
||||
|
||||
var reArg = regexp.MustCompile(reArgStr)
|
||||
var reTagOpen = regexp.MustCompile(reTagOpenStr)
|
||||
var reTagClose = regexp.MustCompile(reTagCloseStr)
|
||||
|
||||
const tokenTypeString = "string"
|
||||
const tokenTypeOpenTag = "openTag"
|
||||
const tokenTypeCloseTag = "closeTag"
|
||||
|
||||
type token struct {
|
||||
Type string
|
||||
StartIndex int
|
||||
EndIndex int
|
||||
Contents string
|
||||
}
|
||||
|
||||
func ParseBBCode(input string) string {
|
||||
return input
|
||||
}
|
||||
|
||||
func tokenizeBBCode(input string) []token {
|
||||
openMatches := reTagOpen.FindAllStringIndex(input, -1)
|
||||
closeMatches := reTagClose.FindAllStringIndex(input, -1)
|
||||
|
||||
// Build tokens from regex matches
|
||||
var tagTokens []token
|
||||
for _, match := range openMatches {
|
||||
tagTokens = append(tagTokens, token{
|
||||
Type: tokenTypeOpenTag,
|
||||
StartIndex: match[0],
|
||||
EndIndex: match[1],
|
||||
Contents: input[match[0]:match[1]],
|
||||
})
|
||||
}
|
||||
for _, match := range closeMatches {
|
||||
tagTokens = append(tagTokens, token{
|
||||
Type: tokenTypeCloseTag,
|
||||
StartIndex: match[0],
|
||||
EndIndex: match[1],
|
||||
Contents: input[match[0]:match[1]],
|
||||
})
|
||||
}
|
||||
|
||||
// Sort those tokens together
|
||||
sort.Slice(tagTokens, func(i, j int) bool {
|
||||
return tagTokens[i].StartIndex < tagTokens[j].StartIndex
|
||||
})
|
||||
|
||||
// Make a new list of tokens that fills in the gaps with plain old text
|
||||
var tokens []token
|
||||
for i, tagToken := range tagTokens {
|
||||
prevEnd := 0
|
||||
if i > 0 {
|
||||
prevEnd = tagTokens[i-1].EndIndex
|
||||
}
|
||||
|
||||
tokens = append(tokens, token{
|
||||
Type: tokenTypeString,
|
||||
StartIndex: prevEnd,
|
||||
EndIndex: tagToken.StartIndex,
|
||||
Contents: input[prevEnd:tagToken.StartIndex],
|
||||
})
|
||||
tokens = append(tokens, tagToken)
|
||||
}
|
||||
tokens = append(tokens, token{
|
||||
Type: tokenTypeString,
|
||||
StartIndex: tokens[len(tokens)-1].EndIndex,
|
||||
EndIndex: len(input),
|
||||
Contents: input[tokens[len(tokens)-1].EndIndex:],
|
||||
})
|
||||
|
||||
return tokens
|
||||
}
|
||||
|
||||
var previewMarkdown = goldmark.New(
|
||||
goldmark.WithExtensions(
|
||||
extension.GFM,
|
||||
|
@ -98,7 +15,7 @@ var previewMarkdown = goldmark.New(
|
|||
Preview: true,
|
||||
},
|
||||
MathjaxExtension{},
|
||||
bTag{},
|
||||
BBCodeExtension{},
|
||||
),
|
||||
)
|
||||
var realMarkdown = goldmark.New(
|
||||
|
@ -107,7 +24,7 @@ var realMarkdown = goldmark.New(
|
|||
SpoilerExtension{},
|
||||
EmbedExtension{},
|
||||
MathjaxExtension{},
|
||||
bTag{},
|
||||
BBCodeExtension{},
|
||||
),
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue