all repos — grayfriday @ dce6df90b9363579a8609fa70faef3a8b56b85b4

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