all repos — grayfriday @ 6438ce6de8115b65bffdce52ff135e150edf79ce

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	SkipStyle                                     // Skip embedded <style> elements
 34	SkipImages                                    // Skip embedded images
 35	SkipLinks                                     // Skip all links
 36	Safelink                                      // Only link to trusted protocols
 37	NofollowLinks                                 // Only link with rel="nofollow"
 38	NoreferrerLinks                               // Only link with rel="noreferrer"
 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
 49	TagName               = "[A-Za-z][A-Za-z0-9-]*"
 50	AttributeName         = "[a-zA-Z_:][a-zA-Z0-9:._-]*"
 51	UnquotedValue         = "[^\"'=<>`\\x00-\\x20]+"
 52	SingleQuotedValue     = "'[^']*'"
 53	DoubleQuotedValue     = "\"[^\"]*\""
 54	AttributeValue        = "(?:" + UnquotedValue + "|" + SingleQuotedValue + "|" + DoubleQuotedValue + ")"
 55	AttributeValueSpec    = "(?:" + "\\s*=" + "\\s*" + AttributeValue + ")"
 56	Attribute             = "(?:" + "\\s+" + AttributeName + AttributeValueSpec + "?)"
 57	OpenTag               = "<" + TagName + Attribute + "*" + "\\s*/?>"
 58	CloseTag              = "</" + TagName + "\\s*[>]"
 59	HTMLComment           = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->"
 60	ProcessingInstruction = "[<][?].*?[?][>]"
 61	Declaration           = "<![A-Z]+" + "\\s+[^>]*>"
 62	CDATA                 = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"
 63	HTMLTag               = "(?:" + OpenTag + "|" + CloseTag + "|" + HTMLComment + "|" +
 64		ProcessingInstruction + "|" + Declaration + "|" + CDATA + ")"
 65)
 66
 67var (
 68	htmlTagRe = regexp.MustCompile("(?i)^" + HTMLTag)
 69)
 70
 71// HTMLRendererParameters is a collection of supplementary parameters tweaking
 72// the behavior of various parts of HTML renderer.
 73type HTMLRendererParameters struct {
 74	// Prepend this text to each relative URL.
 75	AbsolutePrefix string
 76	// Add this text to each footnote anchor, to ensure uniqueness.
 77	FootnoteAnchorPrefix string
 78	// Show this text inside the <a> tag for a footnote return link, if the
 79	// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
 80	// <sup>[return]</sup> is used.
 81	FootnoteReturnLinkContents string
 82	// If set, add this text to the front of each Header ID, to ensure
 83	// uniqueness.
 84	HeaderIDPrefix string
 85	// If set, add this text to the back of each Header ID, to ensure uniqueness.
 86	HeaderIDSuffix string
 87
 88	Title string // Document title (used if CompletePage is set)
 89	CSS   string // Optional CSS file URL (used if CompletePage is set)
 90	Icon  string // Optional icon file URL (used if CompletePage is set)
 91
 92	Flags      HTMLFlags  // Flags allow customizing this renderer's behavior
 93	Extensions Extensions // Extensions give Smartypants and HTML renderer access to Blackfriday's global extensions
 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	infoWords := bytes.Split(info, []byte("\t "))
311	if len(infoWords) > 0 && len(infoWords[0]) > 0 {
312		attrs = append(attrs, fmt.Sprintf("class=\"language-%s\"", infoWords[0]))
313	}
314	return attrs
315}
316
317func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) {
318	w.Write(name)
319	if len(attrs) > 0 {
320		w.Write(spaceBytes)
321		w.Write([]byte(strings.Join(attrs, " ")))
322	}
323	w.Write(gtBytes)
324	r.lastOutputLen = 1
325}
326
327func footnoteRef(prefix string, node *Node) []byte {
328	urlFrag := prefix + string(slugify(node.Destination))
329	anchor := fmt.Sprintf(`<a rel="footnote" href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
330	return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor))
331}
332
333func footnoteItem(prefix string, slug []byte) []byte {
334	return []byte(fmt.Sprintf(`<li id="fn:%s%s">`, prefix, slug))
335}
336
337func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte {
338	const format = ` <a class="footnote-return" href="#fnref:%s%s">%s</a>`
339	return []byte(fmt.Sprintf(format, prefix, slug, returnLink))
340}
341
342func itemOpenCR(node *Node) bool {
343	if node.Prev == nil {
344		return false
345	}
346	ld := node.Parent.ListData
347	return !ld.Tight && ld.ListFlags&ListTypeDefinition == 0
348}
349
350func skipParagraphTags(node *Node) bool {
351	grandparent := node.Parent.Parent
352	if grandparent == nil || grandparent.Type != List {
353		return false
354	}
355	tightOrTerm := grandparent.Tight || node.Parent.ListFlags&ListTypeTerm != 0
356	return grandparent.Type == List && tightOrTerm
357}
358
359func cellAlignment(align CellAlignFlags) string {
360	switch align {
361	case TableAlignmentLeft:
362		return "left"
363	case TableAlignmentRight:
364		return "right"
365	case TableAlignmentCenter:
366		return "center"
367	default:
368		return ""
369	}
370}
371
372func (r *HTMLRenderer) out(w io.Writer, text []byte) {
373	if r.disableTags > 0 {
374		w.Write(htmlTagRe.ReplaceAll(text, []byte{}))
375	} else {
376		w.Write(text)
377	}
378	r.lastOutputLen = len(text)
379}
380
381func (r *HTMLRenderer) cr(w io.Writer) {
382	if r.lastOutputLen > 0 {
383		r.out(w, nlBytes)
384	}
385}
386
387var (
388	nlBytes    = []byte{'\n'}
389	gtBytes    = []byte{'>'}
390	spaceBytes = []byte{' '}
391)
392
393var (
394	brTag              = []byte("<br>")
395	brXHTMLTag         = []byte("<br />")
396	emTag              = []byte("<em>")
397	emCloseTag         = []byte("</em>")
398	strongTag          = []byte("<strong>")
399	strongCloseTag     = []byte("</strong>")
400	delTag             = []byte("<del>")
401	delCloseTag        = []byte("</del>")
402	ttTag              = []byte("<tt>")
403	ttCloseTag         = []byte("</tt>")
404	aTag               = []byte("<a")
405	aCloseTag          = []byte("</a>")
406	preTag             = []byte("<pre>")
407	preCloseTag        = []byte("</pre>")
408	codeTag            = []byte("<code>")
409	codeCloseTag       = []byte("</code>")
410	pTag               = []byte("<p>")
411	pCloseTag          = []byte("</p>")
412	blockquoteTag      = []byte("<blockquote>")
413	blockquoteCloseTag = []byte("</blockquote>")
414	hrTag              = []byte("<hr>")
415	hrXHTMLTag         = []byte("<hr />")
416	ulTag              = []byte("<ul>")
417	ulCloseTag         = []byte("</ul>")
418	olTag              = []byte("<ol>")
419	olCloseTag         = []byte("</ol>")
420	dlTag              = []byte("<dl>")
421	dlCloseTag         = []byte("</dl>")
422	liTag              = []byte("<li>")
423	liCloseTag         = []byte("</li>")
424	ddTag              = []byte("<dd>")
425	ddCloseTag         = []byte("</dd>")
426	dtTag              = []byte("<dt>")
427	dtCloseTag         = []byte("</dt>")
428	tableTag           = []byte("<table>")
429	tableCloseTag      = []byte("</table>")
430	tdTag              = []byte("<td")
431	tdCloseTag         = []byte("</td>")
432	thTag              = []byte("<th")
433	thCloseTag         = []byte("</th>")
434	theadTag           = []byte("<thead>")
435	theadCloseTag      = []byte("</thead>")
436	tbodyTag           = []byte("<tbody>")
437	tbodyCloseTag      = []byte("</tbody>")
438	trTag              = []byte("<tr>")
439	trCloseTag         = []byte("</tr>")
440	h1Tag              = []byte("<h1")
441	h1CloseTag         = []byte("</h1>")
442	h2Tag              = []byte("<h2")
443	h2CloseTag         = []byte("</h2>")
444	h3Tag              = []byte("<h3")
445	h3CloseTag         = []byte("</h3>")
446	h4Tag              = []byte("<h4")
447	h4CloseTag         = []byte("</h4>")
448	h5Tag              = []byte("<h5")
449	h5CloseTag         = []byte("</h5>")
450	h6Tag              = []byte("<h6")
451	h6CloseTag         = []byte("</h6>")
452
453	footnotesDivBytes      = []byte("\n<div class=\"footnotes\">\n\n")
454	footnotesCloseDivBytes = []byte("\n</div>\n")
455)
456
457func headerTagsFromLevel(level int) ([]byte, []byte) {
458	switch level {
459	case 1:
460		return h1Tag, h1CloseTag
461	case 2:
462		return h2Tag, h2CloseTag
463	case 3:
464		return h3Tag, h3CloseTag
465	case 4:
466		return h4Tag, h4CloseTag
467	case 5:
468		return h5Tag, h5CloseTag
469	default:
470		return h6Tag, h6CloseTag
471	}
472}
473
474func (r *HTMLRenderer) outHRTag(w io.Writer) {
475	if r.Flags&UseXHTML == 0 {
476		r.out(w, hrTag)
477	} else {
478		r.out(w, hrXHTMLTag)
479	}
480}
481
482// RenderNode is a default renderer of a single node of a syntax tree. For
483// block nodes it will be called twice: first time with entering=true, second
484// time with entering=false, so that it could know when it's working on an open
485// tag and when on close. It writes the result to w.
486//
487// The return value is a way to tell the calling walker to adjust its walk
488// pattern: e.g. it can terminate the traversal by returning Terminate. Or it
489// can ask the walker to skip a subtree of this node by returning SkipChildren.
490// The typical behavior is to return GoToNext, which asks for the usual
491// traversal to the next node.
492func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus {
493	attrs := []string{}
494	switch node.Type {
495	case Text:
496		if r.Flags&Smartypants != 0 {
497			var tmp bytes.Buffer
498			escapeHTML(&tmp, node.Literal)
499			r.sr.Process(w, tmp.Bytes())
500		} else {
501			if node.Parent.Type == Link {
502				escLink(w, node.Literal)
503			} else {
504				escapeHTML(w, node.Literal)
505			}
506		}
507	case Softbreak:
508		r.cr(w)
509		// TODO: make it configurable via out(renderer.softbreak)
510	case Hardbreak:
511		if r.Flags&UseXHTML == 0 {
512			r.out(w, brTag)
513		} else {
514			r.out(w, brXHTMLTag)
515		}
516		r.cr(w)
517	case Emph:
518		if entering {
519			r.out(w, emTag)
520		} else {
521			r.out(w, emCloseTag)
522		}
523	case Strong:
524		if entering {
525			r.out(w, strongTag)
526		} else {
527			r.out(w, strongCloseTag)
528		}
529	case Del:
530		if entering {
531			r.out(w, delTag)
532		} else {
533			r.out(w, delCloseTag)
534		}
535	case HTMLSpan:
536		if r.Flags&SkipHTML != 0 {
537			break
538		}
539		if r.Flags&SkipStyle != 0 && isHTMLTag(node.Literal, "style") {
540			break
541		}
542		//if options.safe {
543		//	out(w, "<!-- raw HTML omitted -->")
544		//} else {
545		r.out(w, node.Literal)
546		//}
547	case Link:
548		// mark it but don't link it if it is not a safe link: no smartypants
549		dest := node.LinkData.Destination
550		if needSkipLink(r.Flags, dest) {
551			if entering {
552				r.out(w, ttTag)
553			} else {
554				r.out(w, ttCloseTag)
555			}
556		} else {
557			if entering {
558				dest = r.addAbsPrefix(dest)
559				var hrefBuff bytes.Buffer
560				hrefBuff.WriteString("href=\"")
561				escLink(&hrefBuff, dest)
562				hrefBuff.WriteByte('"')
563				attrs = append(attrs, hrefBuff.String())
564				if node.NoteID != 0 {
565					r.out(w, footnoteRef(r.FootnoteAnchorPrefix, node))
566					break
567				}
568				attrs = appendLinkAttrs(attrs, r.Flags, dest)
569				if len(node.LinkData.Title) > 0 {
570					var titleBuff bytes.Buffer
571					titleBuff.WriteString("title=\"")
572					escapeHTML(&titleBuff, node.LinkData.Title)
573					titleBuff.WriteByte('"')
574					attrs = append(attrs, titleBuff.String())
575				}
576				r.tag(w, aTag, attrs)
577			} else {
578				if node.NoteID != 0 {
579					break
580				}
581				r.out(w, aCloseTag)
582			}
583		}
584	case Image:
585		if r.Flags&SkipImages != 0 {
586			return SkipChildren
587		}
588		if entering {
589			dest := node.LinkData.Destination
590			dest = r.addAbsPrefix(dest)
591			if r.disableTags == 0 {
592				//if options.safe && potentiallyUnsafe(dest) {
593				//out(w, `<img src="" alt="`)
594				//} else {
595				r.out(w, []byte(`<img src="`))
596				escLink(w, dest)
597				r.out(w, []byte(`" alt="`))
598				//}
599			}
600			r.disableTags++
601		} else {
602			r.disableTags--
603			if r.disableTags == 0 {
604				if node.LinkData.Title != nil {
605					r.out(w, []byte(`" title="`))
606					escapeHTML(w, node.LinkData.Title)
607				}
608				r.out(w, []byte(`" />`))
609			}
610		}
611	case Code:
612		r.out(w, codeTag)
613		escapeHTML(w, node.Literal)
614		r.out(w, codeCloseTag)
615	case Document:
616		break
617	case Paragraph:
618		if skipParagraphTags(node) {
619			break
620		}
621		if entering {
622			// TODO: untangle this clusterfuck about when the newlines need
623			// to be added and when not.
624			if node.Prev != nil {
625				switch node.Prev.Type {
626				case HTMLBlock, List, Paragraph, Header, CodeBlock, BlockQuote, HorizontalRule:
627					r.cr(w)
628				}
629			}
630			if node.Parent.Type == BlockQuote && node.Prev == nil {
631				r.cr(w)
632			}
633			r.out(w, pTag)
634		} else {
635			r.out(w, pCloseTag)
636			if !(node.Parent.Type == Item && node.Next == nil) {
637				r.cr(w)
638			}
639		}
640	case BlockQuote:
641		if entering {
642			r.cr(w)
643			r.out(w, blockquoteTag)
644		} else {
645			r.out(w, blockquoteCloseTag)
646			r.cr(w)
647		}
648	case HTMLBlock:
649		if r.Flags&SkipHTML != 0 {
650			break
651		}
652		r.cr(w)
653		r.out(w, node.Literal)
654		r.cr(w)
655	case Header:
656		openTag, closeTag := headerTagsFromLevel(node.Level)
657		if entering {
658			if node.IsTitleblock {
659				attrs = append(attrs, `class="title"`)
660			}
661			if node.HeaderID != "" {
662				id := r.ensureUniqueHeaderID(node.HeaderID)
663				if r.HeaderIDPrefix != "" {
664					id = r.HeaderIDPrefix + id
665				}
666				if r.HeaderIDSuffix != "" {
667					id = id + r.HeaderIDSuffix
668				}
669				attrs = append(attrs, fmt.Sprintf(`id="%s"`, id))
670			}
671			r.cr(w)
672			r.tag(w, openTag, attrs)
673		} else {
674			r.out(w, closeTag)
675			if !(node.Parent.Type == Item && node.Next == nil) {
676				r.cr(w)
677			}
678		}
679	case HorizontalRule:
680		r.cr(w)
681		r.outHRTag(w)
682		r.cr(w)
683	case List:
684		openTag := ulTag
685		closeTag := ulCloseTag
686		if node.ListFlags&ListTypeOrdered != 0 {
687			openTag = olTag
688			closeTag = olCloseTag
689		}
690		if node.ListFlags&ListTypeDefinition != 0 {
691			openTag = dlTag
692			closeTag = dlCloseTag
693		}
694		if entering {
695			if node.IsFootnotesList {
696				r.out(w, footnotesDivBytes)
697				r.outHRTag(w)
698				r.cr(w)
699			}
700			r.cr(w)
701			if node.Parent.Type == Item && node.Parent.Parent.Tight {
702				r.cr(w)
703			}
704			r.tag(w, openTag[:len(openTag)-1], attrs)
705			r.cr(w)
706		} else {
707			r.out(w, closeTag)
708			//cr(w)
709			//if node.parent.Type != Item {
710			//	cr(w)
711			//}
712			if node.Parent.Type == Item && node.Next != nil {
713				r.cr(w)
714			}
715			if node.Parent.Type == Document || node.Parent.Type == BlockQuote {
716				r.cr(w)
717			}
718			if node.IsFootnotesList {
719				r.out(w, footnotesCloseDivBytes)
720			}
721		}
722	case Item:
723		openTag := liTag
724		closeTag := liCloseTag
725		if node.ListFlags&ListTypeDefinition != 0 {
726			openTag = ddTag
727			closeTag = ddCloseTag
728		}
729		if node.ListFlags&ListTypeTerm != 0 {
730			openTag = dtTag
731			closeTag = dtCloseTag
732		}
733		if entering {
734			if itemOpenCR(node) {
735				r.cr(w)
736			}
737			if node.ListData.RefLink != nil {
738				slug := slugify(node.ListData.RefLink)
739				r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug))
740				break
741			}
742			r.out(w, openTag)
743		} else {
744			if node.ListData.RefLink != nil {
745				slug := slugify(node.ListData.RefLink)
746				if r.Flags&FootnoteReturnLinks != 0 {
747					r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug))
748				}
749			}
750			r.out(w, closeTag)
751			r.cr(w)
752		}
753	case CodeBlock:
754		attrs = appendLanguageAttr(attrs, node.Info)
755		r.cr(w)
756		r.out(w, preTag)
757		r.tag(w, codeTag[:len(codeTag)-1], attrs)
758		escapeHTML(w, node.Literal)
759		r.out(w, codeCloseTag)
760		r.out(w, preCloseTag)
761		if node.Parent.Type != Item {
762			r.cr(w)
763		}
764	case Table:
765		if entering {
766			r.cr(w)
767			r.out(w, tableTag)
768		} else {
769			r.out(w, tableCloseTag)
770			r.cr(w)
771		}
772	case TableCell:
773		openTag := tdTag
774		closeTag := tdCloseTag
775		if node.IsHeader {
776			openTag = thTag
777			closeTag = thCloseTag
778		}
779		if entering {
780			align := cellAlignment(node.Align)
781			if align != "" {
782				attrs = append(attrs, fmt.Sprintf(`align="%s"`, align))
783			}
784			if node.Prev == nil {
785				r.cr(w)
786			}
787			r.tag(w, openTag, attrs)
788		} else {
789			r.out(w, closeTag)
790			r.cr(w)
791		}
792	case TableHead:
793		if entering {
794			r.cr(w)
795			r.out(w, theadTag)
796		} else {
797			r.out(w, theadCloseTag)
798			r.cr(w)
799		}
800	case TableBody:
801		if entering {
802			r.cr(w)
803			r.out(w, tbodyTag)
804			// XXX: this is to adhere to a rather silly test. Should fix test.
805			if node.FirstChild == nil {
806				r.cr(w)
807			}
808		} else {
809			r.out(w, tbodyCloseTag)
810			r.cr(w)
811		}
812	case TableRow:
813		if entering {
814			r.cr(w)
815			r.out(w, trTag)
816		} else {
817			r.out(w, trCloseTag)
818			r.cr(w)
819		}
820	default:
821		panic("Unknown node type " + node.Type.String())
822	}
823	return GoToNext
824}
825
826func (r *HTMLRenderer) writeDocumentHeader(w *bytes.Buffer) {
827	if r.Flags&CompletePage == 0 {
828		return
829	}
830	ending := ""
831	if r.Flags&UseXHTML != 0 {
832		w.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
833		w.WriteString("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
834		w.WriteString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
835		ending = " /"
836	} else {
837		w.WriteString("<!DOCTYPE html>\n")
838		w.WriteString("<html>\n")
839	}
840	w.WriteString("<head>\n")
841	w.WriteString("  <title>")
842	if r.Flags&Smartypants != 0 {
843		r.sr.Process(w, []byte(r.Title))
844	} else {
845		escapeHTML(w, []byte(r.Title))
846	}
847	w.WriteString("</title>\n")
848	w.WriteString("  <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
849	w.WriteString(Version)
850	w.WriteString("\"")
851	w.WriteString(ending)
852	w.WriteString(">\n")
853	w.WriteString("  <meta charset=\"utf-8\"")
854	w.WriteString(ending)
855	w.WriteString(">\n")
856	if r.CSS != "" {
857		w.WriteString("  <link rel=\"stylesheet\" type=\"text/css\" href=\"")
858		escapeHTML(w, []byte(r.CSS))
859		w.WriteString("\"")
860		w.WriteString(ending)
861		w.WriteString(">\n")
862	}
863	if r.Icon != "" {
864		w.WriteString("  <link rel=\"icon\" type=\"image/x-icon\" href=\"")
865		escapeHTML(w, []byte(r.Icon))
866		w.WriteString("\"")
867		w.WriteString(ending)
868		w.WriteString(">\n")
869	}
870	w.WriteString("</head>\n")
871	w.WriteString("<body>\n\n")
872}
873
874func (r *HTMLRenderer) writeTOC(w *bytes.Buffer, ast *Node) {
875	buf := bytes.Buffer{}
876
877	inHeader := false
878	tocLevel := 0
879	headerCount := 0
880
881	ast.Walk(func(node *Node, entering bool) WalkStatus {
882		if node.Type == Header && !node.HeaderData.IsTitleblock {
883			inHeader = entering
884			if entering {
885				node.HeaderID = fmt.Sprintf("toc_%d", headerCount)
886				if node.Level == tocLevel {
887					buf.WriteString("</li>\n\n<li>")
888				} else if node.Level < tocLevel {
889					for node.Level < tocLevel {
890						tocLevel--
891						buf.WriteString("</li>\n</ul>")
892					}
893					buf.WriteString("</li>\n\n<li>")
894				} else {
895					for node.Level > tocLevel {
896						tocLevel++
897						buf.WriteString("\n<ul>\n<li>")
898					}
899				}
900
901				fmt.Fprintf(&buf, `<a href="#toc_%d">`, headerCount)
902				headerCount++
903			} else {
904				buf.WriteString("</a>")
905			}
906			return GoToNext
907		}
908
909		if inHeader {
910			return r.RenderNode(&buf, node, entering)
911		}
912
913		return GoToNext
914	})
915
916	for ; tocLevel > 0; tocLevel-- {
917		buf.WriteString("</li>\n</ul>")
918	}
919
920	if buf.Len() > 0 {
921		w.WriteString("<nav>\n")
922		w.Write(buf.Bytes())
923		w.WriteString("\n\n</nav>\n")
924	}
925	r.lastOutputLen = buf.Len()
926}
927
928func (r *HTMLRenderer) writeDocumentFooter(w *bytes.Buffer) {
929	if r.Flags&CompletePage == 0 {
930		return
931	}
932	w.WriteString("\n</body>\n</html>\n")
933}
934
935// Render walks the specified syntax (sub)tree and returns a HTML document.
936func (r *HTMLRenderer) Render(ast *Node) []byte {
937	//println("render_Blackfriday")
938	//dump(ast)
939	var buff bytes.Buffer
940	r.writeDocumentHeader(&buff)
941	if r.Extensions&TOC != 0 || r.Extensions&OmitContents != 0 {
942		r.writeTOC(&buff, ast)
943		if r.Extensions&OmitContents != 0 {
944			return buff.Bytes()
945		}
946	}
947	ast.Walk(func(node *Node, entering bool) WalkStatus {
948		return r.RenderNode(&buff, node, entering)
949	})
950	r.writeDocumentFooter(&buff)
951	return buff.Bytes()
952}