hanayo/vendor/github.com/frustra/bbcode/compiler.go
2019-02-23 13:29:15 +00:00

257 lines
6.2 KiB
Go

// Copyright 2015 Frustra. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.
package bbcode
import (
"fmt"
"strconv"
"strings"
)
type TagCompilerFunc func(*BBCodeNode) (*HTMLTag, bool)
type Compiler struct {
tagCompilers map[string]TagCompilerFunc
defaultCompiler TagCompilerFunc
AutoCloseTags bool
IgnoreUnmatchedClosingTags bool
}
func NewCompiler(autoCloseTags, ignoreUnmatchedClosingTags bool) Compiler {
compiler := Compiler{make(map[string]TagCompilerFunc), DefaultTagCompiler, autoCloseTags, ignoreUnmatchedClosingTags}
for tag, compilerFunc := range DefaultTagCompilers {
compiler.SetTag(tag, compilerFunc)
}
return compiler
}
func (c Compiler) Compile(str string) string {
tokens := Lex(str)
tree := Parse(tokens)
return c.CompileTree(tree).String()
}
func (c Compiler) SetDefault(compiler TagCompilerFunc) {
if compiler == nil {
panic("Default tag compiler can't be nil")
} else {
c.defaultCompiler = compiler
}
}
func (c Compiler) SetTag(tag string, compiler TagCompilerFunc) {
if compiler == nil {
delete(c.tagCompilers, tag)
} else {
c.tagCompilers[tag] = compiler
}
}
// CompileTree transforms BBCodeNode into an HTML tag.
func (c Compiler) CompileTree(node *BBCodeNode) *HTMLTag {
var out = NewHTMLTag("")
if node.ID == TEXT {
out.Value = node.Value.(string)
InsertNewlines(out)
for _, child := range node.Children {
out.AppendChild(c.CompileTree(child))
}
} else if node.ID == CLOSING_TAG {
if !c.IgnoreUnmatchedClosingTags {
out.Value = node.Value.(BBClosingTag).Raw
InsertNewlines(out)
}
for _, child := range node.Children {
out.AppendChild(c.CompileTree(child))
}
} else if node.ClosingTag == nil && !c.AutoCloseTags {
out.Value = node.Value.(BBOpeningTag).Raw
InsertNewlines(out)
for _, child := range node.Children {
out.AppendChild(c.CompileTree(child))
}
} else {
in := node.GetOpeningTag()
compileFunc, ok := c.tagCompilers[in.Name]
if !ok {
compileFunc = c.defaultCompiler
}
var appendExpr bool
node.Compiler = &c
out, appendExpr = compileFunc(node)
if appendExpr {
if len(node.Children) == 0 {
out.AppendChild(NewHTMLTag(""))
} else {
for _, child := range node.Children {
out.AppendChild(c.CompileTree(child))
}
}
}
}
return out
}
func CompileText(in *BBCodeNode) string {
out := ""
if in.ID == TEXT {
out = in.Value.(string)
}
for _, child := range in.Children {
out += CompileText(child)
}
return out
}
func CompileRaw(in *BBCodeNode) *HTMLTag {
out := NewHTMLTag("")
if in.ID == TEXT {
out.Value = in.Value.(string)
} else if in.ID == CLOSING_TAG {
out.Value = in.Value.(BBClosingTag).Raw
} else {
out.Value = in.Value.(BBOpeningTag).Raw
}
for _, child := range in.Children {
out.AppendChild(CompileRaw(child))
}
if in.ID == OPENING_TAG && in.ClosingTag != nil {
tag := NewHTMLTag(in.ClosingTag.Raw)
out.AppendChild(tag)
}
return out
}
var DefaultTagCompilers map[string]TagCompilerFunc
var DefaultTagCompiler TagCompilerFunc
func init() {
DefaultTagCompiler = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag(node.GetOpeningTag().Raw)
InsertNewlines(out)
if len(node.Children) == 0 {
out.AppendChild(NewHTMLTag(""))
} else {
for _, child := range node.Children {
out.AppendChild(node.Compiler.CompileTree(child))
}
}
if node.ClosingTag != nil {
tag := NewHTMLTag(node.ClosingTag.Raw)
InsertNewlines(tag)
out.AppendChild(tag)
}
return out, false
}
DefaultTagCompilers = make(map[string]TagCompilerFunc)
DefaultTagCompilers["url"] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = "a"
value := node.GetOpeningTag().Value
if value == "" {
text := CompileText(node)
if len(text) > 0 {
out.Attrs["href"] = ValidURL(text)
}
} else {
out.Attrs["href"] = ValidURL(value)
}
return out, true
}
DefaultTagCompilers["img"] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = "img"
value := node.GetOpeningTag().Value
if value == "" {
out.Attrs["src"] = ValidURL(CompileText(node))
} else {
out.Attrs["src"] = ValidURL(value)
text := CompileText(node)
if len(text) > 0 {
out.Attrs["alt"] = text
out.Attrs["title"] = out.Attrs["alt"]
}
}
return out, false
}
DefaultTagCompilers["center"] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = "div"
out.Attrs["style"] = "text-align: center;"
return out, true
}
DefaultTagCompilers["color"] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = "span"
sanitize := func(r rune) rune {
if r == '#' || r == ',' || r == '.' || r == '(' || r == ')' || r == '%' {
return r
} else if r >= '0' && r <= '9' {
return r
} else if r >= 'a' && r <= 'z' {
return r
} else if r >= 'A' && r <= 'Z' {
return r
}
return -1
}
color := strings.Map(sanitize, node.GetOpeningTag().Value)
out.Attrs["style"] = "color: " + color + ";"
return out, true
}
DefaultTagCompilers["size"] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = "span"
if size, err := strconv.Atoi(node.GetOpeningTag().Value); err == nil {
out.Attrs["class"] = fmt.Sprintf("size%d", size)
}
return out, true
}
DefaultTagCompilers["quote"] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = "blockquote"
who := ""
in := node.GetOpeningTag()
if name, ok := in.Args["name"]; ok && name != "" {
who = name
} else {
who = in.Value
}
cite := NewHTMLTag("")
cite.Name = "cite"
if who != "" {
cite.AppendChild(NewHTMLTag(who + " said:"))
} else {
cite.AppendChild(NewHTMLTag("Quote"))
}
return out.AppendChild(cite), true
}
DefaultTagCompilers["code"] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = "pre"
for _, child := range node.Children {
out.AppendChild(CompileRaw(child))
}
return out, false
}
for _, tag := range []string{"i", "b", "u", "s"} {
DefaultTagCompilers[tag] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = node.GetOpeningTag().Name
return out, true
}
}
}