257 lines
6.2 KiB
Go
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
|
|
}
|
|
}
|
|
}
|