// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. package markdown import ( "fmt" "strings" ) var htmlEscaper = strings.NewReplacer( `&`, "&", `<`, "<", `>`, ">", `"`, """, ) // RenderHTML produces HTML with the same behavior as the example renderer used in the CommonMark // reference materials except for one slight difference: for brevity, no unnecessary whitespace is // inserted between elements. The output is not defined by the CommonMark spec, and it exists // primarily as an aid in testing. func RenderHTML(markdown string) string { return RenderBlockHTML(Parse(markdown)) } func RenderBlockHTML(block Block, referenceDefinitions []*ReferenceDefinition) (result string) { return renderBlockHTML(block, referenceDefinitions, false) } func renderBlockHTML(block Block, referenceDefinitions []*ReferenceDefinition, isTightList bool) string { var resultSb strings.Builder switch v := block.(type) { case *Document: for _, block := range v.Children { resultSb.WriteString(RenderBlockHTML(block, referenceDefinitions)) } case *Paragraph: if len(v.Text) == 0 { return "" } if !isTightList { resultSb.WriteString("

") } for _, inline := range v.ParseInlines(referenceDefinitions) { resultSb.WriteString(RenderInlineHTML(inline)) } if !isTightList { resultSb.WriteString("

") } case *List: if v.IsOrdered { if v.OrderedStart != 1 { resultSb.WriteString(fmt.Sprintf(`
    `, v.OrderedStart)) } else { resultSb.WriteString("
      ") } } else { resultSb.WriteString("
    ") } else { resultSb.WriteString("") } case *ListItem: resultSb.WriteString("
  1. ") for _, block := range v.Children { resultSb.WriteString(renderBlockHTML(block, referenceDefinitions, isTightList)) } resultSb.WriteString("
  2. ") case *BlockQuote: resultSb.WriteString("
    ") for _, block := range v.Children { resultSb.WriteString(RenderBlockHTML(block, referenceDefinitions)) } resultSb.WriteString("
    ") case *FencedCode: if info := v.Info(); info != "" { language := strings.Fields(info)[0] resultSb.WriteString(`
    `)
    		} else {
    			resultSb.WriteString("
    ")
    		}
    		resultSb.WriteString(htmlEscaper.Replace(v.Code()))
    		resultSb.WriteString("
    ") case *IndentedCode: resultSb.WriteString("
    ")
    		resultSb.WriteString(htmlEscaper.Replace(v.Code()))
    		resultSb.WriteString("
    ") default: panic(fmt.Sprintf("missing case for type %T", v)) } return resultSb.String() } func escapeURL(url string) (result string) { for i := 0; i < len(url); { switch b := url[i]; b { case ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '-', '_', '.', '!', '~', '*', '\'', '(', ')', '#': result += string(b) i++ default: if b == '%' && i+2 < len(url) && isHexByte(url[i+1]) && isHexByte(url[i+2]) { result += url[i : i+3] i += 3 } else if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') { result += string(b) i++ } else { result += fmt.Sprintf("%%%0X", b) i++ } } } return } func RenderInlineHTML(inline Inline) (result string) { switch v := inline.(type) { case *Text: return htmlEscaper.Replace(v.Text) case *HardLineBreak: return "
    " case *SoftLineBreak: return "\n" case *CodeSpan: return "" + htmlEscaper.Replace(v.Code) + "" case *InlineImage: result += `` + htmlEscaper.Replace(renderImageAltText(v.Children)) + `` case *ReferenceImage: result += `` + htmlEscaper.Replace(renderImageAltText(v.Children)) + `` case *InlineLink: var resultSb strings.Builder resultSb.WriteString(``) for _, inline := range v.Children { resultSb.WriteString(RenderInlineHTML(inline)) } resultSb.WriteString("") return resultSb.String() case *ReferenceLink: var resultSb strings.Builder resultSb.WriteString(``) for _, inline := range v.Children { resultSb.WriteString(RenderInlineHTML(inline)) } resultSb.WriteString("") return resultSb.String() case *Autolink: var resultSb strings.Builder resultSb.WriteString(``) for _, inline := range v.Children { resultSb.WriteString(RenderInlineHTML(inline)) } resultSb.WriteString("") return resultSb.String() case *Emoji: escapedName := htmlEscaper.Replace(v.Name) result += fmt.Sprintf(``, escapedName, escapedName) default: panic(fmt.Sprintf("missing case for type %T", v)) } return } func renderImageAltText(children []Inline) string { var resultSb strings.Builder for _, inline := range children { resultSb.WriteString(renderImageChildAltText(inline)) } return resultSb.String() } func renderImageChildAltText(inline Inline) string { switch v := inline.(type) { case *Text: return v.Text case *InlineImage: var resultSb strings.Builder for _, inline := range v.Children { resultSb.WriteString(renderImageChildAltText(inline)) } return resultSb.String() case *InlineLink: var resultSb strings.Builder for _, inline := range v.Children { resultSb.WriteString(renderImageChildAltText(inline)) } return resultSb.String() } return "" }