From 8f2cb1e1d5648ead0291267813a8b7dc982c5938 Mon Sep 17 00:00:00 2001 From: mappu Date: Sun, 9 Jul 2017 12:13:16 +1200 Subject: [PATCH] working bbcode support --- bbcode.go | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ regex.go | 15 +++++ 2 files changed, 203 insertions(+) create mode 100644 bbcode.go create mode 100644 regex.go diff --git a/bbcode.go b/bbcode.go new file mode 100644 index 0000000..807e13d --- /dev/null +++ b/bbcode.go @@ -0,0 +1,188 @@ +package yatwiki3 + +import ( + "encoding/json" + "html/template" + "regexp" + "strings" +) + +// An embarassing cascade of half-working hacks follows. +type BBCodeRenderer struct { + baseUrl string + CodePresent bool + DynamicContentWarning string +} + +func NewBBCodeRenderer(baseUrl string) *BBCodeRenderer { + return &BBCodeRenderer{ + baseUrl: baseUrl, + CodePresent: false, + DynamicContentWarning: `⚠ run dynamic content`, + } +} + +type pregReplaceRule struct { + match *regexp.Regexp + replace string + replaceFunc func([]string) string +} + +// Internal part of BBCode rendering. +// It handles most leaf-level tags. +func (this *BBCodeRenderer) bbcode(data string) string { + + s_to_r := []pregReplaceRule{ + pregReplaceRule{regexp.MustCompile(`(?si)\[h\](.*?)\[/h\]`), `

$1

`, nil}, + pregReplaceRule{regexp.MustCompile(`(?si)\[b\](.*?)\[/b\]`), `$1`, nil}, + pregReplaceRule{regexp.MustCompile(`(?si)\[i\](.*?)\[/i\]`), `$1`, nil}, + pregReplaceRule{regexp.MustCompile(`(?si)\[u\](.*?)\[/u\]`), `$1`, nil}, + pregReplaceRule{regexp.MustCompile(`(?si)\[s\](.*?)\[/s\]`), `$1`, nil}, + pregReplaceRule{regexp.MustCompile(`(?si)\[spoiler\](.*?)\[/spoiler\]`), `$1`, nil}, + pregReplaceRule{regexp.MustCompile(`(?si)\[img\](.*?)\[/img\]`), ``, nil}, + pregReplaceRule{regexp.MustCompile(`(?si)\[list\](.*?)\[/list\]`), ``, nil}, + pregReplaceRule{regexp.MustCompile(`(?si)\[\*\]`), `
  • `, nil}, + pregReplaceRule{regexp.MustCompile(`(?si)\[url=(.*?)\](.*?)\[/url\]`), `$2`, nil}, + pregReplaceRule{regexp.MustCompile(`(?si)\[article=(.*?)\](.*?)\[/article\]`), "", func(m []string) string { + return `` + m[2] + `` + }}, + pregReplaceRule{regexp.MustCompile(`(?si)\[rev=(.*?)\](.*?)\[/rev\]`), "", func(m []string) string { + return `` + m[2] + `` + }}, + + pregReplaceRule{regexp.MustCompile(`(?si)\[imgur\](.*?)\.(...)\[/imgur\]`), + ``, + nil, + }, + + pregReplaceRule{regexp.MustCompile(`(?si)\[section=(.*?)](.*?)\[/section\]`), "", func(m []string) string { + return `
    ` + m[1] + `` + strings.TrimSpace(m[2]) + `
    ` + }}, + } + + for _, prr := range s_to_r { + + for prr.match.MatchString(data) { // repeat until all recursive replacements are consumed + if len(prr.replace) > 0 { + data = prr.match.ReplaceAllString(data, prr.replace) + + } else { + data = PregReplaceCallback(prr.match, prr.replaceFunc, data) + } + } + } + return data +} + +// Internal part of BBCode rendering. +// It extracts tables and then passes the remainder to bbcode(). +func (this *BBCodeRenderer) bbtables(s string) string { + lines := strings.Split(s, "\n") + lastct := 0 + + ret := make([]string, 0) + tbl := make([]string, 0) + buffer := make([]string, 0) + + maybe_close_previous_table := func() { + if lastct > 0 { // Close previous table + tbl = append(tbl, "") + ret = append(ret, strings.Join(tbl, "")) + tbl = []string{} + lastct = 0 + } + } + + inner_formatter := func(s string) string { + return this.bbcode(template.HTMLEscapeString(s)) + } + + flush_buffer := func() { + if len(buffer) > 0 { + ret = append(ret, inner_formatter(strings.Join(buffer, "\n"))) + buffer = []string{} + } + } + + for _, line := range lines { + if len(line) > 0 && line[0] == '|' { + flush_buffer() + + cols := strings.Split(line, "|") + if lastct != len(cols) { + maybe_close_previous_table() + + // Start new table + lastct = len(cols) + tbl = append(tbl, "") + } + + for i := 0; i < len(cols); i += 1 { + cols[i] = inner_formatter(strings.Replace(cols[i], ";", "\n", -1)) + } + + // Add these columns + tbl = append(tbl, "") + + } else { + maybe_close_previous_table() + buffer = append(buffer, line) + } + } + + flush_buffer() + maybe_close_previous_table() + + return strings.Join(ret, "\n") +} + +// Internal part of BBCode rendering. +// It extracts [code] sections and then passes the remainder to bbtables(). +func (this *BBCodeRenderer) bbformat(str string) string { + return PregReplaceCallback( + regexp.MustCompile(`(?si)\[code\](.*?)\[/code\]`), + func(m []string) string { + this.CodePresent = true + return "
    " + strings.Replace(m[1], "
    \r\n", "\r\n", -1) + "
    " + }, + strings.Replace( + strings.Replace(this.bbtables(str), "\r\n", "
    ", -1), //bbtables(bbcode(h($str))) + "
    \r\n
    ", + "
    ", + -1, // replace all + ), + ) +} + +// Internal part of BBCode rendering. +// It extracts [html] sections and then passes the remainder to bbformat(). +func (this *BBCodeRenderer) displayfmt(s string) string { + hpos := 0 + ret := "" + + for { + spos := strings.Index(s[hpos:], "[html]") + if spos == -1 { + break + } + + ret += this.bbformat(s[hpos : spos-hpos]) + spos += 6 + + epos := strings.Index(s[spos:], "[/html]") + if epos == -1 { + break // no matching [/html] tag found + } + + jsonInnerContent, _ := json.Marshal(s[spos : epos-spos]) + + ret += `` + hpos = epos + 7 + } + return ret + this.bbformat(s[hpos:]) +} + +// RenderHTML converts the input string to HTML. +func (this *BBCodeRenderer) RenderHTML(s string) template.HTML { + return template.HTML(this.displayfmt(s)) +} diff --git a/regex.go b/regex.go new file mode 100644 index 0000000..54aca17 --- /dev/null +++ b/regex.go @@ -0,0 +1,15 @@ +package yatwiki3 + +import ( + "regexp" +) + +func PregReplaceCallback(pattern *regexp.Regexp, cb func([]string) string, content string) string { + // FIXME avoid double-matching + // Submit to upstream https://github.com/golang/go/issues/5690 + return pattern.ReplaceAllStringFunc(content, func(fullmatch string) string { + parts := pattern.FindStringSubmatch(fullmatch) + return cb(parts) + }) + +}
    "+strings.Join(cols[1:], "")+"