github.com/fretkak/mattermost-mattermost-server@v5.11.1+incompatible/utils/markdown/html.go (about) 1 // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package markdown 5 6 import ( 7 "fmt" 8 "strings" 9 ) 10 11 var htmlEscaper = strings.NewReplacer( 12 `&`, "&", 13 `<`, "<", 14 `>`, ">", 15 `"`, """, 16 ) 17 18 // RenderHTML produces HTML with the same behavior as the example renderer used in the CommonMark 19 // reference materials except for one slight difference: for brevity, no unnecessary whitespace is 20 // inserted between elements. The output is not defined by the CommonMark spec, and it exists 21 // primarily as an aid in testing. 22 func RenderHTML(markdown string) string { 23 return RenderBlockHTML(Parse(markdown)) 24 } 25 26 func RenderBlockHTML(block Block, referenceDefinitions []*ReferenceDefinition) (result string) { 27 return renderBlockHTML(block, referenceDefinitions, false) 28 } 29 30 func renderBlockHTML(block Block, referenceDefinitions []*ReferenceDefinition, isTightList bool) (result string) { 31 switch v := block.(type) { 32 case *Document: 33 for _, block := range v.Children { 34 result += RenderBlockHTML(block, referenceDefinitions) 35 } 36 case *Paragraph: 37 if len(v.Text) == 0 { 38 return 39 } 40 if !isTightList { 41 result += "<p>" 42 } 43 for _, inline := range v.ParseInlines(referenceDefinitions) { 44 result += RenderInlineHTML(inline) 45 } 46 if !isTightList { 47 result += "</p>" 48 } 49 case *List: 50 if v.IsOrdered { 51 if v.OrderedStart != 1 { 52 result += fmt.Sprintf(`<ol start="%v">`, v.OrderedStart) 53 } else { 54 result += "<ol>" 55 } 56 } else { 57 result += "<ul>" 58 } 59 for _, block := range v.Children { 60 result += renderBlockHTML(block, referenceDefinitions, !v.IsLoose) 61 } 62 if v.IsOrdered { 63 result += "</ol>" 64 } else { 65 result += "</ul>" 66 } 67 case *ListItem: 68 result += "<li>" 69 for _, block := range v.Children { 70 result += renderBlockHTML(block, referenceDefinitions, isTightList) 71 } 72 result += "</li>" 73 case *BlockQuote: 74 result += "<blockquote>" 75 for _, block := range v.Children { 76 result += RenderBlockHTML(block, referenceDefinitions) 77 } 78 result += "</blockquote>" 79 case *FencedCode: 80 if info := v.Info(); info != "" { 81 language := strings.Fields(info)[0] 82 result += `<pre><code class="language-` + htmlEscaper.Replace(language) + `">` 83 } else { 84 result += "<pre><code>" 85 } 86 result += htmlEscaper.Replace(v.Code()) + "</code></pre>" 87 case *IndentedCode: 88 result += "<pre><code>" + htmlEscaper.Replace(v.Code()) + "</code></pre>" 89 default: 90 panic(fmt.Sprintf("missing case for type %T", v)) 91 } 92 return 93 } 94 95 func escapeURL(url string) (result string) { 96 for i := 0; i < len(url); { 97 switch b := url[i]; b { 98 case ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '-', '_', '.', '!', '~', '*', '\'', '(', ')', '#': 99 result += string(b) 100 i++ 101 default: 102 if b == '%' && i+2 < len(url) && isHexByte(url[i+1]) && isHexByte(url[i+2]) { 103 result += url[i : i+3] 104 i += 3 105 } else if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') { 106 result += string(b) 107 i++ 108 } else { 109 result += fmt.Sprintf("%%%0X", b) 110 i++ 111 } 112 } 113 } 114 return 115 } 116 117 func RenderInlineHTML(inline Inline) (result string) { 118 switch v := inline.(type) { 119 case *Text: 120 return htmlEscaper.Replace(v.Text) 121 case *HardLineBreak: 122 return "<br />" 123 case *SoftLineBreak: 124 return "\n" 125 case *CodeSpan: 126 return "<code>" + htmlEscaper.Replace(v.Code) + "</code>" 127 case *InlineImage: 128 result += `<img src="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `" alt="` + htmlEscaper.Replace(renderImageAltText(v.Children)) + `"` 129 if title := v.Title(); title != "" { 130 result += ` title="` + htmlEscaper.Replace(title) + `"` 131 } 132 result += ` />` 133 case *ReferenceImage: 134 result += `<img src="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `" alt="` + htmlEscaper.Replace(renderImageAltText(v.Children)) + `"` 135 if title := v.Title(); title != "" { 136 result += ` title="` + htmlEscaper.Replace(title) + `"` 137 } 138 result += ` />` 139 case *InlineLink: 140 result += `<a href="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `"` 141 if title := v.Title(); title != "" { 142 result += ` title="` + htmlEscaper.Replace(title) + `"` 143 } 144 result += `>` 145 for _, inline := range v.Children { 146 result += RenderInlineHTML(inline) 147 } 148 result += "</a>" 149 case *ReferenceLink: 150 result += `<a href="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `"` 151 if title := v.Title(); title != "" { 152 result += ` title="` + htmlEscaper.Replace(title) + `"` 153 } 154 result += `>` 155 for _, inline := range v.Children { 156 result += RenderInlineHTML(inline) 157 } 158 result += "</a>" 159 case *Autolink: 160 result += `<a href="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `">` 161 for _, inline := range v.Children { 162 result += RenderInlineHTML(inline) 163 } 164 result += "</a>" 165 default: 166 panic(fmt.Sprintf("missing case for type %T", v)) 167 } 168 return 169 } 170 171 func renderImageAltText(children []Inline) (result string) { 172 for _, inline := range children { 173 result += renderImageChildAltText(inline) 174 } 175 return 176 } 177 178 func renderImageChildAltText(inline Inline) (result string) { 179 switch v := inline.(type) { 180 case *Text: 181 return v.Text 182 case *InlineImage: 183 for _, inline := range v.Children { 184 result += renderImageChildAltText(inline) 185 } 186 case *InlineLink: 187 for _, inline := range v.Children { 188 result += renderImageChildAltText(inline) 189 } 190 } 191 return 192 }