package markdown import ( "html" "strings" ) var ( headerStart = []string{"<h1>", "<h2>", "<h3>", "<h4>", "<h5>", "<h6>"} headerEnd = []string{"</h1>", "</h2>", "</h3>", "</h4>", "</h5>", "</h6>"} ) // Render creates HTML from the supplied markdown text. func Render(markdown string) string { var ( out = strings.Builder{} paragraph = strings.Builder{} i = 0 lineStart = 0 ) flush := func() { if paragraph.Len() == 0 { return } out.WriteString("<p>") writeText(&out, paragraph.String()) out.WriteString("</p>") paragraph.Reset() } for { if i > len(markdown) { flush() return out.String() } if i != len(markdown) && markdown[i] != '\n' { i++ continue } line := markdown[lineStart:i] lineStart = i + 1 i++ switch { case strings.HasPrefix(line, "#"): flush() space := strings.IndexByte(line, ' ') if space > 0 && space <= 6 { out.WriteString(headerStart[space-1]) writeText(&out, line[space+1:]) out.WriteString(headerEnd[space-1]) } default: if len(line) == 0 { flush() continue } if paragraph.Len() > 0 { paragraph.WriteByte(' ') } paragraph.WriteString(line) } } } func writeText(out *strings.Builder, text string) { var ( i = 0 tokenStart = 0 ) var ( textStart = -1 textEnd = -1 urlStart = -1 parentheses = 0 ) for { if i == len(text) { out.WriteString(html.EscapeString(text[tokenStart:])) return } c := text[i] switch c { case '[': out.WriteString(html.EscapeString(text[tokenStart:i])) tokenStart = i textStart = i case ']': textEnd = i case '(': if parentheses == 0 { urlStart = i } parentheses++ case ')': parentheses-- if parentheses == 0 && textStart >= 0 && textEnd >= 0 && urlStart >= 0 { linkText := text[textStart+1 : textEnd] linkURL := text[urlStart+1 : i] out.WriteString("<a href=\"") out.WriteString(formatURL(linkURL)) out.WriteString("\">") out.WriteString(html.EscapeString(linkText)) out.WriteString("</a>") textStart = -1 textEnd = -1 urlStart = -1 tokenStart = i + 1 } } i++ } } func formatURL(linkURL string) string { if strings.HasPrefix(strings.ToLower(linkURL), "javascript:") { return "" } return html.EscapeString(linkURL) }