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