github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/russross/blackfriday/html.go (about)

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