all repos — grayfriday @ 97235182ac5e768200740f8050b75e07bb97a43f

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	"regexp"
  22	"strconv"
  23	"strings"
  24)
  25
  26type HtmlFlags int
  27
  28// Html renderer configuration options.
  29const (
  30	HtmlFlagsNone           HtmlFlags = 0
  31	SkipHTML                HtmlFlags = 1 << iota // Skip preformatted HTML blocks
  32	SkipStyle                                     // Skip embedded <style> elements
  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	Toc                                           // Generate a table of contents
  40	OmitContents                                  // Skip the main contents (for a standalone table of contents)
  41	CompletePage                                  // Generate a complete HTML page
  42	UseXHTML                                      // Generate XHTML output instead of HTML
  43	UseSmartypants                                // Enable smart punctuation substitutions
  44	SmartypantsFractions                          // Enable smart fractions (with UseSmartypants)
  45	SmartypantsDashes                             // Enable smart dashes (with UseSmartypants)
  46	SmartypantsLatexDashes                        // Enable LaTeX-style dashes (with UseSmartypants)
  47	SmartypantsAngledQuotes                       // Enable angled double quotes (with UseSmartypants) for double quotes rendering
  48	FootnoteReturnLinks                           // Generate a link at the end of a footnote to return to the source
  49)
  50
  51var (
  52	alignments = []string{
  53		"left",
  54		"right",
  55		"center",
  56	}
  57
  58	// TODO: improve this regexp to catch all possible entities:
  59	htmlEntity = regexp.MustCompile(`&[a-z]{2,5};`)
  60)
  61
  62type HtmlRendererParameters struct {
  63	// Prepend this text to each relative URL.
  64	AbsolutePrefix string
  65	// Add this text to each footnote anchor, to ensure uniqueness.
  66	FootnoteAnchorPrefix string
  67	// Show this text inside the <a> tag for a footnote return link, if the
  68	// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
  69	// <sup>[return]</sup> is used.
  70	FootnoteReturnLinkContents string
  71	// If set, add this text to the front of each Header ID, to ensure
  72	// uniqueness.
  73	HeaderIDPrefix string
  74	// If set, add this text to the back of each Header ID, to ensure uniqueness.
  75	HeaderIDSuffix string
  76}
  77
  78// Html is a type that implements the Renderer interface for HTML output.
  79//
  80// Do not create this directly, instead use the HtmlRenderer function.
  81type Html struct {
  82	flags    HtmlFlags
  83	closeTag string // how to end singleton tags: either " />" or ">"
  84	title    string // document title
  85	css      string // optional css file url (used with HTML_COMPLETE_PAGE)
  86
  87	parameters HtmlRendererParameters
  88
  89	// table of contents data
  90	tocMarker    int
  91	headerCount  int
  92	currentLevel int
  93	toc          *bytes.Buffer
  94
  95	// Track header IDs to prevent ID collision in a single generation.
  96	headerIDs map[string]int
  97
  98	smartypants *smartypantsRenderer
  99	w           HtmlWriter
 100}
 101
 102const (
 103	xhtmlClose = " />"
 104	htmlClose  = ">"
 105)
 106
 107// HtmlRenderer creates and configures an Html object, which
 108// satisfies the Renderer interface.
 109//
 110// flags is a set of HtmlFlags ORed together.
 111// title is the title of the document, and css is a URL for the document's
 112// stylesheet.
 113// title and css are only used when HTML_COMPLETE_PAGE is selected.
 114func HtmlRenderer(flags HtmlFlags, title string, css string) Renderer {
 115	return HtmlRendererWithParameters(flags, title, css, HtmlRendererParameters{})
 116}
 117
 118type HtmlWriter struct {
 119	output      bytes.Buffer
 120	captureBuff *bytes.Buffer
 121	copyBuff    *bytes.Buffer
 122	dirty       bool
 123}
 124
 125func (w *HtmlWriter) Write(p []byte) (n int, err error) {
 126	w.dirty = true
 127	if w.copyBuff != nil {
 128		w.copyBuff.Write(p)
 129	}
 130	if w.captureBuff != nil {
 131		w.captureBuff.Write(p)
 132		return
 133	}
 134	return w.output.Write(p)
 135}
 136
 137func (w *HtmlWriter) WriteString(s string) (n int, err error) {
 138	w.dirty = true
 139	if w.copyBuff != nil {
 140		w.copyBuff.WriteString(s)
 141	}
 142	if w.captureBuff != nil {
 143		w.captureBuff.WriteString(s)
 144		return
 145	}
 146	return w.output.WriteString(s)
 147}
 148
 149func (w *HtmlWriter) WriteByte(b byte) error {
 150	w.dirty = true
 151	if w.copyBuff != nil {
 152		w.copyBuff.WriteByte(b)
 153	}
 154	if w.captureBuff != nil {
 155		return w.captureBuff.WriteByte(b)
 156	}
 157	return w.output.WriteByte(b)
 158}
 159
 160// Writes out a newline if the output is not pristine. Used at the beginning of
 161// every rendering func
 162func (w *HtmlWriter) Newline() {
 163	if w.dirty {
 164		w.WriteByte('\n')
 165	}
 166}
 167
 168func (r *Html) CaptureWrites(processor func()) []byte {
 169	var output bytes.Buffer
 170	// preserve old captureBuff state for possible nested captures:
 171	tmp := r.w.captureBuff
 172	tmpd := r.w.dirty
 173	r.w.captureBuff = &output
 174	r.w.dirty = false
 175	processor()
 176	// restore:
 177	r.w.captureBuff = tmp
 178	r.w.dirty = tmpd
 179	return output.Bytes()
 180}
 181
 182func (r *Html) CopyWrites(processor func()) []byte {
 183	var output bytes.Buffer
 184	r.w.copyBuff = &output
 185	processor()
 186	r.w.copyBuff = nil
 187	return output.Bytes()
 188}
 189
 190func (r *Html) Write(b []byte) (int, error) {
 191	return r.w.Write(b)
 192}
 193
 194func (r *Html) GetResult() []byte {
 195	return r.w.output.Bytes()
 196}
 197
 198func HtmlRendererWithParameters(flags HtmlFlags, title string,
 199	css string, renderParameters HtmlRendererParameters) Renderer {
 200	// configure the rendering engine
 201	closeTag := htmlClose
 202	if flags&UseXHTML != 0 {
 203		closeTag = xhtmlClose
 204	}
 205
 206	if renderParameters.FootnoteReturnLinkContents == "" {
 207		renderParameters.FootnoteReturnLinkContents = `<sup>[return]</sup>`
 208	}
 209
 210	var writer HtmlWriter
 211	return &Html{
 212		flags:      flags,
 213		closeTag:   closeTag,
 214		title:      title,
 215		css:        css,
 216		parameters: renderParameters,
 217
 218		headerCount:  0,
 219		currentLevel: 0,
 220		toc:          new(bytes.Buffer),
 221
 222		headerIDs: make(map[string]int),
 223
 224		smartypants: smartypants(flags),
 225		w:           writer,
 226	}
 227}
 228
 229// Using if statements is a bit faster than a switch statement. As the compiler
 230// improves, this should be unnecessary this is only worthwhile because
 231// attrEscape is the single largest CPU user in normal use.
 232// Also tried using map, but that gave a ~3x slowdown.
 233func escapeSingleChar(char byte) (string, bool) {
 234	if char == '"' {
 235		return "&quot;", true
 236	}
 237	if char == '&' {
 238		return "&amp;", true
 239	}
 240	if char == '<' {
 241		return "&lt;", true
 242	}
 243	if char == '>' {
 244		return "&gt;", true
 245	}
 246	return "", false
 247}
 248
 249func (r *Html) attrEscape(src []byte) {
 250	org := 0
 251	for i, ch := range src {
 252		if entity, ok := escapeSingleChar(ch); ok {
 253			if i > org {
 254				// copy all the normal characters since the last escape
 255				r.w.Write(src[org:i])
 256			}
 257			org = i + 1
 258			r.w.WriteString(entity)
 259		}
 260	}
 261	if org < len(src) {
 262		r.w.Write(src[org:])
 263	}
 264}
 265
 266func (r *Html) entityEscapeWithSkip(src []byte, skipRanges [][]int) {
 267	end := 0
 268	for _, rang := range skipRanges {
 269		r.attrEscape(src[end:rang[0]])
 270		r.w.Write(src[rang[0]:rang[1]])
 271		end = rang[1]
 272	}
 273	r.attrEscape(src[end:])
 274}
 275
 276func (r *Html) GetFlags() HtmlFlags {
 277	return r.flags
 278}
 279
 280func (r *Html) TitleBlock(text []byte) {
 281	text = bytes.TrimPrefix(text, []byte("% "))
 282	text = bytes.Replace(text, []byte("\n% "), []byte("\n"), -1)
 283	r.w.WriteString("<h1 class=\"title\">")
 284	r.w.Write(text)
 285	r.w.WriteString("\n</h1>")
 286}
 287
 288func (r *Html) BeginHeader(level int, id string) {
 289	r.w.Newline()
 290
 291	if id == "" && r.flags&Toc != 0 {
 292		id = fmt.Sprintf("toc_%d", r.headerCount)
 293	}
 294
 295	if id != "" {
 296		id = r.ensureUniqueHeaderID(id)
 297
 298		if r.parameters.HeaderIDPrefix != "" {
 299			id = r.parameters.HeaderIDPrefix + id
 300		}
 301
 302		if r.parameters.HeaderIDSuffix != "" {
 303			id = id + r.parameters.HeaderIDSuffix
 304		}
 305
 306		r.w.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, id))
 307	} else {
 308		r.w.WriteString(fmt.Sprintf("<h%d>", level))
 309	}
 310}
 311
 312func (r *Html) EndHeader(level int, id string, header []byte) {
 313	// are we building a table of contents?
 314	if r.flags&Toc != 0 {
 315		r.TocHeaderWithAnchor(header, level, id)
 316	}
 317
 318	r.w.WriteString(fmt.Sprintf("</h%d>\n", level))
 319}
 320
 321func (r *Html) BlockHtml(text []byte) {
 322	if r.flags&SkipHTML != 0 {
 323		return
 324	}
 325
 326	r.w.Newline()
 327	r.w.Write(text)
 328	r.w.WriteByte('\n')
 329}
 330
 331func (r *Html) HRule() {
 332	r.w.Newline()
 333	r.w.WriteString("<hr")
 334	r.w.WriteString(r.closeTag)
 335	r.w.WriteByte('\n')
 336}
 337
 338func (r *Html) BlockCode(text []byte, lang string) {
 339	r.w.Newline()
 340
 341	// parse out the language names/classes
 342	count := 0
 343	for _, elt := range strings.Fields(lang) {
 344		if elt[0] == '.' {
 345			elt = elt[1:]
 346		}
 347		if len(elt) == 0 {
 348			continue
 349		}
 350		if count == 0 {
 351			r.w.WriteString("<pre><code class=\"language-")
 352		} else {
 353			r.w.WriteByte(' ')
 354		}
 355		r.attrEscape([]byte(elt))
 356		count++
 357	}
 358
 359	if count == 0 {
 360		r.w.WriteString("<pre><code>")
 361	} else {
 362		r.w.WriteString("\">")
 363	}
 364
 365	r.attrEscape(text)
 366	r.w.WriteString("</code></pre>\n")
 367}
 368
 369func (r *Html) BlockQuote(text []byte) {
 370	r.w.Newline()
 371	r.w.WriteString("<blockquote>\n")
 372	r.w.Write(text)
 373	r.w.WriteString("</blockquote>\n")
 374}
 375
 376func (r *Html) Table(header []byte, body []byte, columnData []int) {
 377	r.w.Newline()
 378	r.w.WriteString("<table>\n<thead>\n")
 379	r.w.Write(header)
 380	r.w.WriteString("</thead>\n\n<tbody>\n")
 381	r.w.Write(body)
 382	r.w.WriteString("</tbody>\n</table>\n")
 383}
 384
 385func (r *Html) TableRow(text []byte) {
 386	r.w.Newline()
 387	r.w.WriteString("<tr>\n")
 388	r.w.Write(text)
 389	r.w.WriteString("\n</tr>\n")
 390}
 391
 392func leadingNewline(out *bytes.Buffer) {
 393	if out.Len() > 0 {
 394		out.WriteByte('\n')
 395	}
 396}
 397
 398func (r *Html) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
 399	leadingNewline(out)
 400	switch align {
 401	case TableAlignmentLeft:
 402		out.WriteString("<th align=\"left\">")
 403	case TableAlignmentRight:
 404		out.WriteString("<th align=\"right\">")
 405	case TableAlignmentCenter:
 406		out.WriteString("<th align=\"center\">")
 407	default:
 408		out.WriteString("<th>")
 409	}
 410
 411	out.Write(text)
 412	out.WriteString("</th>")
 413}
 414
 415func (r *Html) TableCell(out *bytes.Buffer, text []byte, align int) {
 416	leadingNewline(out)
 417	switch align {
 418	case TableAlignmentLeft:
 419		out.WriteString("<td align=\"left\">")
 420	case TableAlignmentRight:
 421		out.WriteString("<td align=\"right\">")
 422	case TableAlignmentCenter:
 423		out.WriteString("<td align=\"center\">")
 424	default:
 425		out.WriteString("<td>")
 426	}
 427
 428	out.Write(text)
 429	out.WriteString("</td>")
 430}
 431
 432func (r *Html) BeginFootnotes() {
 433	r.w.WriteString("<div class=\"footnotes\">\n")
 434	r.HRule()
 435	r.BeginList(ListTypeOrdered)
 436}
 437
 438func (r *Html) EndFootnotes() {
 439	r.EndList(ListTypeOrdered)
 440	r.w.WriteString("</div>\n")
 441}
 442
 443func (r *Html) FootnoteItem(name, text []byte, flags ListType) {
 444	if flags&ListItemContainsBlock != 0 || flags&ListItemBeginningOfList != 0 {
 445		r.w.Newline()
 446	}
 447	slug := slugify(name)
 448	r.w.WriteString(`<li id="`)
 449	r.w.WriteString(`fn:`)
 450	r.w.WriteString(r.parameters.FootnoteAnchorPrefix)
 451	r.w.Write(slug)
 452	r.w.WriteString(`">`)
 453	r.w.Write(text)
 454	if r.flags&FootnoteReturnLinks != 0 {
 455		r.w.WriteString(` <a class="footnote-return" href="#`)
 456		r.w.WriteString(`fnref:`)
 457		r.w.WriteString(r.parameters.FootnoteAnchorPrefix)
 458		r.w.Write(slug)
 459		r.w.WriteString(`">`)
 460		r.w.WriteString(r.parameters.FootnoteReturnLinkContents)
 461		r.w.WriteString(`</a>`)
 462	}
 463	r.w.WriteString("</li>\n")
 464}
 465
 466func (r *Html) BeginList(flags ListType) {
 467	r.w.Newline()
 468
 469	if flags&ListTypeDefinition != 0 {
 470		r.w.WriteString("<dl>")
 471	} else if flags&ListTypeOrdered != 0 {
 472		r.w.WriteString("<ol>")
 473	} else {
 474		r.w.WriteString("<ul>")
 475	}
 476}
 477
 478func (r *Html) EndList(flags ListType) {
 479	if flags&ListTypeDefinition != 0 {
 480		r.w.WriteString("</dl>\n")
 481	} else if flags&ListTypeOrdered != 0 {
 482		r.w.WriteString("</ol>\n")
 483	} else {
 484		r.w.WriteString("</ul>\n")
 485	}
 486}
 487
 488func (r *Html) ListItem(text []byte, flags ListType) {
 489	if (flags&ListItemContainsBlock != 0 && flags&ListTypeDefinition == 0) ||
 490		flags&ListItemBeginningOfList != 0 {
 491		r.w.Newline()
 492	}
 493	if flags&ListTypeTerm != 0 {
 494		r.w.WriteString("<dt>")
 495	} else if flags&ListTypeDefinition != 0 {
 496		r.w.WriteString("<dd>")
 497	} else {
 498		r.w.WriteString("<li>")
 499	}
 500	r.w.Write(text)
 501	if flags&ListTypeTerm != 0 {
 502		r.w.WriteString("</dt>\n")
 503	} else if flags&ListTypeDefinition != 0 {
 504		r.w.WriteString("</dd>\n")
 505	} else {
 506		r.w.WriteString("</li>\n")
 507	}
 508}
 509
 510func (r *Html) BeginParagraph() {
 511	r.w.Newline()
 512	r.w.WriteString("<p>")
 513}
 514
 515func (r *Html) EndParagraph() {
 516	r.w.WriteString("</p>\n")
 517}
 518
 519func (r *Html) AutoLink(link []byte, kind LinkType) {
 520	skipRanges := htmlEntity.FindAllIndex(link, -1)
 521	if r.flags&Safelink != 0 && !isSafeLink(link) && kind != LinkTypeEmail {
 522		// mark it but don't link it if it is not a safe link: no smartypants
 523		r.w.WriteString("<tt>")
 524		r.entityEscapeWithSkip(link, skipRanges)
 525		r.w.WriteString("</tt>")
 526		return
 527	}
 528
 529	r.w.WriteString("<a href=\"")
 530	if kind == LinkTypeEmail {
 531		r.w.WriteString("mailto:")
 532	} else {
 533		r.maybeWriteAbsolutePrefix(link)
 534	}
 535
 536	r.entityEscapeWithSkip(link, skipRanges)
 537
 538	var relAttrs []string
 539	if r.flags&NofollowLinks != 0 && !isRelativeLink(link) {
 540		relAttrs = append(relAttrs, "nofollow")
 541	}
 542	if r.flags&NoreferrerLinks != 0 && !isRelativeLink(link) {
 543		relAttrs = append(relAttrs, "noreferrer")
 544	}
 545	if len(relAttrs) > 0 {
 546		r.w.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
 547	}
 548
 549	// blank target only add to external link
 550	if r.flags&HrefTargetBlank != 0 && !isRelativeLink(link) {
 551		r.w.WriteString("\" target=\"_blank")
 552	}
 553
 554	r.w.WriteString("\">")
 555
 556	// Pretty print: if we get an email address as
 557	// an actual URI, e.g. `mailto:foo@bar.com`, we don't
 558	// want to print the `mailto:` prefix
 559	switch {
 560	case bytes.HasPrefix(link, []byte("mailto://")):
 561		r.attrEscape(link[len("mailto://"):])
 562	case bytes.HasPrefix(link, []byte("mailto:")):
 563		r.attrEscape(link[len("mailto:"):])
 564	default:
 565		r.entityEscapeWithSkip(link, skipRanges)
 566	}
 567
 568	r.w.WriteString("</a>")
 569}
 570
 571func (r *Html) CodeSpan(text []byte) {
 572	r.w.WriteString("<code>")
 573	r.attrEscape(text)
 574	r.w.WriteString("</code>")
 575}
 576
 577func (r *Html) DoubleEmphasis(text []byte) {
 578	r.w.WriteString("<strong>")
 579	r.w.Write(text)
 580	r.w.WriteString("</strong>")
 581}
 582
 583func (r *Html) Emphasis(text []byte) {
 584	if len(text) == 0 {
 585		return
 586	}
 587	r.w.WriteString("<em>")
 588	r.w.Write(text)
 589	r.w.WriteString("</em>")
 590}
 591
 592func (r *Html) maybeWriteAbsolutePrefix(link []byte) {
 593	if r.parameters.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
 594		r.w.WriteString(r.parameters.AbsolutePrefix)
 595		if link[0] != '/' {
 596			r.w.WriteByte('/')
 597		}
 598	}
 599}
 600
 601func (r *Html) Image(link []byte, title []byte, alt []byte) {
 602	if r.flags&SkipImages != 0 {
 603		return
 604	}
 605
 606	r.w.WriteString("<img src=\"")
 607	r.maybeWriteAbsolutePrefix(link)
 608	r.attrEscape(link)
 609	r.w.WriteString("\" alt=\"")
 610	if len(alt) > 0 {
 611		r.attrEscape(alt)
 612	}
 613	if len(title) > 0 {
 614		r.w.WriteString("\" title=\"")
 615		r.attrEscape(title)
 616	}
 617
 618	r.w.WriteByte('"')
 619	r.w.WriteString(r.closeTag)
 620}
 621
 622func (r *Html) LineBreak() {
 623	r.w.WriteString("<br")
 624	r.w.WriteString(r.closeTag)
 625	r.w.WriteByte('\n')
 626}
 627
 628func (r *Html) Link(link []byte, title []byte, content []byte) {
 629	if r.flags&SkipLinks != 0 {
 630		// write the link text out but don't link it, just mark it with typewriter font
 631		r.w.WriteString("<tt>")
 632		r.attrEscape(content)
 633		r.w.WriteString("</tt>")
 634		return
 635	}
 636
 637	if r.flags&Safelink != 0 && !isSafeLink(link) {
 638		// write the link text out but don't link it, just mark it with typewriter font
 639		r.w.WriteString("<tt>")
 640		r.attrEscape(content)
 641		r.w.WriteString("</tt>")
 642		return
 643	}
 644
 645	r.w.WriteString("<a href=\"")
 646	r.maybeWriteAbsolutePrefix(link)
 647	r.attrEscape(link)
 648	if len(title) > 0 {
 649		r.w.WriteString("\" title=\"")
 650		r.attrEscape(title)
 651	}
 652	var relAttrs []string
 653	if r.flags&NofollowLinks != 0 && !isRelativeLink(link) {
 654		relAttrs = append(relAttrs, "nofollow")
 655	}
 656	if r.flags&NoreferrerLinks != 0 && !isRelativeLink(link) {
 657		relAttrs = append(relAttrs, "noreferrer")
 658	}
 659	if len(relAttrs) > 0 {
 660		r.w.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
 661	}
 662
 663	// blank target only add to external link
 664	if r.flags&HrefTargetBlank != 0 && !isRelativeLink(link) {
 665		r.w.WriteString("\" target=\"_blank")
 666	}
 667
 668	r.w.WriteString("\">")
 669	r.w.Write(content)
 670	r.w.WriteString("</a>")
 671	return
 672}
 673
 674func (r *Html) RawHtmlTag(text []byte) {
 675	if r.flags&SkipHTML != 0 {
 676		return
 677	}
 678	if r.flags&SkipStyle != 0 && isHtmlTag(text, "style") {
 679		return
 680	}
 681	if r.flags&SkipLinks != 0 && isHtmlTag(text, "a") {
 682		return
 683	}
 684	if r.flags&SkipImages != 0 && isHtmlTag(text, "img") {
 685		return
 686	}
 687	r.w.Write(text)
 688}
 689
 690func (r *Html) TripleEmphasis(text []byte) {
 691	r.w.WriteString("<strong><em>")
 692	r.w.Write(text)
 693	r.w.WriteString("</em></strong>")
 694}
 695
 696func (r *Html) StrikeThrough(text []byte) {
 697	r.w.WriteString("<del>")
 698	r.w.Write(text)
 699	r.w.WriteString("</del>")
 700}
 701
 702func (r *Html) FootnoteRef(ref []byte, id int) {
 703	slug := slugify(ref)
 704	r.w.WriteString(`<sup class="footnote-ref" id="`)
 705	r.w.WriteString(`fnref:`)
 706	r.w.WriteString(r.parameters.FootnoteAnchorPrefix)
 707	r.w.Write(slug)
 708	r.w.WriteString(`"><a rel="footnote" href="#`)
 709	r.w.WriteString(`fn:`)
 710	r.w.WriteString(r.parameters.FootnoteAnchorPrefix)
 711	r.w.Write(slug)
 712	r.w.WriteString(`">`)
 713	r.w.WriteString(strconv.Itoa(id))
 714	r.w.WriteString(`</a></sup>`)
 715}
 716
 717func (r *Html) Entity(entity []byte) {
 718	r.w.Write(entity)
 719}
 720
 721func (r *Html) NormalText(text []byte) {
 722	if r.flags&UseSmartypants != 0 {
 723		r.Smartypants(text)
 724	} else {
 725		r.attrEscape(text)
 726	}
 727}
 728
 729func (r *Html) Smartypants(text []byte) {
 730	smrt := smartypantsData{false, false}
 731
 732	// first do normal entity escaping
 733	text = r.CaptureWrites(func() {
 734		r.attrEscape(text)
 735	})
 736
 737	mark := 0
 738	for i := 0; i < len(text); i++ {
 739		if action := r.smartypants[text[i]]; action != nil {
 740			if i > mark {
 741				r.w.Write(text[mark:i])
 742			}
 743
 744			previousChar := byte(0)
 745			if i > 0 {
 746				previousChar = text[i-1]
 747			}
 748			var tmp bytes.Buffer
 749			i += action(&tmp, &smrt, previousChar, text[i:])
 750			r.w.Write(tmp.Bytes())
 751			mark = i + 1
 752		}
 753	}
 754
 755	if mark < len(text) {
 756		r.w.Write(text[mark:])
 757	}
 758}
 759
 760func (r *Html) DocumentHeader() {
 761	if r.flags&CompletePage == 0 {
 762		return
 763	}
 764
 765	ending := ""
 766	if r.flags&UseXHTML != 0 {
 767		r.w.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
 768		r.w.WriteString("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
 769		r.w.WriteString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
 770		ending = " /"
 771	} else {
 772		r.w.WriteString("<!DOCTYPE html>\n")
 773		r.w.WriteString("<html>\n")
 774	}
 775	r.w.WriteString("<head>\n")
 776	r.w.WriteString("  <title>")
 777	r.NormalText([]byte(r.title))
 778	r.w.WriteString("</title>\n")
 779	r.w.WriteString("  <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
 780	r.w.WriteString(VERSION)
 781	r.w.WriteString("\"")
 782	r.w.WriteString(ending)
 783	r.w.WriteString(">\n")
 784	r.w.WriteString("  <meta charset=\"utf-8\"")
 785	r.w.WriteString(ending)
 786	r.w.WriteString(">\n")
 787	if r.css != "" {
 788		r.w.WriteString("  <link rel=\"stylesheet\" type=\"text/css\" href=\"")
 789		r.attrEscape([]byte(r.css))
 790		r.w.WriteString("\"")
 791		r.w.WriteString(ending)
 792		r.w.WriteString(">\n")
 793	}
 794	r.w.WriteString("</head>\n")
 795	r.w.WriteString("<body>\n")
 796
 797	r.tocMarker = out.Len()
 798}
 799
 800func (r *Html) DocumentFooter() {
 801	// finalize and insert the table of contents
 802	if r.flags&Toc != 0 {
 803		r.TocFinalize()
 804
 805		// now we have to insert the table of contents into the document
 806		var temp bytes.Buffer
 807
 808		// start by making a copy of everything after the document header
 809		temp.Write(out.Bytes()[r.tocMarker:])
 810
 811		// now clear the copied material from the main output buffer
 812		out.Truncate(r.tocMarker)
 813
 814		// corner case spacing issue
 815		if r.flags&CompletePage != 0 {
 816			r.w.WriteByte('\n')
 817		}
 818
 819		// insert the table of contents
 820		r.w.WriteString("<nav>\n")
 821		r.w.Write(r.toc.Bytes())
 822		r.w.WriteString("</nav>\n")
 823
 824		// corner case spacing issue
 825		if r.flags&CompletePage == 0 && r.flags&OmitContents == 0 {
 826			r.w.WriteByte('\n')
 827		}
 828
 829		// write out everything that came after it
 830		if r.flags&OmitContents == 0 {
 831			r.w.Write(temp.Bytes())
 832		}
 833	}
 834
 835	if r.flags&CompletePage != 0 {
 836		r.w.WriteString("\n</body>\n")
 837		r.w.WriteString("</html>\n")
 838	}
 839
 840}
 841
 842func (r *Html) TocHeaderWithAnchor(text []byte, level int, anchor string) {
 843	for level > r.currentLevel {
 844		switch {
 845		case bytes.HasSuffix(r.toc.Bytes(), []byte("</li>\n")):
 846			// this sublist can nest underneath a header
 847			size := r.toc.Len()
 848			r.toc.Truncate(size - len("</li>\n"))
 849
 850		case r.currentLevel > 0:
 851			r.toc.WriteString("<li>")
 852		}
 853		if r.toc.Len() > 0 {
 854			r.toc.WriteByte('\n')
 855		}
 856		r.toc.WriteString("<ul>\n")
 857		r.currentLevel++
 858	}
 859
 860	for level < r.currentLevel {
 861		r.toc.WriteString("</ul>")
 862		if r.currentLevel > 1 {
 863			r.toc.WriteString("</li>\n")
 864		}
 865		r.currentLevel--
 866	}
 867
 868	r.toc.WriteString("<li><a href=\"#")
 869	if anchor != "" {
 870		r.toc.WriteString(anchor)
 871	} else {
 872		r.toc.WriteString("toc_")
 873		r.toc.WriteString(strconv.Itoa(r.headerCount))
 874	}
 875	r.toc.WriteString("\">")
 876	r.headerCount++
 877
 878	r.toc.Write(text)
 879
 880	r.toc.WriteString("</a></li>\n")
 881}
 882
 883func (r *Html) TocHeader(text []byte, level int) {
 884	r.TocHeaderWithAnchor(text, level, "")
 885}
 886
 887func (r *Html) TocFinalize() {
 888	for r.currentLevel > 1 {
 889		r.toc.WriteString("</ul></li>\n")
 890		r.currentLevel--
 891	}
 892
 893	if r.currentLevel > 0 {
 894		r.toc.WriteString("</ul>\n")
 895	}
 896}
 897
 898func isHtmlTag(tag []byte, tagname string) bool {
 899	found, _ := findHtmlTagPos(tag, tagname)
 900	return found
 901}
 902
 903// Look for a character, but ignore it when it's in any kind of quotes, it
 904// might be JavaScript
 905func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
 906	inSingleQuote := false
 907	inDoubleQuote := false
 908	inGraveQuote := false
 909	i := start
 910	for i < len(html) {
 911		switch {
 912		case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
 913			return i
 914		case html[i] == '\'':
 915			inSingleQuote = !inSingleQuote
 916		case html[i] == '"':
 917			inDoubleQuote = !inDoubleQuote
 918		case html[i] == '`':
 919			inGraveQuote = !inGraveQuote
 920		}
 921		i++
 922	}
 923	return start
 924}
 925
 926func findHtmlTagPos(tag []byte, tagname string) (bool, int) {
 927	i := 0
 928	if i < len(tag) && tag[0] != '<' {
 929		return false, -1
 930	}
 931	i++
 932	i = skipSpace(tag, i)
 933
 934	if i < len(tag) && tag[i] == '/' {
 935		i++
 936	}
 937
 938	i = skipSpace(tag, i)
 939	j := 0
 940	for ; i < len(tag); i, j = i+1, j+1 {
 941		if j >= len(tagname) {
 942			break
 943		}
 944
 945		if strings.ToLower(string(tag[i]))[0] != tagname[j] {
 946			return false, -1
 947		}
 948	}
 949
 950	if i == len(tag) {
 951		return false, -1
 952	}
 953
 954	rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
 955	if rightAngle > i {
 956		return true, rightAngle
 957	}
 958
 959	return false, -1
 960}
 961
 962func skipUntilChar(text []byte, start int, char byte) int {
 963	i := start
 964	for i < len(text) && text[i] != char {
 965		i++
 966	}
 967	return i
 968}
 969
 970func skipSpace(tag []byte, i int) int {
 971	for i < len(tag) && isspace(tag[i]) {
 972		i++
 973	}
 974	return i
 975}
 976
 977func skipChar(data []byte, start int, char byte) int {
 978	i := start
 979	for i < len(data) && data[i] == char {
 980		i++
 981	}
 982	return i
 983}
 984
 985func isRelativeLink(link []byte) (yes bool) {
 986	// a tag begin with '#'
 987	if link[0] == '#' {
 988		return true
 989	}
 990
 991	// link begin with '/' but not '//', the second maybe a protocol relative link
 992	if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
 993		return true
 994	}
 995
 996	// only the root '/'
 997	if len(link) == 1 && link[0] == '/' {
 998		return true
 999	}
1000
1001	// current directory : begin with "./"
1002	if bytes.HasPrefix(link, []byte("./")) {
1003		return true
1004	}
1005
1006	// parent directory : begin with "../"
1007	if bytes.HasPrefix(link, []byte("../")) {
1008		return true
1009	}
1010
1011	return false
1012}
1013
1014func (r *Html) ensureUniqueHeaderID(id string) string {
1015	for count, found := r.headerIDs[id]; found; count, found = r.headerIDs[id] {
1016		tmp := fmt.Sprintf("%s-%d", id, count+1)
1017
1018		if _, tmpFound := r.headerIDs[tmp]; !tmpFound {
1019			r.headerIDs[id] = count + 1
1020			id = tmp
1021		} else {
1022			id = id + "-1"
1023		}
1024	}
1025
1026	if _, found := r.headerIDs[id]; !found {
1027		r.headerIDs[id] = 0
1028	}
1029
1030	return id
1031}