1
0
mirror of https://github.com/danog/blackfriday.git synced 2025-01-23 05:41:27 +01:00

Clean up Renderer interface: remove all callbacks

We now expose the structure of the document in the form of AST, there's
no longer need to have all those callbacks to represent structure.
This commit is contained in:
Vytautas Šaltenis 2016-04-11 11:22:38 +03:00
parent c9ea588e6f
commit c207eca993
2 changed files with 0 additions and 634 deletions

592
html.go
View File

@ -21,7 +21,6 @@ import (
"html"
"io"
"regexp"
"strconv"
"strings"
)
@ -202,597 +201,6 @@ func (r *HTML) entityEscapeWithSkip(src []byte, skipRanges [][]int) {
r.attrEscape(src[end:])
}
func (r *HTML) TitleBlock(text []byte) {
text = bytes.TrimPrefix(text, []byte("% "))
text = bytes.Replace(text, []byte("\n% "), []byte("\n"), -1)
r.w.WriteString("<h1 class=\"title\">")
r.w.Write(text)
r.w.WriteString("\n</h1>")
}
func (r *HTML) BeginHeader(level int, id string) {
r.w.Newline()
if id == "" && r.Extensions&TOC != 0 {
id = fmt.Sprintf("toc_%d", r.headerCount)
}
if id != "" {
id = r.ensureUniqueHeaderID(id)
if r.HeaderIDPrefix != "" {
id = r.HeaderIDPrefix + id
}
if r.HeaderIDSuffix != "" {
id = id + r.HeaderIDSuffix
}
r.w.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, id))
} else {
r.w.WriteString(fmt.Sprintf("<h%d>", level))
}
}
func (r *HTML) EndHeader(level int, id string, header []byte) {
// are we building a table of contents?
if r.Extensions&TOC != 0 {
r.TocHeaderWithAnchor(header, level, id)
}
r.w.WriteString(fmt.Sprintf("</h%d>\n", level))
}
func (r *HTML) BlockHtml(text []byte) {
if r.Flags&SkipHTML != 0 {
return
}
r.w.Newline()
r.w.Write(text)
r.w.WriteByte('\n')
}
func (r *HTML) HRule() {
r.w.Newline()
r.w.WriteString("<hr")
r.w.WriteString(r.closeTag)
r.w.WriteByte('\n')
}
func (r *HTML) BlockCode(text []byte, lang string) {
r.w.Newline()
// 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 {
r.w.WriteString("<pre><code class=\"language-")
} else {
r.w.WriteByte(' ')
}
r.attrEscape([]byte(elt))
count++
}
if count == 0 {
r.w.WriteString("<pre><code>")
} else {
r.w.WriteString("\">")
}
r.attrEscape(text)
r.w.WriteString("</code></pre>\n")
}
func (r *HTML) BlockQuote(text []byte) {
r.w.Newline()
r.w.WriteString("<blockquote>\n")
r.w.Write(text)
r.w.WriteString("</blockquote>\n")
}
func (r *HTML) Table(header []byte, body []byte, columnData []CellAlignFlags) {
r.w.Newline()
r.w.WriteString("<table>\n<thead>\n")
r.w.Write(header)
r.w.WriteString("</thead>\n\n<tbody>\n")
r.w.Write(body)
r.w.WriteString("</tbody>\n</table>\n")
}
func (r *HTML) TableRow(text []byte) {
r.w.Newline()
r.w.WriteString("<tr>\n")
r.w.Write(text)
r.w.WriteString("\n</tr>\n")
}
func leadingNewline(out *bytes.Buffer) {
if out.Len() > 0 {
out.WriteByte('\n')
}
}
func (r *HTML) TableHeaderCell(out *bytes.Buffer, text []byte, align CellAlignFlags) {
leadingNewline(out)
switch align {
case TableAlignmentLeft:
out.WriteString("<th align=\"left\">")
case TableAlignmentRight:
out.WriteString("<th align=\"right\">")
case TableAlignmentCenter:
out.WriteString("<th align=\"center\">")
default:
out.WriteString("<th>")
}
out.Write(text)
out.WriteString("</th>")
}
func (r *HTML) TableCell(out *bytes.Buffer, text []byte, align CellAlignFlags) {
leadingNewline(out)
switch align {
case TableAlignmentLeft:
out.WriteString("<td align=\"left\">")
case TableAlignmentRight:
out.WriteString("<td align=\"right\">")
case TableAlignmentCenter:
out.WriteString("<td align=\"center\">")
default:
out.WriteString("<td>")
}
out.Write(text)
out.WriteString("</td>")
}
func (r *HTML) BeginFootnotes() {
r.w.WriteString("<div class=\"footnotes\">\n")
r.HRule()
r.BeginList(ListTypeOrdered)
}
func (r *HTML) EndFootnotes() {
r.EndList(ListTypeOrdered)
r.w.WriteString("</div>\n")
}
func (r *HTML) FootnoteItem(name, text []byte, flags ListType) {
if flags&ListItemContainsBlock != 0 || flags&ListItemBeginningOfList != 0 {
r.w.Newline()
}
slug := slugify(name)
r.w.WriteString(`<li id="`)
r.w.WriteString(`fn:`)
r.w.WriteString(r.FootnoteAnchorPrefix)
r.w.Write(slug)
r.w.WriteString(`">`)
r.w.Write(text)
if r.Flags&FootnoteReturnLinks != 0 {
r.w.WriteString(` <a class="footnote-return" href="#`)
r.w.WriteString(`fnref:`)
r.w.WriteString(r.FootnoteAnchorPrefix)
r.w.Write(slug)
r.w.WriteString(`">`)
r.w.WriteString(r.FootnoteReturnLinkContents)
r.w.WriteString(`</a>`)
}
r.w.WriteString("</li>\n")
}
func (r *HTML) BeginList(flags ListType) {
r.w.Newline()
if flags&ListTypeDefinition != 0 {
r.w.WriteString("<dl>")
} else if flags&ListTypeOrdered != 0 {
r.w.WriteString("<ol>")
} else {
r.w.WriteString("<ul>")
}
}
func (r *HTML) EndList(flags ListType) {
if flags&ListTypeDefinition != 0 {
r.w.WriteString("</dl>\n")
} else if flags&ListTypeOrdered != 0 {
r.w.WriteString("</ol>\n")
} else {
r.w.WriteString("</ul>\n")
}
}
func (r *HTML) ListItem(text []byte, flags ListType) {
if (flags&ListItemContainsBlock != 0 && flags&ListTypeDefinition == 0) ||
flags&ListItemBeginningOfList != 0 {
r.w.Newline()
}
if flags&ListTypeTerm != 0 {
r.w.WriteString("<dt>")
} else if flags&ListTypeDefinition != 0 {
r.w.WriteString("<dd>")
} else {
r.w.WriteString("<li>")
}
r.w.Write(text)
if flags&ListTypeTerm != 0 {
r.w.WriteString("</dt>\n")
} else if flags&ListTypeDefinition != 0 {
r.w.WriteString("</dd>\n")
} else {
r.w.WriteString("</li>\n")
}
}
func (r *HTML) BeginParagraph() {
r.w.Newline()
r.w.WriteString("<p>")
}
func (r *HTML) EndParagraph() {
r.w.WriteString("</p>\n")
}
func (r *HTML) AutoLink(link []byte, kind LinkType) {
skipRanges := htmlEntity.FindAllIndex(link, -1)
if r.Flags&Safelink != 0 && !isSafeLink(link) && kind != LinkTypeEmail {
// mark it but don't link it if it is not a safe link: no smartypants
r.w.WriteString("<tt>")
r.entityEscapeWithSkip(link, skipRanges)
r.w.WriteString("</tt>")
return
}
r.w.WriteString("<a href=\"")
if kind == LinkTypeEmail {
r.w.WriteString("mailto:")
} else {
r.maybeWriteAbsolutePrefix(link)
}
r.entityEscapeWithSkip(link, skipRanges)
var relAttrs []string
if r.Flags&NofollowLinks != 0 && !isRelativeLink(link) {
relAttrs = append(relAttrs, "nofollow")
}
if r.Flags&NoreferrerLinks != 0 && !isRelativeLink(link) {
relAttrs = append(relAttrs, "noreferrer")
}
if len(relAttrs) > 0 {
r.w.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
}
// blank target only add to external link
if r.Flags&HrefTargetBlank != 0 && !isRelativeLink(link) {
r.w.WriteString("\" target=\"_blank")
}
r.w.WriteString("\">")
// 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://")):
r.attrEscape(link[len("mailto://"):])
case bytes.HasPrefix(link, []byte("mailto:")):
r.attrEscape(link[len("mailto:"):])
default:
r.entityEscapeWithSkip(link, skipRanges)
}
r.w.WriteString("</a>")
}
func (r *HTML) CodeSpan(text []byte) {
r.w.WriteString("<code>")
r.attrEscape(text)
r.w.WriteString("</code>")
}
func (r *HTML) DoubleEmphasis(text []byte) {
r.w.WriteString("<strong>")
r.w.Write(text)
r.w.WriteString("</strong>")
}
func (r *HTML) Emphasis(text []byte) {
if len(text) == 0 {
return
}
r.w.WriteString("<em>")
r.w.Write(text)
r.w.WriteString("</em>")
}
func (r *HTML) maybeWriteAbsolutePrefix(link []byte) {
if r.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
r.w.WriteString(r.AbsolutePrefix)
if link[0] != '/' {
r.w.WriteByte('/')
}
}
}
func (r *HTML) Image(link []byte, title []byte, alt []byte) {
if r.Flags&SkipImages != 0 {
return
}
r.w.WriteString("<img src=\"")
r.maybeWriteAbsolutePrefix(link)
r.attrEscape(link)
r.w.WriteString("\" alt=\"")
if len(alt) > 0 {
r.attrEscape(alt)
}
if len(title) > 0 {
r.w.WriteString("\" title=\"")
r.attrEscape(title)
}
r.w.WriteByte('"')
r.w.WriteString(r.closeTag)
}
func (r *HTML) LineBreak() {
r.w.WriteString("<br")
r.w.WriteString(r.closeTag)
r.w.WriteByte('\n')
}
func (r *HTML) Link(link []byte, title []byte, content []byte) {
if r.Flags&SkipLinks != 0 {
// write the link text out but don't link it, just mark it with typewriter font
r.w.WriteString("<tt>")
r.attrEscape(content)
r.w.WriteString("</tt>")
return
}
if r.Flags&Safelink != 0 && !isSafeLink(link) {
// write the link text out but don't link it, just mark it with typewriter font
r.w.WriteString("<tt>")
r.attrEscape(content)
r.w.WriteString("</tt>")
return
}
r.w.WriteString("<a href=\"")
r.maybeWriteAbsolutePrefix(link)
r.attrEscape(link)
if len(title) > 0 {
r.w.WriteString("\" title=\"")
r.attrEscape(title)
}
var relAttrs []string
if r.Flags&NofollowLinks != 0 && !isRelativeLink(link) {
relAttrs = append(relAttrs, "nofollow")
}
if r.Flags&NoreferrerLinks != 0 && !isRelativeLink(link) {
relAttrs = append(relAttrs, "noreferrer")
}
if len(relAttrs) > 0 {
r.w.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
}
// blank target only add to external link
if r.Flags&HrefTargetBlank != 0 && !isRelativeLink(link) {
r.w.WriteString("\" target=\"_blank")
}
r.w.WriteString("\">")
r.w.Write(content)
r.w.WriteString("</a>")
return
}
func (r *HTML) RawHtmlTag(text []byte) {
if r.Flags&SkipHTML != 0 {
return
}
if r.Flags&SkipStyle != 0 && isHtmlTag(text, "style") {
return
}
if r.Flags&SkipLinks != 0 && isHtmlTag(text, "a") {
return
}
if r.Flags&SkipImages != 0 && isHtmlTag(text, "img") {
return
}
r.w.Write(text)
}
func (r *HTML) TripleEmphasis(text []byte) {
r.w.WriteString("<strong><em>")
r.w.Write(text)
r.w.WriteString("</em></strong>")
}
func (r *HTML) StrikeThrough(text []byte) {
r.w.WriteString("<del>")
r.w.Write(text)
r.w.WriteString("</del>")
}
func (r *HTML) FootnoteRef(ref []byte, id int) {
slug := slugify(ref)
r.w.WriteString(`<sup class="footnote-ref" id="`)
r.w.WriteString(`fnref:`)
r.w.WriteString(r.FootnoteAnchorPrefix)
r.w.Write(slug)
r.w.WriteString(`"><a rel="footnote" href="#`)
r.w.WriteString(`fn:`)
r.w.WriteString(r.FootnoteAnchorPrefix)
r.w.Write(slug)
r.w.WriteString(`">`)
r.w.WriteString(strconv.Itoa(id))
r.w.WriteString(`</a></sup>`)
}
func (r *HTML) Entity(entity []byte) {
r.w.Write(entity)
}
func (r *HTML) NormalText(text []byte) {
if r.Extensions&Smartypants != 0 {
r.Smartypants(text)
} else {
r.attrEscape(text)
}
}
func (r *HTML) Smartypants(text []byte) {
r.w.Write(NewSmartypantsRenderer(r.Extensions).Process(text))
}
func (r *HTML) DocumentHeader() {
if r.Flags&CompletePage == 0 {
return
}
ending := ""
if r.Flags&UseXHTML != 0 {
r.w.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
r.w.WriteString("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
r.w.WriteString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
ending = " /"
} else {
r.w.WriteString("<!DOCTYPE html>\n")
r.w.WriteString("<html>\n")
}
r.w.WriteString("<head>\n")
r.w.WriteString(" <title>")
r.NormalText([]byte(r.Title))
r.w.WriteString("</title>\n")
r.w.WriteString(" <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
r.w.WriteString(VERSION)
r.w.WriteString("\"")
r.w.WriteString(ending)
r.w.WriteString(">\n")
r.w.WriteString(" <meta charset=\"utf-8\"")
r.w.WriteString(ending)
r.w.WriteString(">\n")
if r.CSS != "" {
r.w.WriteString(" <link rel=\"stylesheet\" type=\"text/css\" href=\"")
r.attrEscape([]byte(r.CSS))
r.w.WriteString("\"")
r.w.WriteString(ending)
r.w.WriteString(">\n")
}
r.w.WriteString("</head>\n")
r.w.WriteString("<body>\n")
r.tocMarker = r.w.Len() // XXX
}
func (r *HTML) DocumentFooter() {
// finalize and insert the table of contents
if r.Extensions&TOC != 0 {
r.TocFinalize()
// now we have to insert the table of contents into the document
var temp bytes.Buffer
// start by making a copy of everything after the document header
temp.Write(r.w.Bytes()[r.tocMarker:])
// now clear the copied material from the main output buffer
r.w.Truncate(r.tocMarker)
// corner case spacing issue
if r.Flags&CompletePage != 0 {
r.w.WriteByte('\n')
}
// insert the table of contents
r.w.WriteString("<nav>\n")
r.w.Write(r.toc.Bytes())
r.w.WriteString("</nav>\n")
// corner case spacing issue
if r.Flags&CompletePage == 0 && r.Extensions&OmitContents == 0 {
r.w.WriteByte('\n')
}
// write out everything that came after it
if r.Extensions&OmitContents == 0 {
r.w.Write(temp.Bytes())
}
}
if r.Flags&CompletePage != 0 {
r.w.WriteString("\n</body>\n")
r.w.WriteString("</html>\n")
}
}
func (r *HTML) TocHeaderWithAnchor(text []byte, level int, anchor string) {
for level > r.currentLevel {
switch {
case bytes.HasSuffix(r.toc.Bytes(), []byte("</li>\n")):
// this sublist can nest underneath a header
size := r.toc.Len()
r.toc.Truncate(size - len("</li>\n"))
case r.currentLevel > 0:
r.toc.WriteString("<li>")
}
if r.toc.Len() > 0 {
r.toc.WriteByte('\n')
}
r.toc.WriteString("<ul>\n")
r.currentLevel++
}
for level < r.currentLevel {
r.toc.WriteString("</ul>")
if r.currentLevel > 1 {
r.toc.WriteString("</li>\n")
}
r.currentLevel--
}
r.toc.WriteString("<li><a href=\"#")
if anchor != "" {
r.toc.WriteString(anchor)
} else {
r.toc.WriteString("toc_")
r.toc.WriteString(strconv.Itoa(r.headerCount))
}
r.toc.WriteString("\">")
r.headerCount++
r.toc.Write(text)
r.toc.WriteString("</a></li>\n")
}
func (r *HTML) TocHeader(text []byte, level int) {
r.TocHeaderWithAnchor(text, level, "")
}
func (r *HTML) TocFinalize() {
for r.currentLevel > 1 {
r.toc.WriteString("</ul></li>\n")
r.currentLevel--
}
if r.currentLevel > 0 {
r.toc.WriteString("</ul>\n")
}
}
func isHtmlTag(tag []byte, tagname string) bool {
found, _ := findHtmlTagPos(tag, tagname)
return found

View File

@ -170,48 +170,6 @@ var blockTags = map[string]struct{}{
//
// Currently Html and Latex implementations are provided
type Renderer interface {
// block-level callbacks
BlockCode(text []byte, lang string)
BlockQuote(text []byte)
BlockHtml(text []byte)
BeginHeader(level int, id string)
EndHeader(level int, id string, header []byte)
HRule()
BeginList(flags ListType)
EndList(flags ListType)
ListItem(text []byte, flags ListType)
BeginParagraph()
EndParagraph()
Table(header []byte, body []byte, columnData []CellAlignFlags)
TableRow(text []byte)
TableHeaderCell(out *bytes.Buffer, text []byte, flags CellAlignFlags)
TableCell(out *bytes.Buffer, text []byte, flags CellAlignFlags)
BeginFootnotes()
EndFootnotes()
FootnoteItem(name, text []byte, flags ListType)
TitleBlock(text []byte)
// Span-level callbacks
AutoLink(link []byte, kind LinkType)
CodeSpan(text []byte)
DoubleEmphasis(text []byte)
Emphasis(text []byte)
Image(link []byte, title []byte, alt []byte)
LineBreak()
Link(link []byte, title []byte, content []byte)
RawHtmlTag(tag []byte)
TripleEmphasis(text []byte)
StrikeThrough(text []byte)
FootnoteRef(ref []byte, id int)
// Low-level callbacks
Entity(entity []byte)
NormalText(text []byte)
// Header and footer
DocumentHeader()
DocumentFooter()
Render(ast *Node) []byte
}