From a45de56db1992d469dc9b4714bd839d6c3e9b092 Mon Sep 17 00:00:00 2001 From: spf13 Date: Fri, 6 Dec 2013 23:14:54 -0500 Subject: [PATCH] adding support for shortcodes with opening and closing tags --- hugolib/shortcode.go | 122 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 106 insertions(+), 16 deletions(-) diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go index 4d68b2fe8..f08d30cd7 100644 --- a/hugolib/shortcode.go +++ b/hugolib/shortcode.go @@ -17,6 +17,7 @@ import ( "bytes" "fmt" "github.com/spf13/hugo/template/bundle" + "html/template" "strings" "unicode" ) @@ -32,26 +33,85 @@ type Shortcode struct { type ShortcodeWithPage struct { Params interface{} + Inner template.HTML Page *Page } type Shortcodes map[string]ShortcodeFunc func ShortcodesHandle(stringToParse string, p *Page, t bundle.Template) string { - posStart := strings.Index(stringToParse, "{{%") - if posStart > 0 { - posEnd := strings.Index(stringToParse[posStart:], "%}}") + posStart - if posEnd > posStart { - name, par := SplitParams(stringToParse[posStart+3 : posEnd]) + leadStart := strings.Index(stringToParse, `{{%`) + if leadStart >= 0 { + leadEnd := strings.Index(stringToParse[leadStart:], `%}}`) + leadStart + if leadEnd > leadStart { + name, par := SplitParams(stringToParse[leadStart+3 : leadEnd]) + tmpl := GetTemplate(name, t) + if tmpl == nil { + return stringToParse + } params := Tokenize(par) + // Always look for closing tag. + endStart, endEnd := FindEnd(stringToParse[leadEnd:], name) var data = &ShortcodeWithPage{Params: params, Page: p} - newString := stringToParse[:posStart] + ShortcodeRender(name, data, t) + ShortcodesHandle(stringToParse[posEnd+3:], p, t) - return newString + if endStart > 0 { + s := stringToParse[leadEnd+3 : leadEnd+endStart] + data.Inner = template.HTML(CleanP(ShortcodesHandle(s, p, t))) + remainder := CleanP(stringToParse[leadEnd+endEnd:]) + + return CleanP(stringToParse[:leadStart]) + + ShortcodeRender(tmpl, data) + + CleanP(ShortcodesHandle(remainder, p, t)) + } + return CleanP(stringToParse[:leadStart]) + + ShortcodeRender(tmpl, data) + + CleanP(ShortcodesHandle(stringToParse[leadEnd+3:], p, + t)) } } return stringToParse } +// Clean up odd behavior when closing tag is on first line +// or opening tag is on the last line due to extra line in markdown file +func CleanP(str string) string { + if strings.HasSuffix(strings.TrimSpace(str), "

") { + idx := strings.LastIndex(str, "

") + str = str[:idx] + } + + if strings.HasPrefix(strings.TrimSpace(str), "

") { + str = str[strings.Index(str, "

")+5:] + } + + return str +} + +func FindEnd(str string, name string) (int, int) { + var endPos int + var startPos int + var try []string + + try = append(try, "{{% /"+name+" %}}") + try = append(try, "{{% /"+name+"%}}") + try = append(try, "{{%/"+name+"%}}") + try = append(try, "{{%/"+name+" %}}") + + lowest := len(str) + for _, x := range try { + start := strings.Index(str, x) + if start < lowest && start > 0 { + startPos = start + endPos = startPos + len(x) + } + } + + return startPos, endPos +} + +func GetTemplate(name string, t bundle.Template) *template.Template { + return t.Lookup("shortcodes/" + name + ".html") +} + func StripShortcodes(stringToParse string) string { posStart := strings.Index(stringToParse, "{{%") if posStart > 0 { @@ -67,6 +127,12 @@ func StripShortcodes(stringToParse string) string { func Tokenize(in string) interface{} { first := strings.Fields(in) var final = make([]string, 0) + + // if don't need to parse, don't parse. + if strings.Index(in, " ") < 0 && strings.Index(in, "=") < 1 { + return append(final, in) + } + var keys = make([]string, 0) inQuote := false start := 0 @@ -81,18 +147,38 @@ func Tokenize(in string) interface{} { } } - if !strings.HasPrefix(v, "“") && !inQuote { + // Adjusted to handle htmlencoded and non htmlencoded input + if !strings.HasPrefix(v, "“") && !strings.HasPrefix(v, "\"") && !inQuote { final = append(final, v) - } else if inQuote && strings.HasSuffix(v, "”") && !strings.HasSuffix(v, "\\\"") { - first[i] = v[:len(v)-7] + } else if inQuote && (strings.HasSuffix(v, "”") || + strings.HasSuffix(v, "\"")) && !strings.HasSuffix(v, "\\\"") { + if strings.HasSuffix(v, "\"") { + first[i] = v[:len(v)-1] + } else { + first[i] = v[:len(v)-7] + } final = append(final, strings.Join(first[start:i+1], " ")) inQuote = false - } else if strings.HasPrefix(v, "“") && !inQuote { - if strings.HasSuffix(v, "”") { - final = append(final, v[7:len(v)-7]) + } else if (strings.HasPrefix(v, "“") || + strings.HasPrefix(v, "\"")) && !inQuote { + if strings.HasSuffix(v, "”") || strings.HasSuffix(v, + "\"") { + if strings.HasSuffix(v, "\"") { + if len(v) > 1 { + final = append(final, v[1:len(v)-1]) + } else { + final = append(final, "") + } + } else { + final = append(final, v[7:len(v)-7]) + } } else { start = i - first[i] = v[7:] + if strings.HasPrefix(v, "\"") { + first[i] = v[1:] + } else { + first[i] = v[7:] + } inQuote = true } } @@ -103,6 +189,10 @@ func Tokenize(in string) interface{} { } } + if len(keys) > 0 && (len(keys) != len(final)) { + panic("keys and final different lengths") + } + if len(keys) > 0 { var m = make(map[string]string) for i, k := range keys { @@ -124,8 +214,8 @@ func SplitParams(in string) (name string, par2 string) { return strings.TrimSpace(in[:i+1]), strings.TrimSpace(in[i+1:]) } -func ShortcodeRender(name string, data *ShortcodeWithPage, t bundle.Template) string { +func ShortcodeRender(tmpl *template.Template, data *ShortcodeWithPage) string { buffer := new(bytes.Buffer) - t.ExecuteTemplate(buffer, "shortcodes/"+name+".html", data) + tmpl.Execute(buffer, data) return buffer.String() }