vendor/github.com/russross/blackfriday/v2/html.go
changeset 272 e9ffa471eeb3
parent 271 c8b8b7cc8896
child 273 63ec80976891
equal deleted inserted replaced
271:c8b8b7cc8896 272:e9ffa471eeb3
     1 //
       
     2 // Blackfriday Markdown Processor
       
     3 // Available at http://github.com/russross/blackfriday
       
     4 //
       
     5 // Copyright © 2011 Russ Ross <russ@russross.com>.
       
     6 // Distributed under the Simplified BSD License.
       
     7 // See README.md for details.
       
     8 //
       
     9 
       
    10 //
       
    11 //
       
    12 // HTML rendering backend
       
    13 //
       
    14 //
       
    15 
       
    16 package blackfriday
       
    17 
       
    18 import (
       
    19 	"bytes"
       
    20 	"fmt"
       
    21 	"io"
       
    22 	"regexp"
       
    23 	"strings"
       
    24 )
       
    25 
       
    26 // HTMLFlags control optional behavior of HTML renderer.
       
    27 type HTMLFlags int
       
    28 
       
    29 // HTML renderer configuration options.
       
    30 const (
       
    31 	HTMLFlagsNone           HTMLFlags = 0
       
    32 	SkipHTML                HTMLFlags = 1 << iota // Skip preformatted HTML blocks
       
    33 	SkipImages                                    // Skip embedded images
       
    34 	SkipLinks                                     // Skip all links
       
    35 	Safelink                                      // Only link to trusted protocols
       
    36 	NofollowLinks                                 // Only link with rel="nofollow"
       
    37 	NoreferrerLinks                               // Only link with rel="noreferrer"
       
    38 	NoopenerLinks                                 // Only link with rel="noopener"
       
    39 	HrefTargetBlank                               // Add a blank target
       
    40 	CompletePage                                  // Generate a complete HTML page
       
    41 	UseXHTML                                      // Generate XHTML output instead of HTML
       
    42 	FootnoteReturnLinks                           // Generate a link at the end of a footnote to return to the source
       
    43 	Smartypants                                   // Enable smart punctuation substitutions
       
    44 	SmartypantsFractions                          // Enable smart fractions (with Smartypants)
       
    45 	SmartypantsDashes                             // Enable smart dashes (with Smartypants)
       
    46 	SmartypantsLatexDashes                        // Enable LaTeX-style dashes (with Smartypants)
       
    47 	SmartypantsAngledQuotes                       // Enable angled double quotes (with Smartypants) for double quotes rendering
       
    48 	SmartypantsQuotesNBSP                         // Enable « French guillemets » (with Smartypants)
       
    49 	TOC                                           // Generate a table of contents
       
    50 )
       
    51 
       
    52 var (
       
    53 	htmlTagRe = regexp.MustCompile("(?i)^" + htmlTag)
       
    54 )
       
    55 
       
    56 const (
       
    57 	htmlTag = "(?:" + openTag + "|" + closeTag + "|" + htmlComment + "|" +
       
    58 		processingInstruction + "|" + declaration + "|" + cdata + ")"
       
    59 	closeTag              = "</" + tagName + "\\s*[>]"
       
    60 	openTag               = "<" + tagName + attribute + "*" + "\\s*/?>"
       
    61 	attribute             = "(?:" + "\\s+" + attributeName + attributeValueSpec + "?)"
       
    62 	attributeValue        = "(?:" + unquotedValue + "|" + singleQuotedValue + "|" + doubleQuotedValue + ")"
       
    63 	attributeValueSpec    = "(?:" + "\\s*=" + "\\s*" + attributeValue + ")"
       
    64 	attributeName         = "[a-zA-Z_:][a-zA-Z0-9:._-]*"
       
    65 	cdata                 = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"
       
    66 	declaration           = "<![A-Z]+" + "\\s+[^>]*>"
       
    67 	doubleQuotedValue     = "\"[^\"]*\""
       
    68 	htmlComment           = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->"
       
    69 	processingInstruction = "[<][?].*?[?][>]"
       
    70 	singleQuotedValue     = "'[^']*'"
       
    71 	tagName               = "[A-Za-z][A-Za-z0-9-]*"
       
    72 	unquotedValue         = "[^\"'=<>`\\x00-\\x20]+"
       
    73 )
       
    74 
       
    75 // HTMLRendererParameters is a collection of supplementary parameters tweaking
       
    76 // the behavior of various parts of HTML renderer.
       
    77 type HTMLRendererParameters struct {
       
    78 	// Prepend this text to each relative URL.
       
    79 	AbsolutePrefix string
       
    80 	// Add this text to each footnote anchor, to ensure uniqueness.
       
    81 	FootnoteAnchorPrefix string
       
    82 	// Show this text inside the <a> tag for a footnote return link, if the
       
    83 	// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
       
    84 	// <sup>[return]</sup> is used.
       
    85 	FootnoteReturnLinkContents string
       
    86 	// If set, add this text to the front of each Heading ID, to ensure
       
    87 	// uniqueness.
       
    88 	HeadingIDPrefix string
       
    89 	// If set, add this text to the back of each Heading ID, to ensure uniqueness.
       
    90 	HeadingIDSuffix string
       
    91 	// Increase heading levels: if the offset is 1, <h1> becomes <h2> etc.
       
    92 	// Negative offset is also valid.
       
    93 	// Resulting levels are clipped between 1 and 6.
       
    94 	HeadingLevelOffset int
       
    95 
       
    96 	Title string // Document title (used if CompletePage is set)
       
    97 	CSS   string // Optional CSS file URL (used if CompletePage is set)
       
    98 	Icon  string // Optional icon file URL (used if CompletePage is set)
       
    99 
       
   100 	Flags HTMLFlags // Flags allow customizing this renderer's behavior
       
   101 }
       
   102 
       
   103 // HTMLRenderer is a type that implements the Renderer interface for HTML output.
       
   104 //
       
   105 // Do not create this directly, instead use the NewHTMLRenderer function.
       
   106 type HTMLRenderer struct {
       
   107 	HTMLRendererParameters
       
   108 
       
   109 	closeTag string // how to end singleton tags: either " />" or ">"
       
   110 
       
   111 	// Track heading IDs to prevent ID collision in a single generation.
       
   112 	headingIDs map[string]int
       
   113 
       
   114 	lastOutputLen int
       
   115 	disableTags   int
       
   116 
       
   117 	sr *SPRenderer
       
   118 }
       
   119 
       
   120 const (
       
   121 	xhtmlClose = " />"
       
   122 	htmlClose  = ">"
       
   123 )
       
   124 
       
   125 // NewHTMLRenderer creates and configures an HTMLRenderer object, which
       
   126 // satisfies the Renderer interface.
       
   127 func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer {
       
   128 	// configure the rendering engine
       
   129 	closeTag := htmlClose
       
   130 	if params.Flags&UseXHTML != 0 {
       
   131 		closeTag = xhtmlClose
       
   132 	}
       
   133 
       
   134 	if params.FootnoteReturnLinkContents == "" {
       
   135 		// U+FE0E is VARIATION SELECTOR-15.
       
   136 		// It suppresses automatic emoji presentation of the preceding
       
   137 		// U+21A9 LEFTWARDS ARROW WITH HOOK on iOS and iPadOS.
       
   138 		params.FootnoteReturnLinkContents = "<span aria-label='Return'>↩\ufe0e</span>"
       
   139 	}
       
   140 
       
   141 	return &HTMLRenderer{
       
   142 		HTMLRendererParameters: params,
       
   143 
       
   144 		closeTag:   closeTag,
       
   145 		headingIDs: make(map[string]int),
       
   146 
       
   147 		sr: NewSmartypantsRenderer(params.Flags),
       
   148 	}
       
   149 }
       
   150 
       
   151 func isHTMLTag(tag []byte, tagname string) bool {
       
   152 	found, _ := findHTMLTagPos(tag, tagname)
       
   153 	return found
       
   154 }
       
   155 
       
   156 // Look for a character, but ignore it when it's in any kind of quotes, it
       
   157 // might be JavaScript
       
   158 func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
       
   159 	inSingleQuote := false
       
   160 	inDoubleQuote := false
       
   161 	inGraveQuote := false
       
   162 	i := start
       
   163 	for i < len(html) {
       
   164 		switch {
       
   165 		case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
       
   166 			return i
       
   167 		case html[i] == '\'':
       
   168 			inSingleQuote = !inSingleQuote
       
   169 		case html[i] == '"':
       
   170 			inDoubleQuote = !inDoubleQuote
       
   171 		case html[i] == '`':
       
   172 			inGraveQuote = !inGraveQuote
       
   173 		}
       
   174 		i++
       
   175 	}
       
   176 	return start
       
   177 }
       
   178 
       
   179 func findHTMLTagPos(tag []byte, tagname string) (bool, int) {
       
   180 	i := 0
       
   181 	if i < len(tag) && tag[0] != '<' {
       
   182 		return false, -1
       
   183 	}
       
   184 	i++
       
   185 	i = skipSpace(tag, i)
       
   186 
       
   187 	if i < len(tag) && tag[i] == '/' {
       
   188 		i++
       
   189 	}
       
   190 
       
   191 	i = skipSpace(tag, i)
       
   192 	j := 0
       
   193 	for ; i < len(tag); i, j = i+1, j+1 {
       
   194 		if j >= len(tagname) {
       
   195 			break
       
   196 		}
       
   197 
       
   198 		if strings.ToLower(string(tag[i]))[0] != tagname[j] {
       
   199 			return false, -1
       
   200 		}
       
   201 	}
       
   202 
       
   203 	if i == len(tag) {
       
   204 		return false, -1
       
   205 	}
       
   206 
       
   207 	rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
       
   208 	if rightAngle >= i {
       
   209 		return true, rightAngle
       
   210 	}
       
   211 
       
   212 	return false, -1
       
   213 }
       
   214 
       
   215 func skipSpace(tag []byte, i int) int {
       
   216 	for i < len(tag) && isspace(tag[i]) {
       
   217 		i++
       
   218 	}
       
   219 	return i
       
   220 }
       
   221 
       
   222 func isRelativeLink(link []byte) (yes bool) {
       
   223 	// a tag begin with '#'
       
   224 	if link[0] == '#' {
       
   225 		return true
       
   226 	}
       
   227 
       
   228 	// link begin with '/' but not '//', the second maybe a protocol relative link
       
   229 	if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
       
   230 		return true
       
   231 	}
       
   232 
       
   233 	// only the root '/'
       
   234 	if len(link) == 1 && link[0] == '/' {
       
   235 		return true
       
   236 	}
       
   237 
       
   238 	// current directory : begin with "./"
       
   239 	if bytes.HasPrefix(link, []byte("./")) {
       
   240 		return true
       
   241 	}
       
   242 
       
   243 	// parent directory : begin with "../"
       
   244 	if bytes.HasPrefix(link, []byte("../")) {
       
   245 		return true
       
   246 	}
       
   247 
       
   248 	return false
       
   249 }
       
   250 
       
   251 func (r *HTMLRenderer) ensureUniqueHeadingID(id string) string {
       
   252 	for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] {
       
   253 		tmp := fmt.Sprintf("%s-%d", id, count+1)
       
   254 
       
   255 		if _, tmpFound := r.headingIDs[tmp]; !tmpFound {
       
   256 			r.headingIDs[id] = count + 1
       
   257 			id = tmp
       
   258 		} else {
       
   259 			id = id + "-1"
       
   260 		}
       
   261 	}
       
   262 
       
   263 	if _, found := r.headingIDs[id]; !found {
       
   264 		r.headingIDs[id] = 0
       
   265 	}
       
   266 
       
   267 	return id
       
   268 }
       
   269 
       
   270 func (r *HTMLRenderer) addAbsPrefix(link []byte) []byte {
       
   271 	if r.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
       
   272 		newDest := r.AbsolutePrefix
       
   273 		if link[0] != '/' {
       
   274 			newDest += "/"
       
   275 		}
       
   276 		newDest += string(link)
       
   277 		return []byte(newDest)
       
   278 	}
       
   279 	return link
       
   280 }
       
   281 
       
   282 func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string {
       
   283 	if isRelativeLink(link) {
       
   284 		return attrs
       
   285 	}
       
   286 	val := []string{}
       
   287 	if flags&NofollowLinks != 0 {
       
   288 		val = append(val, "nofollow")
       
   289 	}
       
   290 	if flags&NoreferrerLinks != 0 {
       
   291 		val = append(val, "noreferrer")
       
   292 	}
       
   293 	if flags&NoopenerLinks != 0 {
       
   294 		val = append(val, "noopener")
       
   295 	}
       
   296 	if flags&HrefTargetBlank != 0 {
       
   297 		attrs = append(attrs, "target=\"_blank\"")
       
   298 	}
       
   299 	if len(val) == 0 {
       
   300 		return attrs
       
   301 	}
       
   302 	attr := fmt.Sprintf("rel=%q", strings.Join(val, " "))
       
   303 	return append(attrs, attr)
       
   304 }
       
   305 
       
   306 func isMailto(link []byte) bool {
       
   307 	return bytes.HasPrefix(link, []byte("mailto:"))
       
   308 }
       
   309 
       
   310 func needSkipLink(flags HTMLFlags, dest []byte) bool {
       
   311 	if flags&SkipLinks != 0 {
       
   312 		return true
       
   313 	}
       
   314 	return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest)
       
   315 }
       
   316 
       
   317 func isSmartypantable(node *Node) bool {
       
   318 	pt := node.Parent.Type
       
   319 	return pt != Link && pt != CodeBlock && pt != Code
       
   320 }
       
   321 
       
   322 func appendLanguageAttr(attrs []string, info []byte) []string {
       
   323 	if len(info) == 0 {
       
   324 		return attrs
       
   325 	}
       
   326 	endOfLang := bytes.IndexAny(info, "\t ")
       
   327 	if endOfLang < 0 {
       
   328 		endOfLang = len(info)
       
   329 	}
       
   330 	return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang]))
       
   331 }
       
   332 
       
   333 func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) {
       
   334 	w.Write(name)
       
   335 	if len(attrs) > 0 {
       
   336 		w.Write(spaceBytes)
       
   337 		w.Write([]byte(strings.Join(attrs, " ")))
       
   338 	}
       
   339 	w.Write(gtBytes)
       
   340 	r.lastOutputLen = 1
       
   341 }
       
   342 
       
   343 func footnoteRef(prefix string, node *Node) []byte {
       
   344 	urlFrag := prefix + string(slugify(node.Destination))
       
   345 	anchor := fmt.Sprintf(`<a href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
       
   346 	return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor))
       
   347 }
       
   348 
       
   349 func footnoteItem(prefix string, slug []byte) []byte {
       
   350 	return []byte(fmt.Sprintf(`<li id="fn:%s%s">`, prefix, slug))
       
   351 }
       
   352 
       
   353 func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte {
       
   354 	const format = ` <a class="footnote-return" href="#fnref:%s%s">%s</a>`
       
   355 	return []byte(fmt.Sprintf(format, prefix, slug, returnLink))
       
   356 }
       
   357 
       
   358 func itemOpenCR(node *Node) bool {
       
   359 	if node.Prev == nil {
       
   360 		return false
       
   361 	}
       
   362 	ld := node.Parent.ListData
       
   363 	return !ld.Tight && ld.ListFlags&ListTypeDefinition == 0
       
   364 }
       
   365 
       
   366 func skipParagraphTags(node *Node) bool {
       
   367 	grandparent := node.Parent.Parent
       
   368 	if grandparent == nil || grandparent.Type != List {
       
   369 		return false
       
   370 	}
       
   371 	tightOrTerm := grandparent.Tight || node.Parent.ListFlags&ListTypeTerm != 0
       
   372 	return grandparent.Type == List && tightOrTerm
       
   373 }
       
   374 
       
   375 func cellAlignment(align CellAlignFlags) string {
       
   376 	switch align {
       
   377 	case TableAlignmentLeft:
       
   378 		return "left"
       
   379 	case TableAlignmentRight:
       
   380 		return "right"
       
   381 	case TableAlignmentCenter:
       
   382 		return "center"
       
   383 	default:
       
   384 		return ""
       
   385 	}
       
   386 }
       
   387 
       
   388 func (r *HTMLRenderer) out(w io.Writer, text []byte) {
       
   389 	if r.disableTags > 0 {
       
   390 		w.Write(htmlTagRe.ReplaceAll(text, []byte{}))
       
   391 	} else {
       
   392 		w.Write(text)
       
   393 	}
       
   394 	r.lastOutputLen = len(text)
       
   395 }
       
   396 
       
   397 func (r *HTMLRenderer) cr(w io.Writer) {
       
   398 	if r.lastOutputLen > 0 {
       
   399 		r.out(w, nlBytes)
       
   400 	}
       
   401 }
       
   402 
       
   403 var (
       
   404 	nlBytes    = []byte{'\n'}
       
   405 	gtBytes    = []byte{'>'}
       
   406 	spaceBytes = []byte{' '}
       
   407 )
       
   408 
       
   409 var (
       
   410 	brTag              = []byte("<br>")
       
   411 	brXHTMLTag         = []byte("<br />")
       
   412 	emTag              = []byte("<em>")
       
   413 	emCloseTag         = []byte("</em>")
       
   414 	strongTag          = []byte("<strong>")
       
   415 	strongCloseTag     = []byte("</strong>")
       
   416 	delTag             = []byte("<del>")
       
   417 	delCloseTag        = []byte("</del>")
       
   418 	ttTag              = []byte("<tt>")
       
   419 	ttCloseTag         = []byte("</tt>")
       
   420 	aTag               = []byte("<a")
       
   421 	aCloseTag          = []byte("</a>")
       
   422 	preTag             = []byte("<pre>")
       
   423 	preCloseTag        = []byte("</pre>")
       
   424 	codeTag            = []byte("<code>")
       
   425 	codeCloseTag       = []byte("</code>")
       
   426 	pTag               = []byte("<p>")
       
   427 	pCloseTag          = []byte("</p>")
       
   428 	blockquoteTag      = []byte("<blockquote>")
       
   429 	blockquoteCloseTag = []byte("</blockquote>")
       
   430 	hrTag              = []byte("<hr>")
       
   431 	hrXHTMLTag         = []byte("<hr />")
       
   432 	ulTag              = []byte("<ul>")
       
   433 	ulCloseTag         = []byte("</ul>")
       
   434 	olTag              = []byte("<ol>")
       
   435 	olCloseTag         = []byte("</ol>")
       
   436 	dlTag              = []byte("<dl>")
       
   437 	dlCloseTag         = []byte("</dl>")
       
   438 	liTag              = []byte("<li>")
       
   439 	liCloseTag         = []byte("</li>")
       
   440 	ddTag              = []byte("<dd>")
       
   441 	ddCloseTag         = []byte("</dd>")
       
   442 	dtTag              = []byte("<dt>")
       
   443 	dtCloseTag         = []byte("</dt>")
       
   444 	tableTag           = []byte("<table>")
       
   445 	tableCloseTag      = []byte("</table>")
       
   446 	tdTag              = []byte("<td")
       
   447 	tdCloseTag         = []byte("</td>")
       
   448 	thTag              = []byte("<th")
       
   449 	thCloseTag         = []byte("</th>")
       
   450 	theadTag           = []byte("<thead>")
       
   451 	theadCloseTag      = []byte("</thead>")
       
   452 	tbodyTag           = []byte("<tbody>")
       
   453 	tbodyCloseTag      = []byte("</tbody>")
       
   454 	trTag              = []byte("<tr>")
       
   455 	trCloseTag         = []byte("</tr>")
       
   456 	h1Tag              = []byte("<h1")
       
   457 	h1CloseTag         = []byte("</h1>")
       
   458 	h2Tag              = []byte("<h2")
       
   459 	h2CloseTag         = []byte("</h2>")
       
   460 	h3Tag              = []byte("<h3")
       
   461 	h3CloseTag         = []byte("</h3>")
       
   462 	h4Tag              = []byte("<h4")
       
   463 	h4CloseTag         = []byte("</h4>")
       
   464 	h5Tag              = []byte("<h5")
       
   465 	h5CloseTag         = []byte("</h5>")
       
   466 	h6Tag              = []byte("<h6")
       
   467 	h6CloseTag         = []byte("</h6>")
       
   468 
       
   469 	footnotesDivBytes      = []byte("\n<div class=\"footnotes\">\n\n")
       
   470 	footnotesCloseDivBytes = []byte("\n</div>\n")
       
   471 )
       
   472 
       
   473 func headingTagsFromLevel(level int) ([]byte, []byte) {
       
   474 	if level <= 1 {
       
   475 		return h1Tag, h1CloseTag
       
   476 	}
       
   477 	switch level {
       
   478 	case 2:
       
   479 		return h2Tag, h2CloseTag
       
   480 	case 3:
       
   481 		return h3Tag, h3CloseTag
       
   482 	case 4:
       
   483 		return h4Tag, h4CloseTag
       
   484 	case 5:
       
   485 		return h5Tag, h5CloseTag
       
   486 	}
       
   487 	return h6Tag, h6CloseTag
       
   488 }
       
   489 
       
   490 func (r *HTMLRenderer) outHRTag(w io.Writer) {
       
   491 	if r.Flags&UseXHTML == 0 {
       
   492 		r.out(w, hrTag)
       
   493 	} else {
       
   494 		r.out(w, hrXHTMLTag)
       
   495 	}
       
   496 }
       
   497 
       
   498 // RenderNode is a default renderer of a single node of a syntax tree. For
       
   499 // block nodes it will be called twice: first time with entering=true, second
       
   500 // time with entering=false, so that it could know when it's working on an open
       
   501 // tag and when on close. It writes the result to w.
       
   502 //
       
   503 // The return value is a way to tell the calling walker to adjust its walk
       
   504 // pattern: e.g. it can terminate the traversal by returning Terminate. Or it
       
   505 // can ask the walker to skip a subtree of this node by returning SkipChildren.
       
   506 // The typical behavior is to return GoToNext, which asks for the usual
       
   507 // traversal to the next node.
       
   508 func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus {
       
   509 	attrs := []string{}
       
   510 	switch node.Type {
       
   511 	case Text:
       
   512 		if r.Flags&Smartypants != 0 {
       
   513 			var tmp bytes.Buffer
       
   514 			escapeHTML(&tmp, node.Literal)
       
   515 			r.sr.Process(w, tmp.Bytes())
       
   516 		} else {
       
   517 			if node.Parent.Type == Link {
       
   518 				escLink(w, node.Literal)
       
   519 			} else {
       
   520 				escapeHTML(w, node.Literal)
       
   521 			}
       
   522 		}
       
   523 	case Softbreak:
       
   524 		r.cr(w)
       
   525 		// TODO: make it configurable via out(renderer.softbreak)
       
   526 	case Hardbreak:
       
   527 		if r.Flags&UseXHTML == 0 {
       
   528 			r.out(w, brTag)
       
   529 		} else {
       
   530 			r.out(w, brXHTMLTag)
       
   531 		}
       
   532 		r.cr(w)
       
   533 	case Emph:
       
   534 		if entering {
       
   535 			r.out(w, emTag)
       
   536 		} else {
       
   537 			r.out(w, emCloseTag)
       
   538 		}
       
   539 	case Strong:
       
   540 		if entering {
       
   541 			r.out(w, strongTag)
       
   542 		} else {
       
   543 			r.out(w, strongCloseTag)
       
   544 		}
       
   545 	case Del:
       
   546 		if entering {
       
   547 			r.out(w, delTag)
       
   548 		} else {
       
   549 			r.out(w, delCloseTag)
       
   550 		}
       
   551 	case HTMLSpan:
       
   552 		if r.Flags&SkipHTML != 0 {
       
   553 			break
       
   554 		}
       
   555 		r.out(w, node.Literal)
       
   556 	case Link:
       
   557 		// mark it but don't link it if it is not a safe link: no smartypants
       
   558 		dest := node.LinkData.Destination
       
   559 		if needSkipLink(r.Flags, dest) {
       
   560 			if entering {
       
   561 				r.out(w, ttTag)
       
   562 			} else {
       
   563 				r.out(w, ttCloseTag)
       
   564 			}
       
   565 		} else {
       
   566 			if entering {
       
   567 				dest = r.addAbsPrefix(dest)
       
   568 				var hrefBuf bytes.Buffer
       
   569 				hrefBuf.WriteString("href=\"")
       
   570 				escLink(&hrefBuf, dest)
       
   571 				hrefBuf.WriteByte('"')
       
   572 				attrs = append(attrs, hrefBuf.String())
       
   573 				if node.NoteID != 0 {
       
   574 					r.out(w, footnoteRef(r.FootnoteAnchorPrefix, node))
       
   575 					break
       
   576 				}
       
   577 				attrs = appendLinkAttrs(attrs, r.Flags, dest)
       
   578 				if len(node.LinkData.Title) > 0 {
       
   579 					var titleBuff bytes.Buffer
       
   580 					titleBuff.WriteString("title=\"")
       
   581 					escapeHTML(&titleBuff, node.LinkData.Title)
       
   582 					titleBuff.WriteByte('"')
       
   583 					attrs = append(attrs, titleBuff.String())
       
   584 				}
       
   585 				r.tag(w, aTag, attrs)
       
   586 			} else {
       
   587 				if node.NoteID != 0 {
       
   588 					break
       
   589 				}
       
   590 				r.out(w, aCloseTag)
       
   591 			}
       
   592 		}
       
   593 	case Image:
       
   594 		if r.Flags&SkipImages != 0 {
       
   595 			return SkipChildren
       
   596 		}
       
   597 		if entering {
       
   598 			dest := node.LinkData.Destination
       
   599 			dest = r.addAbsPrefix(dest)
       
   600 			if r.disableTags == 0 {
       
   601 				//if options.safe && potentiallyUnsafe(dest) {
       
   602 				//out(w, `<img src="" alt="`)
       
   603 				//} else {
       
   604 				r.out(w, []byte(`<img src="`))
       
   605 				escLink(w, dest)
       
   606 				r.out(w, []byte(`" alt="`))
       
   607 				//}
       
   608 			}
       
   609 			r.disableTags++
       
   610 		} else {
       
   611 			r.disableTags--
       
   612 			if r.disableTags == 0 {
       
   613 				if node.LinkData.Title != nil {
       
   614 					r.out(w, []byte(`" title="`))
       
   615 					escapeHTML(w, node.LinkData.Title)
       
   616 				}
       
   617 				r.out(w, []byte(`" />`))
       
   618 			}
       
   619 		}
       
   620 	case Code:
       
   621 		r.out(w, codeTag)
       
   622 		escapeAllHTML(w, node.Literal)
       
   623 		r.out(w, codeCloseTag)
       
   624 	case Document:
       
   625 		break
       
   626 	case Paragraph:
       
   627 		if skipParagraphTags(node) {
       
   628 			break
       
   629 		}
       
   630 		if entering {
       
   631 			// TODO: untangle this clusterfuck about when the newlines need
       
   632 			// to be added and when not.
       
   633 			if node.Prev != nil {
       
   634 				switch node.Prev.Type {
       
   635 				case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule:
       
   636 					r.cr(w)
       
   637 				}
       
   638 			}
       
   639 			if node.Parent.Type == BlockQuote && node.Prev == nil {
       
   640 				r.cr(w)
       
   641 			}
       
   642 			r.out(w, pTag)
       
   643 		} else {
       
   644 			r.out(w, pCloseTag)
       
   645 			if !(node.Parent.Type == Item && node.Next == nil) {
       
   646 				r.cr(w)
       
   647 			}
       
   648 		}
       
   649 	case BlockQuote:
       
   650 		if entering {
       
   651 			r.cr(w)
       
   652 			r.out(w, blockquoteTag)
       
   653 		} else {
       
   654 			r.out(w, blockquoteCloseTag)
       
   655 			r.cr(w)
       
   656 		}
       
   657 	case HTMLBlock:
       
   658 		if r.Flags&SkipHTML != 0 {
       
   659 			break
       
   660 		}
       
   661 		r.cr(w)
       
   662 		r.out(w, node.Literal)
       
   663 		r.cr(w)
       
   664 	case Heading:
       
   665 		headingLevel := r.HTMLRendererParameters.HeadingLevelOffset + node.Level
       
   666 		openTag, closeTag := headingTagsFromLevel(headingLevel)
       
   667 		if entering {
       
   668 			if node.IsTitleblock {
       
   669 				attrs = append(attrs, `class="title"`)
       
   670 			}
       
   671 			if node.HeadingID != "" {
       
   672 				id := r.ensureUniqueHeadingID(node.HeadingID)
       
   673 				if r.HeadingIDPrefix != "" {
       
   674 					id = r.HeadingIDPrefix + id
       
   675 				}
       
   676 				if r.HeadingIDSuffix != "" {
       
   677 					id = id + r.HeadingIDSuffix
       
   678 				}
       
   679 				attrs = append(attrs, fmt.Sprintf(`id="%s"`, id))
       
   680 			}
       
   681 			r.cr(w)
       
   682 			r.tag(w, openTag, attrs)
       
   683 		} else {
       
   684 			r.out(w, closeTag)
       
   685 			if !(node.Parent.Type == Item && node.Next == nil) {
       
   686 				r.cr(w)
       
   687 			}
       
   688 		}
       
   689 	case HorizontalRule:
       
   690 		r.cr(w)
       
   691 		r.outHRTag(w)
       
   692 		r.cr(w)
       
   693 	case List:
       
   694 		openTag := ulTag
       
   695 		closeTag := ulCloseTag
       
   696 		if node.ListFlags&ListTypeOrdered != 0 {
       
   697 			openTag = olTag
       
   698 			closeTag = olCloseTag
       
   699 		}
       
   700 		if node.ListFlags&ListTypeDefinition != 0 {
       
   701 			openTag = dlTag
       
   702 			closeTag = dlCloseTag
       
   703 		}
       
   704 		if entering {
       
   705 			if node.IsFootnotesList {
       
   706 				r.out(w, footnotesDivBytes)
       
   707 				r.outHRTag(w)
       
   708 				r.cr(w)
       
   709 			}
       
   710 			r.cr(w)
       
   711 			if node.Parent.Type == Item && node.Parent.Parent.Tight {
       
   712 				r.cr(w)
       
   713 			}
       
   714 			r.tag(w, openTag[:len(openTag)-1], attrs)
       
   715 			r.cr(w)
       
   716 		} else {
       
   717 			r.out(w, closeTag)
       
   718 			//cr(w)
       
   719 			//if node.parent.Type != Item {
       
   720 			//	cr(w)
       
   721 			//}
       
   722 			if node.Parent.Type == Item && node.Next != nil {
       
   723 				r.cr(w)
       
   724 			}
       
   725 			if node.Parent.Type == Document || node.Parent.Type == BlockQuote {
       
   726 				r.cr(w)
       
   727 			}
       
   728 			if node.IsFootnotesList {
       
   729 				r.out(w, footnotesCloseDivBytes)
       
   730 			}
       
   731 		}
       
   732 	case Item:
       
   733 		openTag := liTag
       
   734 		closeTag := liCloseTag
       
   735 		if node.ListFlags&ListTypeDefinition != 0 {
       
   736 			openTag = ddTag
       
   737 			closeTag = ddCloseTag
       
   738 		}
       
   739 		if node.ListFlags&ListTypeTerm != 0 {
       
   740 			openTag = dtTag
       
   741 			closeTag = dtCloseTag
       
   742 		}
       
   743 		if entering {
       
   744 			if itemOpenCR(node) {
       
   745 				r.cr(w)
       
   746 			}
       
   747 			if node.ListData.RefLink != nil {
       
   748 				slug := slugify(node.ListData.RefLink)
       
   749 				r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug))
       
   750 				break
       
   751 			}
       
   752 			r.out(w, openTag)
       
   753 		} else {
       
   754 			if node.ListData.RefLink != nil {
       
   755 				slug := slugify(node.ListData.RefLink)
       
   756 				if r.Flags&FootnoteReturnLinks != 0 {
       
   757 					r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug))
       
   758 				}
       
   759 			}
       
   760 			r.out(w, closeTag)
       
   761 			r.cr(w)
       
   762 		}
       
   763 	case CodeBlock:
       
   764 		attrs = appendLanguageAttr(attrs, node.Info)
       
   765 		r.cr(w)
       
   766 		r.out(w, preTag)
       
   767 		r.tag(w, codeTag[:len(codeTag)-1], attrs)
       
   768 		escapeAllHTML(w, node.Literal)
       
   769 		r.out(w, codeCloseTag)
       
   770 		r.out(w, preCloseTag)
       
   771 		if node.Parent.Type != Item {
       
   772 			r.cr(w)
       
   773 		}
       
   774 	case Table:
       
   775 		if entering {
       
   776 			r.cr(w)
       
   777 			r.out(w, tableTag)
       
   778 		} else {
       
   779 			r.out(w, tableCloseTag)
       
   780 			r.cr(w)
       
   781 		}
       
   782 	case TableCell:
       
   783 		openTag := tdTag
       
   784 		closeTag := tdCloseTag
       
   785 		if node.IsHeader {
       
   786 			openTag = thTag
       
   787 			closeTag = thCloseTag
       
   788 		}
       
   789 		if entering {
       
   790 			align := cellAlignment(node.Align)
       
   791 			if align != "" {
       
   792 				attrs = append(attrs, fmt.Sprintf(`align="%s"`, align))
       
   793 			}
       
   794 			if node.Prev == nil {
       
   795 				r.cr(w)
       
   796 			}
       
   797 			r.tag(w, openTag, attrs)
       
   798 		} else {
       
   799 			r.out(w, closeTag)
       
   800 			r.cr(w)
       
   801 		}
       
   802 	case TableHead:
       
   803 		if entering {
       
   804 			r.cr(w)
       
   805 			r.out(w, theadTag)
       
   806 		} else {
       
   807 			r.out(w, theadCloseTag)
       
   808 			r.cr(w)
       
   809 		}
       
   810 	case TableBody:
       
   811 		if entering {
       
   812 			r.cr(w)
       
   813 			r.out(w, tbodyTag)
       
   814 			// XXX: this is to adhere to a rather silly test. Should fix test.
       
   815 			if node.FirstChild == nil {
       
   816 				r.cr(w)
       
   817 			}
       
   818 		} else {
       
   819 			r.out(w, tbodyCloseTag)
       
   820 			r.cr(w)
       
   821 		}
       
   822 	case TableRow:
       
   823 		if entering {
       
   824 			r.cr(w)
       
   825 			r.out(w, trTag)
       
   826 		} else {
       
   827 			r.out(w, trCloseTag)
       
   828 			r.cr(w)
       
   829 		}
       
   830 	default:
       
   831 		panic("Unknown node type " + node.Type.String())
       
   832 	}
       
   833 	return GoToNext
       
   834 }
       
   835 
       
   836 // RenderHeader writes HTML document preamble and TOC if requested.
       
   837 func (r *HTMLRenderer) RenderHeader(w io.Writer, ast *Node) {
       
   838 	r.writeDocumentHeader(w)
       
   839 	if r.Flags&TOC != 0 {
       
   840 		r.writeTOC(w, ast)
       
   841 	}
       
   842 }
       
   843 
       
   844 // RenderFooter writes HTML document footer.
       
   845 func (r *HTMLRenderer) RenderFooter(w io.Writer, ast *Node) {
       
   846 	if r.Flags&CompletePage == 0 {
       
   847 		return
       
   848 	}
       
   849 	io.WriteString(w, "\n</body>\n</html>\n")
       
   850 }
       
   851 
       
   852 func (r *HTMLRenderer) writeDocumentHeader(w io.Writer) {
       
   853 	if r.Flags&CompletePage == 0 {
       
   854 		return
       
   855 	}
       
   856 	ending := ""
       
   857 	if r.Flags&UseXHTML != 0 {
       
   858 		io.WriteString(w, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
       
   859 		io.WriteString(w, "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
       
   860 		io.WriteString(w, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
       
   861 		ending = " /"
       
   862 	} else {
       
   863 		io.WriteString(w, "<!DOCTYPE html>\n")
       
   864 		io.WriteString(w, "<html>\n")
       
   865 	}
       
   866 	io.WriteString(w, "<head>\n")
       
   867 	io.WriteString(w, "  <title>")
       
   868 	if r.Flags&Smartypants != 0 {
       
   869 		r.sr.Process(w, []byte(r.Title))
       
   870 	} else {
       
   871 		escapeHTML(w, []byte(r.Title))
       
   872 	}
       
   873 	io.WriteString(w, "</title>\n")
       
   874 	io.WriteString(w, "  <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
       
   875 	io.WriteString(w, Version)
       
   876 	io.WriteString(w, "\"")
       
   877 	io.WriteString(w, ending)
       
   878 	io.WriteString(w, ">\n")
       
   879 	io.WriteString(w, "  <meta charset=\"utf-8\"")
       
   880 	io.WriteString(w, ending)
       
   881 	io.WriteString(w, ">\n")
       
   882 	if r.CSS != "" {
       
   883 		io.WriteString(w, "  <link rel=\"stylesheet\" type=\"text/css\" href=\"")
       
   884 		escapeHTML(w, []byte(r.CSS))
       
   885 		io.WriteString(w, "\"")
       
   886 		io.WriteString(w, ending)
       
   887 		io.WriteString(w, ">\n")
       
   888 	}
       
   889 	if r.Icon != "" {
       
   890 		io.WriteString(w, "  <link rel=\"icon\" type=\"image/x-icon\" href=\"")
       
   891 		escapeHTML(w, []byte(r.Icon))
       
   892 		io.WriteString(w, "\"")
       
   893 		io.WriteString(w, ending)
       
   894 		io.WriteString(w, ">\n")
       
   895 	}
       
   896 	io.WriteString(w, "</head>\n")
       
   897 	io.WriteString(w, "<body>\n\n")
       
   898 }
       
   899 
       
   900 func (r *HTMLRenderer) writeTOC(w io.Writer, ast *Node) {
       
   901 	buf := bytes.Buffer{}
       
   902 
       
   903 	inHeading := false
       
   904 	tocLevel := 0
       
   905 	headingCount := 0
       
   906 
       
   907 	ast.Walk(func(node *Node, entering bool) WalkStatus {
       
   908 		if node.Type == Heading && !node.HeadingData.IsTitleblock {
       
   909 			inHeading = entering
       
   910 			if entering {
       
   911 				node.HeadingID = fmt.Sprintf("toc_%d", headingCount)
       
   912 				if node.Level == tocLevel {
       
   913 					buf.WriteString("</li>\n\n<li>")
       
   914 				} else if node.Level < tocLevel {
       
   915 					for node.Level < tocLevel {
       
   916 						tocLevel--
       
   917 						buf.WriteString("</li>\n</ul>")
       
   918 					}
       
   919 					buf.WriteString("</li>\n\n<li>")
       
   920 				} else {
       
   921 					for node.Level > tocLevel {
       
   922 						tocLevel++
       
   923 						buf.WriteString("\n<ul>\n<li>")
       
   924 					}
       
   925 				}
       
   926 
       
   927 				fmt.Fprintf(&buf, `<a href="#toc_%d">`, headingCount)
       
   928 				headingCount++
       
   929 			} else {
       
   930 				buf.WriteString("</a>")
       
   931 			}
       
   932 			return GoToNext
       
   933 		}
       
   934 
       
   935 		if inHeading {
       
   936 			return r.RenderNode(&buf, node, entering)
       
   937 		}
       
   938 
       
   939 		return GoToNext
       
   940 	})
       
   941 
       
   942 	for ; tocLevel > 0; tocLevel-- {
       
   943 		buf.WriteString("</li>\n</ul>")
       
   944 	}
       
   945 
       
   946 	if buf.Len() > 0 {
       
   947 		io.WriteString(w, "<nav>\n")
       
   948 		w.Write(buf.Bytes())
       
   949 		io.WriteString(w, "\n\n</nav>\n")
       
   950 	}
       
   951 	r.lastOutputLen = buf.Len()
       
   952 }