github.com/ashishbhate/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  	`<`, "&lt;",
    14  	`>`, "&gt;",
    15  	`"`, "&quot;",
    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  }