all repos — grayfriday @ 7500a7e2ed7288a69834057beeec7d8bc49e6668

blackfriday fork with a few changes

html.go (view raw)

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