all repos — grayfriday @ a55b2615a4a5d23822661f416417d71dcb58bd47

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	"html"
  22	"io"
  23	"regexp"
  24	"strconv"
  25	"strings"
  26)
  27
  28type HtmlFlags int
  29
  30// HTML renderer configuration options.
  31const (
  32	HtmlFlagsNone       HtmlFlags = 0
  33	SkipHTML            HtmlFlags = 1 << iota // Skip preformatted HTML blocks
  34	SkipStyle                                 // Skip embedded <style> elements
  35	SkipImages                                // Skip embedded images
  36	SkipLinks                                 // Skip all links
  37	Safelink                                  // Only link to trusted protocols
  38	NofollowLinks                             // Only link with rel="nofollow"
  39	NoreferrerLinks                           // Only link with rel="noreferrer"
  40	HrefTargetBlank                           // Add a blank target
  41	Toc                                       // Generate a table of contents
  42	OmitContents                              // Skip the main contents (for a standalone table of contents)
  43	CompletePage                              // Generate a complete HTML page
  44	UseXHTML                                  // Generate XHTML output instead of HTML
  45	FootnoteReturnLinks                       // Generate a link at the end of a footnote to return to the source
  46
  47	TagName               = "[A-Za-z][A-Za-z0-9-]*"
  48	AttributeName         = "[a-zA-Z_:][a-zA-Z0-9:._-]*"
  49	UnquotedValue         = "[^\"'=<>`\\x00-\\x20]+"
  50	SingleQuotedValue     = "'[^']*'"
  51	DoubleQuotedValue     = "\"[^\"]*\""
  52	AttributeValue        = "(?:" + UnquotedValue + "|" + SingleQuotedValue + "|" + DoubleQuotedValue + ")"
  53	AttributeValueSpec    = "(?:" + "\\s*=" + "\\s*" + AttributeValue + ")"
  54	Attribute             = "(?:" + "\\s+" + AttributeName + AttributeValueSpec + "?)"
  55	OpenTag               = "<" + TagName + Attribute + "*" + "\\s*/?>"
  56	CloseTag              = "</" + TagName + "\\s*[>]"
  57	HTMLComment           = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->"
  58	ProcessingInstruction = "[<][?].*?[?][>]"
  59	Declaration           = "<![A-Z]+" + "\\s+[^>]*>"
  60	CDATA                 = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"
  61	HTMLTag               = "(?:" + OpenTag + "|" + CloseTag + "|" + HTMLComment + "|" +
  62		ProcessingInstruction + "|" + Declaration + "|" + CDATA + ")"
  63)
  64
  65var (
  66	// TODO: improve this regexp to catch all possible entities:
  67	htmlEntity = regexp.MustCompile(`&[a-z]{2,5};`)
  68	reHtmlTag  = regexp.MustCompile("(?i)^" + HTMLTag)
  69)
  70
  71type HtmlRendererParameters struct {
  72	// Prepend this text to each relative URL.
  73	AbsolutePrefix string
  74	// Add this text to each footnote anchor, to ensure uniqueness.
  75	FootnoteAnchorPrefix string
  76	// Show this text inside the <a> tag for a footnote return link, if the
  77	// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
  78	// <sup>[return]</sup> is used.
  79	FootnoteReturnLinkContents string
  80	// If set, add this text to the front of each Header ID, to ensure
  81	// uniqueness.
  82	HeaderIDPrefix string
  83	// If set, add this text to the back of each Header ID, to ensure uniqueness.
  84	HeaderIDSuffix string
  85}
  86
  87// HTML is a type that implements the Renderer interface for HTML output.
  88//
  89// Do not create this directly, instead use the HtmlRenderer function.
  90type HTML struct {
  91	flags    HtmlFlags
  92	closeTag string // how to end singleton tags: either " />" or ">"
  93	title    string // document title
  94	css      string // optional css file url (used with HTML_COMPLETE_PAGE)
  95
  96	parameters HtmlRendererParameters
  97
  98	// table of contents data
  99	tocMarker    int
 100	headerCount  int
 101	currentLevel int
 102	toc          *bytes.Buffer
 103
 104	// Track header IDs to prevent ID collision in a single generation.
 105	headerIDs map[string]int
 106
 107	w             HtmlWriter
 108	lastOutputLen int
 109	disableTags   int
 110
 111	extensions Extensions // This gives Smartypants renderer access to flags
 112}
 113
 114const (
 115	xhtmlClose = " />"
 116	htmlClose  = ">"
 117)
 118
 119// HtmlRenderer creates and configures an HTML object, which
 120// satisfies the Renderer interface.
 121//
 122// flags is a set of HtmlFlags ORed together.
 123// title is the title of the document, and css is a URL for the document's
 124// stylesheet.
 125// title and css are only used when HTML_COMPLETE_PAGE is selected.
 126func HtmlRenderer(flags HtmlFlags, extensions Extensions, title string, css string) Renderer {
 127	return HtmlRendererWithParameters(flags, extensions, title, css, HtmlRendererParameters{})
 128}
 129
 130type HtmlWriter struct {
 131	output bytes.Buffer
 132}
 133
 134func (w *HtmlWriter) Write(p []byte) (n int, err error) {
 135	return w.output.Write(p)
 136}
 137
 138func (w *HtmlWriter) WriteString(s string) (n int, err error) {
 139	return w.output.WriteString(s)
 140}
 141
 142func (w *HtmlWriter) WriteByte(b byte) error {
 143	return w.output.WriteByte(b)
 144}
 145
 146// Writes out a newline if the output is not pristine. Used at the beginning of
 147// every rendering func
 148func (w *HtmlWriter) Newline() {
 149	w.WriteByte('\n')
 150}
 151
 152func (r *HTML) Write(b []byte) (int, error) {
 153	return r.w.Write(b)
 154}
 155
 156func HtmlRendererWithParameters(flags HtmlFlags, extensions Extensions, title string,
 157	css string, renderParameters HtmlRendererParameters) Renderer {
 158	// configure the rendering engine
 159	closeTag := htmlClose
 160	if flags&UseXHTML != 0 {
 161		closeTag = xhtmlClose
 162	}
 163
 164	if renderParameters.FootnoteReturnLinkContents == "" {
 165		renderParameters.FootnoteReturnLinkContents = `<sup>[return]</sup>`
 166	}
 167
 168	var writer HtmlWriter
 169	return &HTML{
 170		flags:      flags,
 171		extensions: extensions,
 172		closeTag:   closeTag,
 173		title:      title,
 174		css:        css,
 175		parameters: renderParameters,
 176
 177		headerCount:  0,
 178		currentLevel: 0,
 179		toc:          new(bytes.Buffer),
 180
 181		headerIDs: make(map[string]int),
 182
 183		w: writer,
 184	}
 185}
 186
 187// Using if statements is a bit faster than a switch statement. As the compiler
 188// improves, this should be unnecessary this is only worthwhile because
 189// attrEscape is the single largest CPU user in normal use.
 190// Also tried using map, but that gave a ~3x slowdown.
 191func escapeSingleChar(char byte) (string, bool) {
 192	if char == '"' {
 193		return "&quot;", true
 194	}
 195	if char == '&' {
 196		return "&amp;", true
 197	}
 198	if char == '<' {
 199		return "&lt;", true
 200	}
 201	if char == '>' {
 202		return "&gt;", true
 203	}
 204	return "", false
 205}
 206
 207func (r *HTML) attrEscape(src []byte) {
 208	org := 0
 209	for i, ch := range src {
 210		if entity, ok := escapeSingleChar(ch); ok {
 211			if i > org {
 212				// copy all the normal characters since the last escape
 213				r.w.Write(src[org:i])
 214			}
 215			org = i + 1
 216			r.w.WriteString(entity)
 217		}
 218	}
 219	if org < len(src) {
 220		r.w.Write(src[org:])
 221	}
 222}
 223
 224func attrEscape2(src []byte) []byte {
 225	unesc := []byte(html.UnescapeString(string(src)))
 226	esc1 := []byte(html.EscapeString(string(unesc)))
 227	esc2 := bytes.Replace(esc1, []byte("&#34;"), []byte("&quot;"), -1)
 228	return bytes.Replace(esc2, []byte("&#39;"), []byte{'\''}, -1)
 229}
 230
 231func (r *HTML) entityEscapeWithSkip(src []byte, skipRanges [][]int) {
 232	end := 0
 233	for _, rang := range skipRanges {
 234		r.attrEscape(src[end:rang[0]])
 235		r.w.Write(src[rang[0]:rang[1]])
 236		end = rang[1]
 237	}
 238	r.attrEscape(src[end:])
 239}
 240
 241func (r *HTML) GetFlags() HtmlFlags {
 242	return r.flags
 243}
 244
 245func (r *HTML) TitleBlock(text []byte) {
 246	text = bytes.TrimPrefix(text, []byte("% "))
 247	text = bytes.Replace(text, []byte("\n% "), []byte("\n"), -1)
 248	r.w.WriteString("<h1 class=\"title\">")
 249	r.w.Write(text)
 250	r.w.WriteString("\n</h1>")
 251}
 252
 253func (r *HTML) BeginHeader(level int, id string) {
 254	r.w.Newline()
 255
 256	if id == "" && r.flags&Toc != 0 {
 257		id = fmt.Sprintf("toc_%d", r.headerCount)
 258	}
 259
 260	if id != "" {
 261		id = r.ensureUniqueHeaderID(id)
 262
 263		if r.parameters.HeaderIDPrefix != "" {
 264			id = r.parameters.HeaderIDPrefix + id
 265		}
 266
 267		if r.parameters.HeaderIDSuffix != "" {
 268			id = id + r.parameters.HeaderIDSuffix
 269		}
 270
 271		r.w.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, id))
 272	} else {
 273		r.w.WriteString(fmt.Sprintf("<h%d>", level))
 274	}
 275}
 276
 277func (r *HTML) EndHeader(level int, id string, header []byte) {
 278	// are we building a table of contents?
 279	if r.flags&Toc != 0 {
 280		r.TocHeaderWithAnchor(header, level, id)
 281	}
 282
 283	r.w.WriteString(fmt.Sprintf("</h%d>\n", level))
 284}
 285
 286func (r *HTML) BlockHtml(text []byte) {
 287	if r.flags&SkipHTML != 0 {
 288		return
 289	}
 290
 291	r.w.Newline()
 292	r.w.Write(text)
 293	r.w.WriteByte('\n')
 294}
 295
 296func (r *HTML) HRule() {
 297	r.w.Newline()
 298	r.w.WriteString("<hr")
 299	r.w.WriteString(r.closeTag)
 300	r.w.WriteByte('\n')
 301}
 302
 303func (r *HTML) BlockCode(text []byte, lang string) {
 304	r.w.Newline()
 305
 306	// parse out the language names/classes
 307	count := 0
 308	for _, elt := range strings.Fields(lang) {
 309		if elt[0] == '.' {
 310			elt = elt[1:]
 311		}
 312		if len(elt) == 0 {
 313			continue
 314		}
 315		if count == 0 {
 316			r.w.WriteString("<pre><code class=\"language-")
 317		} else {
 318			r.w.WriteByte(' ')
 319		}
 320		r.attrEscape([]byte(elt))
 321		count++
 322	}
 323
 324	if count == 0 {
 325		r.w.WriteString("<pre><code>")
 326	} else {
 327		r.w.WriteString("\">")
 328	}
 329
 330	r.attrEscape(text)
 331	r.w.WriteString("</code></pre>\n")
 332}
 333
 334func (r *HTML) BlockQuote(text []byte) {
 335	r.w.Newline()
 336	r.w.WriteString("<blockquote>\n")
 337	r.w.Write(text)
 338	r.w.WriteString("</blockquote>\n")
 339}
 340
 341func (r *HTML) Table(header []byte, body []byte, columnData []int) {
 342	r.w.Newline()
 343	r.w.WriteString("<table>\n<thead>\n")
 344	r.w.Write(header)
 345	r.w.WriteString("</thead>\n\n<tbody>\n")
 346	r.w.Write(body)
 347	r.w.WriteString("</tbody>\n</table>\n")
 348}
 349
 350func (r *HTML) TableRow(text []byte) {
 351	r.w.Newline()
 352	r.w.WriteString("<tr>\n")
 353	r.w.Write(text)
 354	r.w.WriteString("\n</tr>\n")
 355}
 356
 357func leadingNewline(out *bytes.Buffer) {
 358	if out.Len() > 0 {
 359		out.WriteByte('\n')
 360	}
 361}
 362
 363func (r *HTML) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
 364	leadingNewline(out)
 365	switch align {
 366	case TableAlignmentLeft:
 367		out.WriteString("<th align=\"left\">")
 368	case TableAlignmentRight:
 369		out.WriteString("<th align=\"right\">")
 370	case TableAlignmentCenter:
 371		out.WriteString("<th align=\"center\">")
 372	default:
 373		out.WriteString("<th>")
 374	}
 375
 376	out.Write(text)
 377	out.WriteString("</th>")
 378}
 379
 380func (r *HTML) TableCell(out *bytes.Buffer, text []byte, align int) {
 381	leadingNewline(out)
 382	switch align {
 383	case TableAlignmentLeft:
 384		out.WriteString("<td align=\"left\">")
 385	case TableAlignmentRight:
 386		out.WriteString("<td align=\"right\">")
 387	case TableAlignmentCenter:
 388		out.WriteString("<td align=\"center\">")
 389	default:
 390		out.WriteString("<td>")
 391	}
 392
 393	out.Write(text)
 394	out.WriteString("</td>")
 395}
 396
 397func (r *HTML) BeginFootnotes() {
 398	r.w.WriteString("<div class=\"footnotes\">\n")
 399	r.HRule()
 400	r.BeginList(ListTypeOrdered)
 401}
 402
 403func (r *HTML) EndFootnotes() {
 404	r.EndList(ListTypeOrdered)
 405	r.w.WriteString("</div>\n")
 406}
 407
 408func (r *HTML) FootnoteItem(name, text []byte, flags ListType) {
 409	if flags&ListItemContainsBlock != 0 || flags&ListItemBeginningOfList != 0 {
 410		r.w.Newline()
 411	}
 412	slug := slugify(name)
 413	r.w.WriteString(`<li id="`)
 414	r.w.WriteString(`fn:`)
 415	r.w.WriteString(r.parameters.FootnoteAnchorPrefix)
 416	r.w.Write(slug)
 417	r.w.WriteString(`">`)
 418	r.w.Write(text)
 419	if r.flags&FootnoteReturnLinks != 0 {
 420		r.w.WriteString(` <a class="footnote-return" href="#`)
 421		r.w.WriteString(`fnref:`)
 422		r.w.WriteString(r.parameters.FootnoteAnchorPrefix)
 423		r.w.Write(slug)
 424		r.w.WriteString(`">`)
 425		r.w.WriteString(r.parameters.FootnoteReturnLinkContents)
 426		r.w.WriteString(`</a>`)
 427	}
 428	r.w.WriteString("</li>\n")
 429}
 430
 431func (r *HTML) BeginList(flags ListType) {
 432	r.w.Newline()
 433
 434	if flags&ListTypeDefinition != 0 {
 435		r.w.WriteString("<dl>")
 436	} else if flags&ListTypeOrdered != 0 {
 437		r.w.WriteString("<ol>")
 438	} else {
 439		r.w.WriteString("<ul>")
 440	}
 441}
 442
 443func (r *HTML) EndList(flags ListType) {
 444	if flags&ListTypeDefinition != 0 {
 445		r.w.WriteString("</dl>\n")
 446	} else if flags&ListTypeOrdered != 0 {
 447		r.w.WriteString("</ol>\n")
 448	} else {
 449		r.w.WriteString("</ul>\n")
 450	}
 451}
 452
 453func (r *HTML) ListItem(text []byte, flags ListType) {
 454	if (flags&ListItemContainsBlock != 0 && flags&ListTypeDefinition == 0) ||
 455		flags&ListItemBeginningOfList != 0 {
 456		r.w.Newline()
 457	}
 458	if flags&ListTypeTerm != 0 {
 459		r.w.WriteString("<dt>")
 460	} else if flags&ListTypeDefinition != 0 {
 461		r.w.WriteString("<dd>")
 462	} else {
 463		r.w.WriteString("<li>")
 464	}
 465	r.w.Write(text)
 466	if flags&ListTypeTerm != 0 {
 467		r.w.WriteString("</dt>\n")
 468	} else if flags&ListTypeDefinition != 0 {
 469		r.w.WriteString("</dd>\n")
 470	} else {
 471		r.w.WriteString("</li>\n")
 472	}
 473}
 474
 475func (r *HTML) BeginParagraph() {
 476	r.w.Newline()
 477	r.w.WriteString("<p>")
 478}
 479
 480func (r *HTML) EndParagraph() {
 481	r.w.WriteString("</p>\n")
 482}
 483
 484func (r *HTML) AutoLink(link []byte, kind LinkType) {
 485	skipRanges := htmlEntity.FindAllIndex(link, -1)
 486	if r.flags&Safelink != 0 && !isSafeLink(link) && kind != LinkTypeEmail {
 487		// mark it but don't link it if it is not a safe link: no smartypants
 488		r.w.WriteString("<tt>")
 489		r.entityEscapeWithSkip(link, skipRanges)
 490		r.w.WriteString("</tt>")
 491		return
 492	}
 493
 494	r.w.WriteString("<a href=\"")
 495	if kind == LinkTypeEmail {
 496		r.w.WriteString("mailto:")
 497	} else {
 498		r.maybeWriteAbsolutePrefix(link)
 499	}
 500
 501	r.entityEscapeWithSkip(link, skipRanges)
 502
 503	var relAttrs []string
 504	if r.flags&NofollowLinks != 0 && !isRelativeLink(link) {
 505		relAttrs = append(relAttrs, "nofollow")
 506	}
 507	if r.flags&NoreferrerLinks != 0 && !isRelativeLink(link) {
 508		relAttrs = append(relAttrs, "noreferrer")
 509	}
 510	if len(relAttrs) > 0 {
 511		r.w.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
 512	}
 513
 514	// blank target only add to external link
 515	if r.flags&HrefTargetBlank != 0 && !isRelativeLink(link) {
 516		r.w.WriteString("\" target=\"_blank")
 517	}
 518
 519	r.w.WriteString("\">")
 520
 521	// Pretty print: if we get an email address as
 522	// an actual URI, e.g. `mailto:foo@bar.com`, we don't
 523	// want to print the `mailto:` prefix
 524	switch {
 525	case bytes.HasPrefix(link, []byte("mailto://")):
 526		r.attrEscape(link[len("mailto://"):])
 527	case bytes.HasPrefix(link, []byte("mailto:")):
 528		r.attrEscape(link[len("mailto:"):])
 529	default:
 530		r.entityEscapeWithSkip(link, skipRanges)
 531	}
 532
 533	r.w.WriteString("</a>")
 534}
 535
 536func (r *HTML) CodeSpan(text []byte) {
 537	r.w.WriteString("<code>")
 538	r.attrEscape(text)
 539	r.w.WriteString("</code>")
 540}
 541
 542func (r *HTML) DoubleEmphasis(text []byte) {
 543	r.w.WriteString("<strong>")
 544	r.w.Write(text)
 545	r.w.WriteString("</strong>")
 546}
 547
 548func (r *HTML) Emphasis(text []byte) {
 549	if len(text) == 0 {
 550		return
 551	}
 552	r.w.WriteString("<em>")
 553	r.w.Write(text)
 554	r.w.WriteString("</em>")
 555}
 556
 557func (r *HTML) maybeWriteAbsolutePrefix(link []byte) {
 558	if r.parameters.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
 559		r.w.WriteString(r.parameters.AbsolutePrefix)
 560		if link[0] != '/' {
 561			r.w.WriteByte('/')
 562		}
 563	}
 564}
 565
 566func (r *HTML) Image(link []byte, title []byte, alt []byte) {
 567	if r.flags&SkipImages != 0 {
 568		return
 569	}
 570
 571	r.w.WriteString("<img src=\"")
 572	r.maybeWriteAbsolutePrefix(link)
 573	r.attrEscape(link)
 574	r.w.WriteString("\" alt=\"")
 575	if len(alt) > 0 {
 576		r.attrEscape(alt)
 577	}
 578	if len(title) > 0 {
 579		r.w.WriteString("\" title=\"")
 580		r.attrEscape(title)
 581	}
 582
 583	r.w.WriteByte('"')
 584	r.w.WriteString(r.closeTag)
 585}
 586
 587func (r *HTML) LineBreak() {
 588	r.w.WriteString("<br")
 589	r.w.WriteString(r.closeTag)
 590	r.w.WriteByte('\n')
 591}
 592
 593func (r *HTML) Link(link []byte, title []byte, content []byte) {
 594	if r.flags&SkipLinks != 0 {
 595		// write the link text out but don't link it, just mark it with typewriter font
 596		r.w.WriteString("<tt>")
 597		r.attrEscape(content)
 598		r.w.WriteString("</tt>")
 599		return
 600	}
 601
 602	if r.flags&Safelink != 0 && !isSafeLink(link) {
 603		// write the link text out but don't link it, just mark it with typewriter font
 604		r.w.WriteString("<tt>")
 605		r.attrEscape(content)
 606		r.w.WriteString("</tt>")
 607		return
 608	}
 609
 610	r.w.WriteString("<a href=\"")
 611	r.maybeWriteAbsolutePrefix(link)
 612	r.attrEscape(link)
 613	if len(title) > 0 {
 614		r.w.WriteString("\" title=\"")
 615		r.attrEscape(title)
 616	}
 617	var relAttrs []string
 618	if r.flags&NofollowLinks != 0 && !isRelativeLink(link) {
 619		relAttrs = append(relAttrs, "nofollow")
 620	}
 621	if r.flags&NoreferrerLinks != 0 && !isRelativeLink(link) {
 622		relAttrs = append(relAttrs, "noreferrer")
 623	}
 624	if len(relAttrs) > 0 {
 625		r.w.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
 626	}
 627
 628	// blank target only add to external link
 629	if r.flags&HrefTargetBlank != 0 && !isRelativeLink(link) {
 630		r.w.WriteString("\" target=\"_blank")
 631	}
 632
 633	r.w.WriteString("\">")
 634	r.w.Write(content)
 635	r.w.WriteString("</a>")
 636	return
 637}
 638
 639func (r *HTML) RawHtmlTag(text []byte) {
 640	if r.flags&SkipHTML != 0 {
 641		return
 642	}
 643	if r.flags&SkipStyle != 0 && isHtmlTag(text, "style") {
 644		return
 645	}
 646	if r.flags&SkipLinks != 0 && isHtmlTag(text, "a") {
 647		return
 648	}
 649	if r.flags&SkipImages != 0 && isHtmlTag(text, "img") {
 650		return
 651	}
 652	r.w.Write(text)
 653}
 654
 655func (r *HTML) TripleEmphasis(text []byte) {
 656	r.w.WriteString("<strong><em>")
 657	r.w.Write(text)
 658	r.w.WriteString("</em></strong>")
 659}
 660
 661func (r *HTML) StrikeThrough(text []byte) {
 662	r.w.WriteString("<del>")
 663	r.w.Write(text)
 664	r.w.WriteString("</del>")
 665}
 666
 667func (r *HTML) FootnoteRef(ref []byte, id int) {
 668	slug := slugify(ref)
 669	r.w.WriteString(`<sup class="footnote-ref" id="`)
 670	r.w.WriteString(`fnref:`)
 671	r.w.WriteString(r.parameters.FootnoteAnchorPrefix)
 672	r.w.Write(slug)
 673	r.w.WriteString(`"><a rel="footnote" href="#`)
 674	r.w.WriteString(`fn:`)
 675	r.w.WriteString(r.parameters.FootnoteAnchorPrefix)
 676	r.w.Write(slug)
 677	r.w.WriteString(`">`)
 678	r.w.WriteString(strconv.Itoa(id))
 679	r.w.WriteString(`</a></sup>`)
 680}
 681
 682func (r *HTML) Entity(entity []byte) {
 683	r.w.Write(entity)
 684}
 685
 686func (r *HTML) NormalText(text []byte) {
 687	if r.extensions&Smartypants != 0 {
 688		r.Smartypants(text)
 689	} else {
 690		r.attrEscape(text)
 691	}
 692}
 693
 694func (r *HTML) Smartypants(text []byte) {
 695	r.w.Write(NewSmartypantsRenderer(r.extensions).Process(text))
 696}
 697
 698func (r *HTML) DocumentHeader() {
 699	if r.flags&CompletePage == 0 {
 700		return
 701	}
 702
 703	ending := ""
 704	if r.flags&UseXHTML != 0 {
 705		r.w.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
 706		r.w.WriteString("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
 707		r.w.WriteString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
 708		ending = " /"
 709	} else {
 710		r.w.WriteString("<!DOCTYPE html>\n")
 711		r.w.WriteString("<html>\n")
 712	}
 713	r.w.WriteString("<head>\n")
 714	r.w.WriteString("  <title>")
 715	r.NormalText([]byte(r.title))
 716	r.w.WriteString("</title>\n")
 717	r.w.WriteString("  <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
 718	r.w.WriteString(VERSION)
 719	r.w.WriteString("\"")
 720	r.w.WriteString(ending)
 721	r.w.WriteString(">\n")
 722	r.w.WriteString("  <meta charset=\"utf-8\"")
 723	r.w.WriteString(ending)
 724	r.w.WriteString(">\n")
 725	if r.css != "" {
 726		r.w.WriteString("  <link rel=\"stylesheet\" type=\"text/css\" href=\"")
 727		r.attrEscape([]byte(r.css))
 728		r.w.WriteString("\"")
 729		r.w.WriteString(ending)
 730		r.w.WriteString(">\n")
 731	}
 732	r.w.WriteString("</head>\n")
 733	r.w.WriteString("<body>\n")
 734
 735	r.tocMarker = r.w.output.Len() // XXX
 736}
 737
 738func (r *HTML) DocumentFooter() {
 739	// finalize and insert the table of contents
 740	if r.flags&Toc != 0 {
 741		r.TocFinalize()
 742
 743		// now we have to insert the table of contents into the document
 744		var temp bytes.Buffer
 745
 746		// start by making a copy of everything after the document header
 747		temp.Write(r.w.output.Bytes()[r.tocMarker:])
 748
 749		// now clear the copied material from the main output buffer
 750		r.w.output.Truncate(r.tocMarker)
 751
 752		// corner case spacing issue
 753		if r.flags&CompletePage != 0 {
 754			r.w.WriteByte('\n')
 755		}
 756
 757		// insert the table of contents
 758		r.w.WriteString("<nav>\n")
 759		r.w.Write(r.toc.Bytes())
 760		r.w.WriteString("</nav>\n")
 761
 762		// corner case spacing issue
 763		if r.flags&CompletePage == 0 && r.flags&OmitContents == 0 {
 764			r.w.WriteByte('\n')
 765		}
 766
 767		// write out everything that came after it
 768		if r.flags&OmitContents == 0 {
 769			r.w.Write(temp.Bytes())
 770		}
 771	}
 772
 773	if r.flags&CompletePage != 0 {
 774		r.w.WriteString("\n</body>\n")
 775		r.w.WriteString("</html>\n")
 776	}
 777
 778}
 779
 780func (r *HTML) TocHeaderWithAnchor(text []byte, level int, anchor string) {
 781	for level > r.currentLevel {
 782		switch {
 783		case bytes.HasSuffix(r.toc.Bytes(), []byte("</li>\n")):
 784			// this sublist can nest underneath a header
 785			size := r.toc.Len()
 786			r.toc.Truncate(size - len("</li>\n"))
 787
 788		case r.currentLevel > 0:
 789			r.toc.WriteString("<li>")
 790		}
 791		if r.toc.Len() > 0 {
 792			r.toc.WriteByte('\n')
 793		}
 794		r.toc.WriteString("<ul>\n")
 795		r.currentLevel++
 796	}
 797
 798	for level < r.currentLevel {
 799		r.toc.WriteString("</ul>")
 800		if r.currentLevel > 1 {
 801			r.toc.WriteString("</li>\n")
 802		}
 803		r.currentLevel--
 804	}
 805
 806	r.toc.WriteString("<li><a href=\"#")
 807	if anchor != "" {
 808		r.toc.WriteString(anchor)
 809	} else {
 810		r.toc.WriteString("toc_")
 811		r.toc.WriteString(strconv.Itoa(r.headerCount))
 812	}
 813	r.toc.WriteString("\">")
 814	r.headerCount++
 815
 816	r.toc.Write(text)
 817
 818	r.toc.WriteString("</a></li>\n")
 819}
 820
 821func (r *HTML) TocHeader(text []byte, level int) {
 822	r.TocHeaderWithAnchor(text, level, "")
 823}
 824
 825func (r *HTML) TocFinalize() {
 826	for r.currentLevel > 1 {
 827		r.toc.WriteString("</ul></li>\n")
 828		r.currentLevel--
 829	}
 830
 831	if r.currentLevel > 0 {
 832		r.toc.WriteString("</ul>\n")
 833	}
 834}
 835
 836func isHtmlTag(tag []byte, tagname string) bool {
 837	found, _ := findHtmlTagPos(tag, tagname)
 838	return found
 839}
 840
 841// Look for a character, but ignore it when it's in any kind of quotes, it
 842// might be JavaScript
 843func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
 844	inSingleQuote := false
 845	inDoubleQuote := false
 846	inGraveQuote := false
 847	i := start
 848	for i < len(html) {
 849		switch {
 850		case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
 851			return i
 852		case html[i] == '\'':
 853			inSingleQuote = !inSingleQuote
 854		case html[i] == '"':
 855			inDoubleQuote = !inDoubleQuote
 856		case html[i] == '`':
 857			inGraveQuote = !inGraveQuote
 858		}
 859		i++
 860	}
 861	return start
 862}
 863
 864func findHtmlTagPos(tag []byte, tagname string) (bool, int) {
 865	i := 0
 866	if i < len(tag) && tag[0] != '<' {
 867		return false, -1
 868	}
 869	i++
 870	i = skipSpace(tag, i)
 871
 872	if i < len(tag) && tag[i] == '/' {
 873		i++
 874	}
 875
 876	i = skipSpace(tag, i)
 877	j := 0
 878	for ; i < len(tag); i, j = i+1, j+1 {
 879		if j >= len(tagname) {
 880			break
 881		}
 882
 883		if strings.ToLower(string(tag[i]))[0] != tagname[j] {
 884			return false, -1
 885		}
 886	}
 887
 888	if i == len(tag) {
 889		return false, -1
 890	}
 891
 892	rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
 893	if rightAngle > i {
 894		return true, rightAngle
 895	}
 896
 897	return false, -1
 898}
 899
 900func skipUntilChar(text []byte, start int, char byte) int {
 901	i := start
 902	for i < len(text) && text[i] != char {
 903		i++
 904	}
 905	return i
 906}
 907
 908func skipSpace(tag []byte, i int) int {
 909	for i < len(tag) && isspace(tag[i]) {
 910		i++
 911	}
 912	return i
 913}
 914
 915func skipChar(data []byte, start int, char byte) int {
 916	i := start
 917	for i < len(data) && data[i] == char {
 918		i++
 919	}
 920	return i
 921}
 922
 923func isRelativeLink(link []byte) (yes bool) {
 924	// a tag begin with '#'
 925	if link[0] == '#' {
 926		return true
 927	}
 928
 929	// link begin with '/' but not '//', the second maybe a protocol relative link
 930	if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
 931		return true
 932	}
 933
 934	// only the root '/'
 935	if len(link) == 1 && link[0] == '/' {
 936		return true
 937	}
 938
 939	// current directory : begin with "./"
 940	if bytes.HasPrefix(link, []byte("./")) {
 941		return true
 942	}
 943
 944	// parent directory : begin with "../"
 945	if bytes.HasPrefix(link, []byte("../")) {
 946		return true
 947	}
 948
 949	return false
 950}
 951
 952func (r *HTML) ensureUniqueHeaderID(id string) string {
 953	for count, found := r.headerIDs[id]; found; count, found = r.headerIDs[id] {
 954		tmp := fmt.Sprintf("%s-%d", id, count+1)
 955
 956		if _, tmpFound := r.headerIDs[tmp]; !tmpFound {
 957			r.headerIDs[id] = count + 1
 958			id = tmp
 959		} else {
 960			id = id + "-1"
 961		}
 962	}
 963
 964	if _, found := r.headerIDs[id]; !found {
 965		r.headerIDs[id] = 0
 966	}
 967
 968	return id
 969}
 970
 971func (r *HTML) addAbsPrefix(link []byte) []byte {
 972	if r.parameters.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
 973		newDest := r.parameters.AbsolutePrefix
 974		if link[0] != '/' {
 975			newDest += "/"
 976		}
 977		newDest += string(link)
 978		return []byte(newDest)
 979	}
 980	return link
 981}
 982
 983func appendLinkAttrs(attrs []string, flags HtmlFlags, link []byte) []string {
 984	if isRelativeLink(link) {
 985		return attrs
 986	}
 987	val := []string{}
 988	if flags&NofollowLinks != 0 {
 989		val = append(val, "nofollow")
 990	}
 991	if flags&NoreferrerLinks != 0 {
 992		val = append(val, "noreferrer")
 993	}
 994	if flags&HrefTargetBlank != 0 {
 995		attrs = append(attrs, "target=\"_blank\"")
 996	}
 997	if len(val) == 0 {
 998		return attrs
 999	}
1000	attr := fmt.Sprintf("rel=%q", strings.Join(val, " "))
1001	return append(attrs, attr)
1002}
1003
1004func isMailto(link []byte) bool {
1005	return bytes.HasPrefix(link, []byte("mailto:"))
1006}
1007
1008func isSmartypantable(node *Node) bool {
1009	pt := node.Parent.Type
1010	return pt != Link && pt != CodeBlock && pt != Code
1011}
1012
1013func appendLanguageAttr(attrs []string, info []byte) []string {
1014	infoWords := bytes.Split(info, []byte("\t "))
1015	if len(infoWords) > 0 && len(infoWords[0]) > 0 {
1016		attrs = append(attrs, fmt.Sprintf("class=\"language-%s\"", infoWords[0]))
1017	}
1018	return attrs
1019}
1020
1021func tag(name string, attrs []string, selfClosing bool) []byte {
1022	result := "<" + name
1023	if attrs != nil && len(attrs) > 0 {
1024		result += " " + strings.Join(attrs, " ")
1025	}
1026	if selfClosing {
1027		result += " /"
1028	}
1029	return []byte(result + ">")
1030}
1031
1032func footnoteRef(prefix string, node *Node) []byte {
1033	urlFrag := prefix + string(slugify(node.Destination))
1034	anchor := fmt.Sprintf(`<a rel="footnote" href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
1035	return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor))
1036}
1037
1038func footnoteItem(prefix string, slug []byte) []byte {
1039	return []byte(fmt.Sprintf(`<li id="fn:%s%s">`, prefix, slug))
1040}
1041
1042func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte {
1043	const format = ` <a class="footnote-return" href="#fnref:%s%s">%s</a>`
1044	return []byte(fmt.Sprintf(format, prefix, slug, returnLink))
1045}
1046
1047func itemOpenCR(node *Node) bool {
1048	if node.Prev == nil {
1049		return false
1050	}
1051	ld := node.Parent.ListData
1052	return !ld.Tight && ld.Flags&ListTypeDefinition == 0
1053}
1054
1055func skipParagraphTags(node *Node) bool {
1056	grandparent := node.Parent.Parent
1057	if grandparent == nil || grandparent.ListData == nil {
1058		return false
1059	}
1060	tightOrTerm := grandparent.ListData.Tight || node.Parent.ListData.Flags&ListTypeTerm != 0
1061	return grandparent.Type == List && tightOrTerm
1062}
1063
1064func cellAlignment(align int) string {
1065	switch align {
1066	case TableAlignmentLeft:
1067		return "left"
1068	case TableAlignmentRight:
1069		return "right"
1070	case TableAlignmentCenter:
1071		return "center"
1072	default:
1073		return ""
1074	}
1075}
1076
1077func esc(text []byte, preserveEntities bool) []byte {
1078	return attrEscape2(text)
1079}
1080
1081func escCode(text []byte, preserveEntities bool) []byte {
1082	e1 := []byte(html.EscapeString(string(text)))
1083	e2 := bytes.Replace(e1, []byte("&#34;"), []byte("&quot;"), -1)
1084	return bytes.Replace(e2, []byte("&#39;"), []byte{'\''}, -1)
1085}
1086
1087func (r *HTML) out(w io.Writer, text []byte) {
1088	if r.disableTags > 0 {
1089		w.Write(reHtmlTag.ReplaceAll(text, []byte{}))
1090	} else {
1091		w.Write(text)
1092	}
1093	r.lastOutputLen = len(text)
1094}
1095
1096func (r *HTML) cr(w io.Writer) {
1097	if r.lastOutputLen > 0 {
1098		r.out(w, []byte{'\n'})
1099	}
1100}
1101
1102func (r *HTML) RenderNode(w io.Writer, node *Node, entering bool) {
1103	attrs := []string{}
1104	switch node.Type {
1105	case Text:
1106		r.out(w, node.Literal)
1107		break
1108	case Softbreak:
1109		r.out(w, []byte("\n"))
1110		// TODO: make it configurable via out(renderer.softbreak)
1111	case Hardbreak:
1112		r.out(w, tag("br", nil, true))
1113		r.cr(w)
1114	case Emph:
1115		if entering {
1116			r.out(w, tag("em", nil, false))
1117		} else {
1118			r.out(w, tag("/em", nil, false))
1119		}
1120		break
1121	case Strong:
1122		if entering {
1123			r.out(w, tag("strong", nil, false))
1124		} else {
1125			r.out(w, tag("/strong", nil, false))
1126		}
1127		break
1128	case Del:
1129		if entering {
1130			r.out(w, tag("del", nil, false))
1131		} else {
1132			r.out(w, tag("/del", nil, false))
1133		}
1134	case HtmlSpan:
1135		//if options.safe {
1136		//	out(w, "<!-- raw HTML omitted -->")
1137		//} else {
1138		r.out(w, node.Literal)
1139		//}
1140	case Link:
1141		// mark it but don't link it if it is not a safe link: no smartypants
1142		dest := node.LinkData.Destination
1143		if r.flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest) {
1144			if entering {
1145				r.out(w, tag("tt", nil, false))
1146			} else {
1147				r.out(w, tag("/tt", nil, false))
1148			}
1149		} else {
1150			if entering {
1151				dest = r.addAbsPrefix(dest)
1152				//if (!(options.safe && potentiallyUnsafe(node.destination))) {
1153				attrs = append(attrs, fmt.Sprintf("href=%q", esc(dest, true)))
1154				//}
1155				if node.NoteID != 0 {
1156					r.out(w, footnoteRef(r.parameters.FootnoteAnchorPrefix, node))
1157					break
1158				}
1159				attrs = appendLinkAttrs(attrs, r.flags, dest)
1160				if len(node.LinkData.Title) > 0 {
1161					attrs = append(attrs, fmt.Sprintf("title=%q", esc(node.LinkData.Title, true)))
1162				}
1163				r.out(w, tag("a", attrs, false))
1164			} else {
1165				if node.NoteID != 0 {
1166					break
1167				}
1168				r.out(w, tag("/a", nil, false))
1169			}
1170		}
1171	case Image:
1172		if entering {
1173			dest := node.LinkData.Destination
1174			dest = r.addAbsPrefix(dest)
1175			if r.disableTags == 0 {
1176				//if options.safe && potentiallyUnsafe(dest) {
1177				//out(w, `<img src="" alt="`)
1178				//} else {
1179				r.out(w, []byte(fmt.Sprintf(`<img src="%s" alt="`, esc(dest, true))))
1180				//}
1181			}
1182			r.disableTags++
1183		} else {
1184			r.disableTags--
1185			if r.disableTags == 0 {
1186				if node.LinkData.Title != nil {
1187					r.out(w, []byte(`" title="`))
1188					r.out(w, esc(node.LinkData.Title, true))
1189				}
1190				r.out(w, []byte(`" />`))
1191			}
1192		}
1193	case Code:
1194		r.out(w, tag("code", nil, false))
1195		r.out(w, escCode(node.Literal, false))
1196		r.out(w, tag("/code", nil, false))
1197	case Document:
1198		break
1199	case Paragraph:
1200		if skipParagraphTags(node) {
1201			break
1202		}
1203		if entering {
1204			// TODO: untangle this clusterfuck about when the newlines need
1205			// to be added and when not.
1206			if node.Prev != nil {
1207				t := node.Prev.Type
1208				if t == HtmlBlock || t == List || t == Paragraph || t == Header || t == CodeBlock || t == BlockQuote || t == HorizontalRule {
1209					r.cr(w)
1210				}
1211			}
1212			if node.Parent.Type == BlockQuote && node.Prev == nil {
1213				r.cr(w)
1214			}
1215			r.out(w, tag("p", attrs, false))
1216		} else {
1217			r.out(w, tag("/p", attrs, false))
1218			if !(node.Parent.Type == Item && node.Next == nil) {
1219				r.cr(w)
1220			}
1221		}
1222		break
1223	case BlockQuote:
1224		if entering {
1225			r.cr(w)
1226			r.out(w, tag("blockquote", attrs, false))
1227		} else {
1228			r.out(w, tag("/blockquote", nil, false))
1229			r.cr(w)
1230		}
1231		break
1232	case HtmlBlock:
1233		r.cr(w)
1234		r.out(w, node.Literal)
1235		r.cr(w)
1236	case Header:
1237		tagname := fmt.Sprintf("h%d", node.Level)
1238		if entering {
1239			if node.IsTitleblock {
1240				attrs = append(attrs, `class="title"`)
1241			}
1242			if node.HeaderID != "" {
1243				id := r.ensureUniqueHeaderID(node.HeaderID)
1244				if r.parameters.HeaderIDPrefix != "" {
1245					id = r.parameters.HeaderIDPrefix + id
1246				}
1247				if r.parameters.HeaderIDSuffix != "" {
1248					id = id + r.parameters.HeaderIDSuffix
1249				}
1250				attrs = append(attrs, fmt.Sprintf(`id="%s"`, id))
1251			}
1252			r.cr(w)
1253			r.out(w, tag(tagname, attrs, false))
1254		} else {
1255			r.out(w, tag("/"+tagname, nil, false))
1256			if !(node.Parent.Type == Item && node.Next == nil) {
1257				r.cr(w)
1258			}
1259		}
1260		break
1261	case HorizontalRule:
1262		r.cr(w)
1263		r.out(w, tag("hr", attrs, r.flags&UseXHTML != 0))
1264		r.cr(w)
1265		break
1266	case List:
1267		tagName := "ul"
1268		if node.ListData.Flags&ListTypeOrdered != 0 {
1269			tagName = "ol"
1270		}
1271		if node.ListData.Flags&ListTypeDefinition != 0 {
1272			tagName = "dl"
1273		}
1274		if entering {
1275			// var start = node.listStart;
1276			// if (start !== null && start !== 1) {
1277			//     attrs.push(['start', start.toString()]);
1278			// }
1279			r.cr(w)
1280			if node.Parent.Type == Item && node.Parent.Parent.ListData.Tight {
1281				r.cr(w)
1282			}
1283			r.out(w, tag(tagName, attrs, false))
1284			r.cr(w)
1285		} else {
1286			r.out(w, tag("/"+tagName, nil, false))
1287			//cr(w)
1288			//if node.parent.Type != Item {
1289			//	cr(w)
1290			//}
1291			if node.Parent.Type == Item && node.Next != nil {
1292				r.cr(w)
1293			}
1294			if node.Parent.Type == Document || node.Parent.Type == BlockQuote {
1295				r.cr(w)
1296			}
1297		}
1298	case Item:
1299		tagName := "li"
1300		if node.ListData.Flags&ListTypeDefinition != 0 {
1301			tagName = "dd"
1302		}
1303		if node.ListData.Flags&ListTypeTerm != 0 {
1304			tagName = "dt"
1305		}
1306		if entering {
1307			if itemOpenCR(node) {
1308				r.cr(w)
1309			}
1310			if node.ListData.RefLink != nil {
1311				slug := slugify(node.ListData.RefLink)
1312				r.out(w, footnoteItem(r.parameters.FootnoteAnchorPrefix, slug))
1313				break
1314			}
1315			r.out(w, tag(tagName, nil, false))
1316		} else {
1317			if node.ListData.RefLink != nil {
1318				slug := slugify(node.ListData.RefLink)
1319				if r.flags&FootnoteReturnLinks != 0 {
1320					r.out(w, footnoteReturnLink(r.parameters.FootnoteAnchorPrefix, r.parameters.FootnoteReturnLinkContents, slug))
1321				}
1322			}
1323			r.out(w, tag("/"+tagName, nil, false))
1324			r.cr(w)
1325		}
1326	case CodeBlock:
1327		attrs = appendLanguageAttr(attrs, node.Info)
1328		r.cr(w)
1329		r.out(w, tag("pre", nil, false))
1330		r.out(w, tag("code", attrs, false))
1331		r.out(w, escCode(node.Literal, false))
1332		r.out(w, tag("/code", nil, false))
1333		r.out(w, tag("/pre", nil, false))
1334		if node.Parent.Type != Item {
1335			r.cr(w)
1336		}
1337	case Table:
1338		if entering {
1339			r.cr(w)
1340			r.out(w, tag("table", nil, false))
1341		} else {
1342			r.out(w, tag("/table", nil, false))
1343			r.cr(w)
1344		}
1345	case TableCell:
1346		tagName := "td"
1347		if node.IsHeader {
1348			tagName = "th"
1349		}
1350		if entering {
1351			align := cellAlignment(node.Align)
1352			if align != "" {
1353				attrs = append(attrs, fmt.Sprintf(`align="%s"`, align))
1354			}
1355			if node.Prev == nil {
1356				r.cr(w)
1357			}
1358			r.out(w, tag(tagName, attrs, false))
1359		} else {
1360			r.out(w, tag("/"+tagName, nil, false))
1361			r.cr(w)
1362		}
1363	case TableHead:
1364		if entering {
1365			r.cr(w)
1366			r.out(w, tag("thead", nil, false))
1367		} else {
1368			r.out(w, tag("/thead", nil, false))
1369			r.cr(w)
1370		}
1371	case TableBody:
1372		if entering {
1373			r.cr(w)
1374			r.out(w, tag("tbody", nil, false))
1375			// XXX: this is to adhere to a rather silly test. Should fix test.
1376			if node.FirstChild == nil {
1377				r.cr(w)
1378			}
1379		} else {
1380			r.out(w, tag("/tbody", nil, false))
1381			r.cr(w)
1382		}
1383	case TableRow:
1384		if entering {
1385			r.cr(w)
1386			r.out(w, tag("tr", nil, false))
1387		} else {
1388			r.out(w, tag("/tr", nil, false))
1389			r.cr(w)
1390		}
1391	default:
1392		panic("Unknown node type " + node.Type.String())
1393	}
1394}
1395
1396func (r *HTML) Render(ast *Node) []byte {
1397	//println("render_Blackfriday")
1398	//dump(ast)
1399	// Run Smartypants if it's enabled or simply escape text if not
1400	sr := NewSmartypantsRenderer(r.extensions)
1401	ForEachNode(ast, func(node *Node, entering bool) {
1402		if node.Type == Text {
1403			if r.extensions&Smartypants != 0 {
1404				node.Literal = sr.Process(node.Literal)
1405			} else {
1406				node.Literal = esc(node.Literal, false)
1407			}
1408		}
1409	})
1410	var buff bytes.Buffer
1411	ForEachNode(ast, func(node *Node, entering bool) {
1412		r.RenderNode(&buff, node, entering)
1413	})
1414	return buff.Bytes()
1415}