package yatwiki import ( "encoding/json" "html" "html/template" "net/url" "regexp" "strings" ) const ( bbcodeIsDeleted string = `[delete]` ) // An embarassing cascade of half-working hacks follows. type BBCodeRenderer struct { baseUrl string CodePresent bool DynamicContentWarning string ContentedURL string ContentedTag string } func NewBBCodeRenderer(baseUrl, ContentedURL, ContentedTag string) *BBCodeRenderer { return &BBCodeRenderer{ baseUrl: baseUrl, CodePresent: false, DynamicContentWarning: `⚠ run dynamic content`, ContentedURL: ContentedURL, ContentedTag: ContentedTag, } } func (this *BBCodeRenderer) Reset() { this.CodePresent = false } // type transformation interface { MatchString(in string) bool Apply(in string) string } type pregReplaceRule struct { *regexp.Regexp replace string } func (this pregReplaceRule) Apply(in string) string { return this.ReplaceAllString(in, this.replace) } type pregReplaceFunc struct { *regexp.Regexp replaceFunc func([]string) string } func (this pregReplaceFunc) Apply(in string) string { return PregReplaceCallback(this.Regexp, this.replaceFunc, in) } // Internal part of BBCode rendering. // It handles most leaf-level tags. func (this *BBCodeRenderer) bbcode(data string) string { s_to_r := []transformation{ pregReplaceRule{regexp.MustCompile(`(?si)\[h\](.*?)\[/h\]`), `

$1

`}, pregReplaceRule{regexp.MustCompile(`(?si)\[b\](.*?)\[/b\]`), `$1`}, pregReplaceRule{regexp.MustCompile(`(?si)\[i\](.*?)\[/i\]`), `$1`}, pregReplaceRule{regexp.MustCompile(`(?si)\[u\](.*?)\[/u\]`), `$1`}, pregReplaceRule{regexp.MustCompile(`(?si)\[s\](.*?)\[/s\]`), `$1`}, pregReplaceRule{regexp.MustCompile(`(?si)\[spoiler\](.*?)\[/spoiler\]`), `$1`}, pregReplaceRule{regexp.MustCompile(`(?si)\[img\](.*?)\[/img\]`), ``}, pregReplaceRule{regexp.MustCompile(`(?si)\[list\](.*?)\[/list\]`), ``}, pregReplaceRule{regexp.MustCompile(`(?si)\[\*\]`), `
  • `}, pregReplaceRule{regexp.MustCompile(`(?si)\[url=(.*?)\](.*?)\[/url\]`), `$2`}, pregReplaceFunc{regexp.MustCompile(`(?si)\[article=(.*?)\](.*?)\[/article\]`), func(m []string) string { // m[1] has already been hesc'd // Need to unhesc, and then pathescape targetArticle := html.UnescapeString(m[1]) return `` + m[2] + `` }}, pregReplaceFunc{regexp.MustCompile(`(?si)\[rev=(.*?)\](.*?)\[/rev\]`), func(m []string) string { return `` + m[2] + `` }}, pregReplaceRule{regexp.MustCompile(`(?si)\[imgur\](.*?)\.(...)\[/imgur\]`), ``, }, pregReplaceRule{ regexp.MustCompile(`(?si)\[youtube](.*?)\[/youtube\]`), ``, }, pregReplaceFunc{regexp.MustCompile(`(?si)\[section=(.*?)](.*?)\[/section\]`), func(m []string) string { return `
    ` + m[1] + `` + strings.TrimSpace(m[2]) + `
    ` }}, } if len(this.ContentedTag) > 0 { s_to_r = append(s_to_r, pregReplaceRule{regexp.MustCompile(`(?si)\[` + regexp.QuoteMeta(this.ContentedTag) + `\](.*?)\[/` + regexp.QuoteMeta(this.ContentedTag) + `\]`), ``, }, ) } for _, prr := range s_to_r { for prr.MatchString(data) { // repeat until all recursive replacements are consumed data = prr.Apply(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 } spos += hpos ret += this.bbformat(s[hpos : spos-hpos]) spos += 6 epos := strings.Index(s[spos:], "[/html]") if epos == -1 { break // no matching [/html] tag found } epos += spos jsonInnerContent, _ := json.Marshal(s[spos:epos]) ret += `
    ` + this.DynamicContentWarning + `
    ` 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)) }
    "+strings.Join(cols[1:], "")+"