// Copyright (c) 2017-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) (result string) { switch v := block.(type) { case *Document: for _, block := range v.Children { result += RenderBlockHTML(block, referenceDefinitions) } case *Paragraph: if len(v.Text) == 0 { return } if !isTightList { result += "
" } for _, inline := range v.ParseInlines(referenceDefinitions) { result += RenderInlineHTML(inline) } if !isTightList { result += "
" } case *List: if v.IsOrdered { if v.OrderedStart != 1 { result += fmt.Sprintf(`" for _, block := range v.Children { result += RenderBlockHTML(block, referenceDefinitions) } result += "" case *FencedCode: if info := v.Info(); info != "" { language := strings.Fields(info)[0] result += `
`
} else {
result += ""
}
result += htmlEscaper.Replace(v.Code()) + "
"
case *IndentedCode:
result += "" + htmlEscaper.Replace(v.Code()) + "
"
default:
panic(fmt.Sprintf("missing case for type %T", v))
}
return
}
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 += ``
case *ReferenceImage:
result += ``
case *InlineLink:
result += ``
for _, inline := range v.Children {
result += RenderInlineHTML(inline)
}
result += ""
case *ReferenceLink:
result += ``
for _, inline := range v.Children {
result += RenderInlineHTML(inline)
}
result += ""
default:
panic(fmt.Sprintf("missing case for type %T", v))
}
return
}
func renderImageAltText(children []Inline) (result string) {
for _, inline := range children {
result += renderImageChildAltText(inline)
}
return
}
func renderImageChildAltText(inline Inline) (result string) {
switch v := inline.(type) {
case *Text:
return v.Text
case *InlineImage:
for _, inline := range v.Children {
result += renderImageChildAltText(inline)
}
case *InlineLink:
for _, inline := range v.Children {
result += renderImageChildAltText(inline)
}
}
return
}