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