diff options
| author | Christine Dodrill <me@christine.website> | 2017-12-13 10:43:58 -0800 |
|---|---|---|
| committer | Christine Dodrill <me@christine.website> | 2017-12-13 11:42:37 -0800 |
| commit | 3a21ef192628f6952eaa981bcdf718a35a4b43c7 (patch) | |
| tree | 9c88a3ddc57ab5014f436ec2c08c96280872632e /vendor/github.com/russross/blackfriday/html.go | |
| parent | 3b4b6cede9bc30008b0f40989a1564b26e64fd05 (diff) | |
| download | xesite-3a21ef192628f6952eaa981bcdf718a35a4b43c7.tar.xz xesite-3a21ef192628f6952eaa981bcdf718a35a4b43c7.zip | |
convert to go buildpack
Diffstat (limited to 'vendor/github.com/russross/blackfriday/html.go')
| -rw-r--r-- | vendor/github.com/russross/blackfriday/html.go | 1519 |
1 files changed, 755 insertions, 764 deletions
diff --git a/vendor/github.com/russross/blackfriday/html.go b/vendor/github.com/russross/blackfriday/html.go index 74e67ee..25fb185 100644 --- a/vendor/github.com/russross/blackfriday/html.go +++ b/vendor/github.com/russross/blackfriday/html.go @@ -18,45 +18,62 @@ package blackfriday import ( "bytes" "fmt" + "io" "regexp" - "strconv" "strings" ) -// Html renderer configuration options. +// HTMLFlags control optional behavior of HTML renderer. +type HTMLFlags int + +// HTML renderer configuration options. const ( - HTML_SKIP_HTML = 1 << iota // skip preformatted HTML blocks - HTML_SKIP_STYLE // skip embedded <style> elements - HTML_SKIP_IMAGES // skip embedded images - HTML_SKIP_LINKS // skip all links - HTML_SAFELINK // only link to trusted protocols - HTML_NOFOLLOW_LINKS // only link with rel="nofollow" - HTML_NOREFERRER_LINKS // only link with rel="noreferrer" - HTML_HREF_TARGET_BLANK // add a blank target - HTML_TOC // generate a table of contents - HTML_OMIT_CONTENTS // skip the main contents (for a standalone table of contents) - HTML_COMPLETE_PAGE // generate a complete HTML page - HTML_USE_XHTML // generate XHTML output instead of HTML - HTML_USE_SMARTYPANTS // enable smart punctuation substitutions - HTML_SMARTYPANTS_FRACTIONS // enable smart fractions (with HTML_USE_SMARTYPANTS) - HTML_SMARTYPANTS_DASHES // enable smart dashes (with HTML_USE_SMARTYPANTS) - HTML_SMARTYPANTS_LATEX_DASHES // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES) - HTML_SMARTYPANTS_ANGLED_QUOTES // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering - HTML_FOOTNOTE_RETURN_LINKS // generate a link at the end of a footnote to return to the source + HTMLFlagsNone HTMLFlags = 0 + SkipHTML HTMLFlags = 1 << iota // Skip preformatted HTML blocks + SkipImages // Skip embedded images + SkipLinks // Skip all links + Safelink // Only link to trusted protocols + NofollowLinks // Only link with rel="nofollow" + NoreferrerLinks // Only link with rel="noreferrer" + HrefTargetBlank // Add a blank target + CompletePage // Generate a complete HTML page + UseXHTML // Generate XHTML output instead of HTML + FootnoteReturnLinks // Generate a link at the end of a footnote to return to the source + Smartypants // Enable smart punctuation substitutions + SmartypantsFractions // Enable smart fractions (with Smartypants) + SmartypantsDashes // Enable smart dashes (with Smartypants) + SmartypantsLatexDashes // Enable LaTeX-style dashes (with Smartypants) + SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering + SmartypantsQuotesNBSP // Enable « French guillemets » (with Smartypants) + TOC // Generate a table of contents ) var ( - alignments = []string{ - "left", - "right", - "center", - } + htmlTagRe = regexp.MustCompile("(?i)^" + htmlTag) +) - // TODO: improve this regexp to catch all possible entities: - htmlEntity = regexp.MustCompile(`&[a-z]{2,5};`) +const ( + htmlTag = "(?:" + openTag + "|" + closeTag + "|" + htmlComment + "|" + + processingInstruction + "|" + declaration + "|" + cdata + ")" + closeTag = "</" + tagName + "\\s*[>]" + openTag = "<" + tagName + attribute + "*" + "\\s*/?>" + attribute = "(?:" + "\\s+" + attributeName + attributeValueSpec + "?)" + attributeValue = "(?:" + unquotedValue + "|" + singleQuotedValue + "|" + doubleQuotedValue + ")" + attributeValueSpec = "(?:" + "\\s*=" + "\\s*" + attributeValue + ")" + attributeName = "[a-zA-Z_:][a-zA-Z0-9:._-]*" + cdata = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>" + declaration = "<![A-Z]+" + "\\s+[^>]*>" + doubleQuotedValue = "\"[^\"]*\"" + htmlComment = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->" + processingInstruction = "[<][?].*?[?][>]" + singleQuotedValue = "'[^']*'" + tagName = "[A-Za-z][A-Za-z0-9-]*" + unquotedValue = "[^\"'=<>`\\x00-\\x20]+" ) -type HtmlRendererParameters struct { +// HTMLRendererParameters is a collection of supplementary parameters tweaking +// the behavior of various parts of HTML renderer. +type HTMLRendererParameters struct { // Prepend this text to each relative URL. AbsolutePrefix string // Add this text to each footnote anchor, to ensure uniqueness. @@ -65,34 +82,34 @@ type HtmlRendererParameters struct { // HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string // <sup>[return]</sup> is used. FootnoteReturnLinkContents string - // If set, add this text to the front of each Header ID, to ensure + // If set, add this text to the front of each Heading ID, to ensure // uniqueness. - HeaderIDPrefix string - // If set, add this text to the back of each Header ID, to ensure uniqueness. - HeaderIDSuffix string + HeadingIDPrefix string + // If set, add this text to the back of each Heading ID, to ensure uniqueness. + HeadingIDSuffix string + + Title string // Document title (used if CompletePage is set) + CSS string // Optional CSS file URL (used if CompletePage is set) + Icon string // Optional icon file URL (used if CompletePage is set) + + Flags HTMLFlags // Flags allow customizing this renderer's behavior } -// Html is a type that implements the Renderer interface for HTML output. +// HTMLRenderer is a type that implements the Renderer interface for HTML output. // -// Do not create this directly, instead use the HtmlRenderer function. -type Html struct { - flags int // HTML_* options - closeTag string // how to end singleton tags: either " />" or ">" - title string // document title - css string // optional css file url (used with HTML_COMPLETE_PAGE) +// Do not create this directly, instead use the NewHTMLRenderer function. +type HTMLRenderer struct { + HTMLRendererParameters - parameters HtmlRendererParameters + closeTag string // how to end singleton tags: either " />" or ">" - // table of contents data - tocMarker int - headerCount int - currentLevel int - toc *bytes.Buffer + // Track heading IDs to prevent ID collision in a single generation. + headingIDs map[string]int - // Track header IDs to prevent ID collision in a single generation. - headerIDs map[string]int + lastOutputLen int + disableTags int - smartypants *smartypantsRenderer + sr *SPRenderer } const ( @@ -100,850 +117,824 @@ const ( htmlClose = ">" ) -// HtmlRenderer creates and configures an Html object, which +// NewHTMLRenderer creates and configures an HTMLRenderer object, which // satisfies the Renderer interface. -// -// flags is a set of HTML_* options ORed together. -// title is the title of the document, and css is a URL for the document's -// stylesheet. -// title and css are only used when HTML_COMPLETE_PAGE is selected. -func HtmlRenderer(flags int, title string, css string) Renderer { - return HtmlRendererWithParameters(flags, title, css, HtmlRendererParameters{}) -} - -func HtmlRendererWithParameters(flags int, title string, - css string, renderParameters HtmlRendererParameters) Renderer { +func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer { // configure the rendering engine closeTag := htmlClose - if flags&HTML_USE_XHTML != 0 { + if params.Flags&UseXHTML != 0 { closeTag = xhtmlClose } - if renderParameters.FootnoteReturnLinkContents == "" { - renderParameters.FootnoteReturnLinkContents = `<sup>[return]</sup>` + if params.FootnoteReturnLinkContents == "" { + params.FootnoteReturnLinkContents = `<sup>[return]</sup>` } - return &Html{ - flags: flags, - closeTag: closeTag, - title: title, - css: css, - parameters: renderParameters, - - headerCount: 0, - currentLevel: 0, - toc: new(bytes.Buffer), + return &HTMLRenderer{ + HTMLRendererParameters: params, - headerIDs: make(map[string]int), + closeTag: closeTag, + headingIDs: make(map[string]int), - smartypants: smartypants(flags), + sr: NewSmartypantsRenderer(params.Flags), } } -// Using if statements is a bit faster than a switch statement. As the compiler -// improves, this should be unnecessary this is only worthwhile because -// attrEscape is the single largest CPU user in normal use. -// Also tried using map, but that gave a ~3x slowdown. -func escapeSingleChar(char byte) (string, bool) { - if char == '"' { - return """, true - } - if char == '&' { - return "&", true - } - if char == '<' { - return "<", true - } - if char == '>' { - return ">", true - } - return "", false +func isHTMLTag(tag []byte, tagname string) bool { + found, _ := findHTMLTagPos(tag, tagname) + return found } -func attrEscape(out *bytes.Buffer, src []byte) { - org := 0 - for i, ch := range src { - if entity, ok := escapeSingleChar(ch); ok { - if i > org { - // copy all the normal characters since the last escape - out.Write(src[org:i]) - } - org = i + 1 - out.WriteString(entity) +// Look for a character, but ignore it when it's in any kind of quotes, it +// might be JavaScript +func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int { + inSingleQuote := false + inDoubleQuote := false + inGraveQuote := false + i := start + for i < len(html) { + switch { + case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote: + return i + case html[i] == '\'': + inSingleQuote = !inSingleQuote + case html[i] == '"': + inDoubleQuote = !inDoubleQuote + case html[i] == '`': + inGraveQuote = !inGraveQuote } + i++ } - if org < len(src) { - out.Write(src[org:]) - } + return start } -func entityEscapeWithSkip(out *bytes.Buffer, src []byte, skipRanges [][]int) { - end := 0 - for _, rang := range skipRanges { - attrEscape(out, src[end:rang[0]]) - out.Write(src[rang[0]:rang[1]]) - end = rang[1] +func findHTMLTagPos(tag []byte, tagname string) (bool, int) { + i := 0 + if i < len(tag) && tag[0] != '<' { + return false, -1 } - attrEscape(out, src[end:]) -} - -func (options *Html) GetFlags() int { - return options.flags -} - -func (options *Html) TitleBlock(out *bytes.Buffer, text []byte) { - text = bytes.TrimPrefix(text, []byte("% ")) - text = bytes.Replace(text, []byte("\n% "), []byte("\n"), -1) - out.WriteString("<h1 class=\"title\">") - out.Write(text) - out.WriteString("\n</h1>") -} - -func (options *Html) Header(out *bytes.Buffer, text func() bool, level int, id string) { - marker := out.Len() - doubleSpace(out) + i++ + i = skipSpace(tag, i) - if id == "" && options.flags&HTML_TOC != 0 { - id = fmt.Sprintf("toc_%d", options.headerCount) + if i < len(tag) && tag[i] == '/' { + i++ } - if id != "" { - id = options.ensureUniqueHeaderID(id) - - if options.parameters.HeaderIDPrefix != "" { - id = options.parameters.HeaderIDPrefix + id + i = skipSpace(tag, i) + j := 0 + for ; i < len(tag); i, j = i+1, j+1 { + if j >= len(tagname) { + break } - if options.parameters.HeaderIDSuffix != "" { - id = id + options.parameters.HeaderIDSuffix + if strings.ToLower(string(tag[i]))[0] != tagname[j] { + return false, -1 } - - out.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, id)) - } else { - out.WriteString(fmt.Sprintf("<h%d>", level)) } - tocMarker := out.Len() - if !text() { - out.Truncate(marker) - return + if i == len(tag) { + return false, -1 } - // are we building a table of contents? - if options.flags&HTML_TOC != 0 { - options.TocHeaderWithAnchor(out.Bytes()[tocMarker:], level, id) + rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>') + if rightAngle >= i { + return true, rightAngle } - out.WriteString(fmt.Sprintf("</h%d>\n", level)) + return false, -1 } -func (options *Html) BlockHtml(out *bytes.Buffer, text []byte) { - if options.flags&HTML_SKIP_HTML != 0 { - return +func skipSpace(tag []byte, i int) int { + for i < len(tag) && isspace(tag[i]) { + i++ } - - doubleSpace(out) - out.Write(text) - out.WriteByte('\n') -} - -func (options *Html) HRule(out *bytes.Buffer) { - doubleSpace(out) - out.WriteString("<hr") - out.WriteString(options.closeTag) - out.WriteByte('\n') + return i } -func (options *Html) BlockCode(out *bytes.Buffer, text []byte, lang string) { - doubleSpace(out) +func isRelativeLink(link []byte) (yes bool) { + // a tag begin with '#' + if link[0] == '#' { + return true + } - // parse out the language names/classes - count := 0 - for _, elt := range strings.Fields(lang) { - if elt[0] == '.' { - elt = elt[1:] - } - if len(elt) == 0 { - continue - } - if count == 0 { - out.WriteString("<pre><code class=\"language-") - } else { - out.WriteByte(' ') - } - attrEscape(out, []byte(elt)) - count++ + // link begin with '/' but not '//', the second maybe a protocol relative link + if len(link) >= 2 && link[0] == '/' && link[1] != '/' { + return true } - if count == 0 { - out.WriteString("<pre><code>") - } else { - out.WriteString("\">") + // only the root '/' + if len(link) == 1 && link[0] == '/' { + return true } - attrEscape(out, text) - out.WriteString("</code></pre>\n") -} + // current directory : begin with "./" + if bytes.HasPrefix(link, []byte("./")) { + return true + } -func (options *Html) BlockQuote(out *bytes.Buffer, text []byte) { - doubleSpace(out) - out.WriteString("<blockquote>\n") - out.Write(text) - out.WriteString("</blockquote>\n") -} + // parent directory : begin with "../" + if bytes.HasPrefix(link, []byte("../")) { + return true + } -func (options *Html) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) { - doubleSpace(out) - out.WriteString("<table>\n<thead>\n") - out.Write(header) - out.WriteString("</thead>\n\n<tbody>\n") - out.Write(body) - out.WriteString("</tbody>\n</table>\n") + return false } -func (options *Html) TableRow(out *bytes.Buffer, text []byte) { - doubleSpace(out) - out.WriteString("<tr>\n") - out.Write(text) - out.WriteString("\n</tr>\n") -} +func (r *HTMLRenderer) ensureUniqueHeadingID(id string) string { + for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] { + tmp := fmt.Sprintf("%s-%d", id, count+1) -func (options *Html) TableHeaderCell(out *bytes.Buffer, text []byte, align int) { - doubleSpace(out) - switch align { - case TABLE_ALIGNMENT_LEFT: - out.WriteString("<th align=\"left\">") - case TABLE_ALIGNMENT_RIGHT: - out.WriteString("<th align=\"right\">") - case TABLE_ALIGNMENT_CENTER: - out.WriteString("<th align=\"center\">") - default: - out.WriteString("<th>") + if _, tmpFound := r.headingIDs[tmp]; !tmpFound { + r.headingIDs[id] = count + 1 + id = tmp + } else { + id = id + "-1" + } } - out.Write(text) - out.WriteString("</th>") -} - -func (options *Html) TableCell(out *bytes.Buffer, text []byte, align int) { - doubleSpace(out) - switch align { - case TABLE_ALIGNMENT_LEFT: - out.WriteString("<td align=\"left\">") - case TABLE_ALIGNMENT_RIGHT: - out.WriteString("<td align=\"right\">") - case TABLE_ALIGNMENT_CENTER: - out.WriteString("<td align=\"center\">") - default: - out.WriteString("<td>") + if _, found := r.headingIDs[id]; !found { + r.headingIDs[id] = 0 } - out.Write(text) - out.WriteString("</td>") -} - -func (options *Html) Footnotes(out *bytes.Buffer, text func() bool) { - out.WriteString("<div class=\"footnotes\">\n") - options.HRule(out) - options.List(out, text, LIST_TYPE_ORDERED) - out.WriteString("</div>\n") + return id } -func (options *Html) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) { - if flags&LIST_ITEM_CONTAINS_BLOCK != 0 || flags&LIST_ITEM_BEGINNING_OF_LIST != 0 { - doubleSpace(out) - } - slug := slugify(name) - out.WriteString(`<li id="`) - out.WriteString(`fn:`) - out.WriteString(options.parameters.FootnoteAnchorPrefix) - out.Write(slug) - out.WriteString(`">`) - out.Write(text) - if options.flags&HTML_FOOTNOTE_RETURN_LINKS != 0 { - out.WriteString(` <a class="footnote-return" href="#`) - out.WriteString(`fnref:`) - out.WriteString(options.parameters.FootnoteAnchorPrefix) - out.Write(slug) - out.WriteString(`">`) - out.WriteString(options.parameters.FootnoteReturnLinkContents) - out.WriteString(`</a>`) - } - out.WriteString("</li>\n") +func (r *HTMLRenderer) addAbsPrefix(link []byte) []byte { + if r.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' { + newDest := r.AbsolutePrefix + if link[0] != '/' { + newDest += "/" + } + newDest += string(link) + return []byte(newDest) + } + return link } -func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) { - marker := out.Len() - doubleSpace(out) - - if flags&LIST_TYPE_DEFINITION != 0 { - out.WriteString("<dl>") - } else if flags&LIST_TYPE_ORDERED != 0 { - out.WriteString("<ol>") - } else { - out.WriteString("<ul>") +func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string { + if isRelativeLink(link) { + return attrs } - if !text() { - out.Truncate(marker) - return + val := []string{} + if flags&NofollowLinks != 0 { + val = append(val, "nofollow") } - if flags&LIST_TYPE_DEFINITION != 0 { - out.WriteString("</dl>\n") - } else if flags&LIST_TYPE_ORDERED != 0 { - out.WriteString("</ol>\n") - } else { - out.WriteString("</ul>\n") + if flags&NoreferrerLinks != 0 { + val = append(val, "noreferrer") } -} - -func (options *Html) ListItem(out *bytes.Buffer, text []byte, flags int) { - if (flags&LIST_ITEM_CONTAINS_BLOCK != 0 && flags&LIST_TYPE_DEFINITION == 0) || - flags&LIST_ITEM_BEGINNING_OF_LIST != 0 { - doubleSpace(out) + if flags&HrefTargetBlank != 0 { + attrs = append(attrs, "target=\"_blank\"") } - if flags&LIST_TYPE_TERM != 0 { - out.WriteString("<dt>") - } else if flags&LIST_TYPE_DEFINITION != 0 { - out.WriteString("<dd>") - } else { - out.WriteString("<li>") - } - out.Write(text) - if flags&LIST_TYPE_TERM != 0 { - out.WriteString("</dt>\n") - } else if flags&LIST_TYPE_DEFINITION != 0 { - out.WriteString("</dd>\n") - } else { - out.WriteString("</li>\n") + if len(val) == 0 { + return attrs } + attr := fmt.Sprintf("rel=%q", strings.Join(val, " ")) + return append(attrs, attr) } -func (options *Html) Paragraph(out *bytes.Buffer, text func() bool) { - marker := out.Len() - doubleSpace(out) - - out.WriteString("<p>") - if !text() { - out.Truncate(marker) - return - } - out.WriteString("</p>\n") +func isMailto(link []byte) bool { + return bytes.HasPrefix(link, []byte("mailto:")) } -func (options *Html) AutoLink(out *bytes.Buffer, link []byte, kind int) { - skipRanges := htmlEntity.FindAllIndex(link, -1) - if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) && kind != LINK_TYPE_EMAIL { - // mark it but don't link it if it is not a safe link: no smartypants - out.WriteString("<tt>") - entityEscapeWithSkip(out, link, skipRanges) - out.WriteString("</tt>") - return - } - - out.WriteString("<a href=\"") - if kind == LINK_TYPE_EMAIL { - out.WriteString("mailto:") - } else { - options.maybeWriteAbsolutePrefix(out, link) +func needSkipLink(flags HTMLFlags, dest []byte) bool { + if flags&SkipLinks != 0 { + return true } + return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest) +} - entityEscapeWithSkip(out, link, skipRanges) +func isSmartypantable(node *Node) bool { + pt := node.Parent.Type + return pt != Link && pt != CodeBlock && pt != Code +} - var relAttrs []string - if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) { - relAttrs = append(relAttrs, "nofollow") +func appendLanguageAttr(attrs []string, info []byte) []string { + if len(info) == 0 { + return attrs } - if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) { - relAttrs = append(relAttrs, "noreferrer") + endOfLang := bytes.IndexAny(info, "\t ") + if endOfLang < 0 { + endOfLang = len(info) } - if len(relAttrs) > 0 { - out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " "))) - } - - // blank target only add to external link - if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) { - out.WriteString("\" target=\"_blank") - } - - out.WriteString("\">") + return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang])) +} - // Pretty print: if we get an email address as - // an actual URI, e.g. `mailto:foo@bar.com`, we don't - // want to print the `mailto:` prefix - switch { - case bytes.HasPrefix(link, []byte("mailto://")): - attrEscape(out, link[len("mailto://"):]) - case bytes.HasPrefix(link, []byte("mailto:")): - attrEscape(out, link[len("mailto:"):]) - default: - entityEscapeWithSkip(out, link, skipRanges) +func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) { + w.Write(name) + if len(attrs) > 0 { + w.Write(spaceBytes) + w.Write([]byte(strings.Join(attrs, " "))) } - - out.WriteString("</a>") + w.Write(gtBytes) + r.lastOutputLen = 1 } -func (options *Html) CodeSpan(out *bytes.Buffer, text []byte) { - out.WriteString("<code>") - attrEscape(out, text) - out.WriteString("</code>") +func footnoteRef(prefix string, node *Node) []byte { + urlFrag := prefix + string(slugify(node.Destination)) + anchor := fmt.Sprintf(`<a rel="footnote" href="#fn:%s">%d</a>`, urlFrag, node.NoteID) + return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor)) } -func (options *Html) DoubleEmphasis(out *bytes.Buffer, text []byte) { - out.WriteString("<strong>") - out.Write(text) - out.WriteString("</strong>") +func footnoteItem(prefix string, slug []byte) []byte { + return []byte(fmt.Sprintf(`<li id="fn:%s%s">`, prefix, slug)) } -func (options *Html) Emphasis(out *bytes.Buffer, text []byte) { - if len(text) == 0 { - return - } - out.WriteString("<em>") - out.Write(text) - out.WriteString("</em>") +func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte { + const format = ` <a class="footnote-return" href="#fnref:%s%s">%s</a>` + return []byte(fmt.Sprintf(format, prefix, slug, returnLink)) } -func (options *Html) maybeWriteAbsolutePrefix(out *bytes.Buffer, link []byte) { - if options.parameters.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' { - out.WriteString(options.parameters.AbsolutePrefix) - if link[0] != '/' { - out.WriteByte('/') - } +func itemOpenCR(node *Node) bool { + if node.Prev == nil { + return false } + ld := node.Parent.ListData + return !ld.Tight && ld.ListFlags&ListTypeDefinition == 0 } -func (options *Html) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) { - if options.flags&HTML_SKIP_IMAGES != 0 { - return - } - - out.WriteString("<img src=\"") - options.maybeWriteAbsolutePrefix(out, link) - attrEscape(out, link) - out.WriteString("\" alt=\"") - if len(alt) > 0 { - attrEscape(out, alt) +func skipParagraphTags(node *Node) bool { + grandparent := node.Parent.Parent + if grandparent == nil || grandparent.Type != List { + return false } - if len(title) > 0 { - out.WriteString("\" title=\"") - attrEscape(out, title) - } - - out.WriteByte('"') - out.WriteString(options.closeTag) + tightOrTerm := grandparent.Tight || node.Parent.ListFlags&ListTypeTerm != 0 + return grandparent.Type == List && tightOrTerm } -func (options *Html) LineBreak(out *bytes.Buffer) { - out.WriteString("<br") - out.WriteString(options.closeTag) - out.WriteByte('\n') -} - -func (options *Html) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { - if options.flags&HTML_SKIP_LINKS != 0 { - // write the link text out but don't link it, just mark it with typewriter font - out.WriteString("<tt>") - attrEscape(out, content) - out.WriteString("</tt>") - return - } - - if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) { - // write the link text out but don't link it, just mark it with typewriter font - out.WriteString("<tt>") - attrEscape(out, content) - out.WriteString("</tt>") - return - } - - out.WriteString("<a href=\"") - options.maybeWriteAbsolutePrefix(out, link) - attrEscape(out, link) - if len(title) > 0 { - out.WriteString("\" title=\"") - attrEscape(out, title) - } - var relAttrs []string - if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) { - relAttrs = append(relAttrs, "nofollow") - } - if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) { - relAttrs = append(relAttrs, "noreferrer") - } - if len(relAttrs) > 0 { - out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " "))) - } - - // blank target only add to external link - if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) { - out.WriteString("\" target=\"_blank") +func cellAlignment(align CellAlignFlags) string { + switch align { + case TableAlignmentLeft: + return "left" + case TableAlignmentRight: + return "right" + case TableAlignmentCenter: + return "center" + default: + return "" } - - out.WriteString("\">") - out.Write(content) - out.WriteString("</a>") - return } -func (options *Html) RawHtmlTag(out *bytes.Buffer, text []byte) { - if options.flags&HTML_SKIP_HTML != 0 { - return - } - if options.flags&HTML_SKIP_STYLE != 0 && isHtmlTag(text, "style") { - return - } - if options.flags&HTML_SKIP_LINKS != 0 && isHtmlTag(text, "a") { - return - } - if options.flags&HTML_SKIP_IMAGES != 0 && isHtmlTag(text, "img") { - return +func (r *HTMLRenderer) out(w io.Writer, text []byte) { + if r.disableTags > 0 { + w.Write(htmlTagRe.ReplaceAll(text, []byte{})) + } else { + w.Write(text) } - out.Write(text) + r.lastOutputLen = len(text) } -func (options *Html) TripleEmphasis(out *bytes.Buffer, text []byte) { - out.WriteString("<strong><em>") - out.Write(text) - out.WriteString("</em></strong>") +func (r *HTMLRenderer) cr(w io.Writer) { + if r.lastOutputLen > 0 { + r.out(w, nlBytes) + } } -func (options *Html) StrikeThrough(out *bytes.Buffer, text []byte) { - out.WriteString("<del>") - out.Write(text) - out.WriteString("</del>") -} +var ( + nlBytes = []byte{'\n'} + gtBytes = []byte{'>'} + spaceBytes = []byte{' '} +) -func (options *Html) FootnoteRef(out *bytes.Buffer, ref []byte, id int) { - slug := slugify(ref) - out.WriteString(`<sup class="footnote-ref" id="`) - out.WriteString(`fnref:`) - out.WriteString(options.parameters.FootnoteAnchorPrefix) - out.Write(slug) - out.WriteString(`"><a rel="footnote" href="#`) - out.WriteString(`fn:`) - out.WriteString(options.parameters.FootnoteAnchorPrefix) - out.Write(slug) - out.WriteString(`">`) - out.WriteString(strconv.Itoa(id)) - out.WriteString(`</a></sup>`) -} +var ( + brTag = []byte("<br>") + brXHTMLTag = []byte("<br />") + emTag = []byte("<em>") + emCloseTag = []byte("</em>") + strongTag = []byte("<strong>") + strongCloseTag = []byte("</strong>") + delTag = []byte("<del>") + delCloseTag = []byte("</del>") + ttTag = []byte("<tt>") + ttCloseTag = []byte("</tt>") + aTag = []byte("<a") + aCloseTag = []byte("</a>") + preTag = []byte("<pre>") + preCloseTag = []byte("</pre>") + codeTag = []byte("<code>") + codeCloseTag = []byte("</code>") + pTag = []byte("<p>") + pCloseTag = []byte("</p>") + blockquoteTag = []byte("<blockquote>") + blockquoteCloseTag = []byte("</blockquote>") + hrTag = []byte("<hr>") + hrXHTMLTag = []byte("<hr />") + ulTag = []byte("<ul>") + ulCloseTag = []byte("</ul>") + olTag = []byte("<ol>") + olCloseTag = []byte("</ol>") + dlTag = []byte("<dl>") + dlCloseTag = []byte("</dl>") + liTag = []byte("<li>") + liCloseTag = []byte("</li>") + ddTag = []byte("<dd>") + ddCloseTag = []byte("</dd>") + dtTag = []byte("<dt>") + dtCloseTag = []byte("</dt>") + tableTag = []byte("<table>") + tableCloseTag = []byte("</table>") + tdTag = []byte("<td") + tdCloseTag = []byte("</td>") + thTag = []byte("<th") + thCloseTag = []byte("</th>") + theadTag = []byte("<thead>") + theadCloseTag = []byte("</thead>") + tbodyTag = []byte("<tbody>") + tbodyCloseTag = []byte("</tbody>") + trTag = []byte("<tr>") + trCloseTag = []byte("</tr>") + h1Tag = []byte("<h1") + h1CloseTag = []byte("</h1>") + h2Tag = []byte("<h2") + h2CloseTag = []byte("</h2>") + h3Tag = []byte("<h3") + h3CloseTag = []byte("</h3>") + h4Tag = []byte("<h4") + h4CloseTag = []byte("</h4>") + h5Tag = []byte("<h5") + h5CloseTag = []byte("</h5>") + h6Tag = []byte("<h6") + h6CloseTag = []byte("</h6>") + + footnotesDivBytes = []byte("\n<div class=\"footnotes\">\n\n") + footnotesCloseDivBytes = []byte("\n</div>\n") +) -func (options *Html) Entity(out *bytes.Buffer, entity []byte) { - out.Write(entity) +func headingTagsFromLevel(level int) ([]byte, []byte) { + switch level { + case 1: + return h1Tag, h1CloseTag + case 2: + return h2Tag, h2CloseTag + case 3: + return h3Tag, h3CloseTag + case 4: + return h4Tag, h4CloseTag + case 5: + return h5Tag, h5CloseTag + default: + return h6Tag, h6CloseTag + } } -func (options *Html) NormalText(out *bytes.Buffer, text []byte) { - if options.flags&HTML_USE_SMARTYPANTS != 0 { - options.Smartypants(out, text) +func (r *HTMLRenderer) outHRTag(w io.Writer) { + if r.Flags&UseXHTML == 0 { + r.out(w, hrTag) } else { - attrEscape(out, text) + r.out(w, hrXHTMLTag) } } -func (options *Html) Smartypants(out *bytes.Buffer, text []byte) { - smrt := smartypantsData{false, false} - - // first do normal entity escaping - var escaped bytes.Buffer - attrEscape(&escaped, text) - text = escaped.Bytes() - - mark := 0 - for i := 0; i < len(text); i++ { - if action := options.smartypants[text[i]]; action != nil { - if i > mark { - out.Write(text[mark:i]) - } - - previousChar := byte(0) - if i > 0 { - previousChar = text[i-1] +// RenderNode is a default renderer of a single node of a syntax tree. For +// block nodes it will be called twice: first time with entering=true, second +// time with entering=false, so that it could know when it's working on an open +// tag and when on close. It writes the result to w. +// +// The return value is a way to tell the calling walker to adjust its walk +// pattern: e.g. it can terminate the traversal by returning Terminate. Or it +// can ask the walker to skip a subtree of this node by returning SkipChildren. +// The typical behavior is to return GoToNext, which asks for the usual +// traversal to the next node. +func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus { + attrs := []string{} + switch node.Type { + case Text: + if r.Flags&Smartypants != 0 { + var tmp bytes.Buffer + escapeHTML(&tmp, node.Literal) + r.sr.Process(w, tmp.Bytes()) + } else { + if node.Parent.Type == Link { + escLink(w, node.Literal) + } else { + escapeHTML(w, node.Literal) } - i += action(out, &smrt, previousChar, text[i:]) - mark = i + 1 } - } - - if mark < len(text) { - out.Write(text[mark:]) - } -} - -func (options *Html) DocumentHeader(out *bytes.Buffer) { - if options.flags&HTML_COMPLETE_PAGE == 0 { - return - } - - ending := "" - if options.flags&HTML_USE_XHTML != 0 { - |
