all repos — grayfriday @ 0dfcd3beb5e3d62153ef29396eccccf9eb13f238

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