package markdown import ( "html" "strings" "unsafe" ) var ( headerStart = []string{"

", "

", "

", "

", "

", "
"} headerEnd = []string{"
", "", "", "", "", ""} ) // renderer represents a Markdown to HTML renderer. type renderer struct { out []byte paragraphLevel int quoteLevel int listLevel int tableLevel int codeLines int tableHeaderWritten bool inCodeBlock bool } // Render creates HTML from the supplied markdown text. func Render(markdown string) string { var ( r renderer i = 0 lineStart = 0 ) r.out = make([]byte, 0, nextPowerOf2(uint32(len(markdown)+4))) for { if i > len(markdown) { r.closeAll() for range r.quoteLevel { r.WriteString("") } return unsafe.String(unsafe.SliceData(r.out), len(r.out)) } if i != len(markdown) && markdown[i] != '\n' { i++ continue } line := markdown[lineStart:i] lineStart = i + 1 i++ r.processLine(line) } } func (r *renderer) processLine(line string) { if r.inCodeBlock { if strings.HasPrefix(line, "```") { r.WriteString("") r.inCodeBlock = false r.codeLines = 0 } else { if r.codeLines != 0 { r.WriteByte('\n') } r.WriteString(html.EscapeString(line)) r.codeLines++ } return } newQuoteLevel := 0 for strings.HasPrefix(line, ">") { line = strings.TrimSpace(line[1:]) newQuoteLevel++ } if newQuoteLevel > r.quoteLevel { r.closeParagraphs() for range newQuoteLevel - r.quoteLevel { r.WriteString("
") } } else if newQuoteLevel < r.quoteLevel { r.closeParagraphs() for range r.quoteLevel - newQuoteLevel { r.WriteString("
") } } r.quoteLevel = newQuoteLevel if len(line) == 0 { r.closeAll() return } switch line[0] { case '#': r.closeAll() space := strings.IndexByte(line, ' ') if space > 0 && space <= 6 { r.WriteString(headerStart[space-1]) r.writeText(line[space+1:]) r.WriteString(headerEnd[space-1]) } return case '-', '*': if strings.HasPrefix(line, "---") { r.WriteString("
") return } if len(line) > 1 && line[1] == ' ' { line = line[2:] if r.listLevel == 0 { r.WriteString("