Remove lots of string literals

Using strings in helper functions causes a lot of string-to-[]byte
allocations. This fix is centered around converging the tag() helper
func to the []byte lingo. In order to do that, a lot of string literals
have moved to global variables, where string to []byte conversion can
happen once.
This commit is contained in:
Vytautas Šaltenis 2016-09-10 12:39:22 +03:00
parent e0fc1a0cb1
commit 31f2685bfe

221
html.go
View File

@ -315,15 +315,19 @@ func appendLanguageAttr(attrs []string, info []byte) []string {
return attrs return attrs
} }
func tag(name string, attrs []string, selfClosing bool) []byte { var (
result := "<" + name gtBytes = []byte{'>'}
if attrs != nil && len(attrs) > 0 { spaceBytes = []byte{' '}
result += " " + strings.Join(attrs, " ") )
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, " ")))
} }
if selfClosing { w.Write(gtBytes)
result += " /" r.lastOutputLen = 1
}
return []byte(result + ">")
} }
func footnoteRef(prefix string, node *Node) []byte { func footnoteRef(prefix string, node *Node) []byte {
@ -397,6 +401,95 @@ func (r *HTMLRenderer) cr(w io.Writer) {
} }
} }
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 headerTagsFromLevel(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 (r *HTMLRenderer) outHRTag(w io.Writer) {
if r.Flags&UseXHTML == 0 {
r.out(w, hrTag)
} else {
r.out(w, hrXHTMLTag)
}
}
// RenderNode is a default renderer of a single node of a syntax tree. For // 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 // 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 // time with entering=false, so that it could know when it's working on an open
@ -420,25 +513,29 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
r.out(w, []byte{'\n'}) r.out(w, []byte{'\n'})
// TODO: make it configurable via out(renderer.softbreak) // TODO: make it configurable via out(renderer.softbreak)
case Hardbreak: case Hardbreak:
r.out(w, tag("br", nil, true)) if r.Flags&UseXHTML == 0 {
r.out(w, brTag)
} else {
r.out(w, brXHTMLTag)
}
r.cr(w) r.cr(w)
case Emph: case Emph:
if entering { if entering {
r.out(w, tag("em", nil, false)) r.out(w, emTag)
} else { } else {
r.out(w, tag("/em", nil, false)) r.out(w, emCloseTag)
} }
case Strong: case Strong:
if entering { if entering {
r.out(w, tag("strong", nil, false)) r.out(w, strongTag)
} else { } else {
r.out(w, tag("/strong", nil, false)) r.out(w, strongCloseTag)
} }
case Del: case Del:
if entering { if entering {
r.out(w, tag("del", nil, false)) r.out(w, delTag)
} else { } else {
r.out(w, tag("/del", nil, false)) r.out(w, delCloseTag)
} }
case HTMLSpan: case HTMLSpan:
if r.Flags&SkipHTML != 0 { if r.Flags&SkipHTML != 0 {
@ -457,9 +554,9 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
dest := node.LinkData.Destination dest := node.LinkData.Destination
if needSkipLink(r.Flags, dest) { if needSkipLink(r.Flags, dest) {
if entering { if entering {
r.out(w, tag("tt", nil, false)) r.out(w, ttTag)
} else { } else {
r.out(w, tag("/tt", nil, false)) r.out(w, ttCloseTag)
} }
} else { } else {
if entering { if entering {
@ -475,12 +572,12 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
if len(node.LinkData.Title) > 0 { if len(node.LinkData.Title) > 0 {
attrs = append(attrs, fmt.Sprintf("title=%q", esc(node.LinkData.Title))) attrs = append(attrs, fmt.Sprintf("title=%q", esc(node.LinkData.Title)))
} }
r.out(w, tag("a", attrs, false)) r.tag(w, aTag, attrs)
} else { } else {
if node.NoteID != 0 { if node.NoteID != 0 {
break break
} }
r.out(w, tag("/a", nil, false)) r.out(w, aCloseTag)
} }
} }
case Image: case Image:
@ -509,9 +606,9 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
} }
} }
case Code: case Code:
r.out(w, tag("code", nil, false)) r.out(w, codeTag)
r.out(w, escCode(node.Literal)) r.out(w, escCode(node.Literal))
r.out(w, tag("/code", nil, false)) r.out(w, codeCloseTag)
case Document: case Document:
break break
case Paragraph: case Paragraph:
@ -530,9 +627,9 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
if node.Parent.Type == BlockQuote && node.Prev == nil { if node.Parent.Type == BlockQuote && node.Prev == nil {
r.cr(w) r.cr(w)
} }
r.out(w, tag("p", attrs, false)) r.out(w, pTag)
} else { } else {
r.out(w, tag("/p", attrs, false)) r.out(w, pCloseTag)
if !(node.Parent.Type == Item && node.Next == nil) { if !(node.Parent.Type == Item && node.Next == nil) {
r.cr(w) r.cr(w)
} }
@ -540,9 +637,9 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
case BlockQuote: case BlockQuote:
if entering { if entering {
r.cr(w) r.cr(w)
r.out(w, tag("blockquote", attrs, false)) r.out(w, blockquoteTag)
} else { } else {
r.out(w, tag("/blockquote", nil, false)) r.out(w, blockquoteCloseTag)
r.cr(w) r.cr(w)
} }
case HTMLBlock: case HTMLBlock:
@ -553,7 +650,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
r.out(w, node.Literal) r.out(w, node.Literal)
r.cr(w) r.cr(w)
case Header: case Header:
tagname := fmt.Sprintf("h%d", node.Level) openTag, closeTag := headerTagsFromLevel(node.Level)
if entering { if entering {
if node.IsTitleblock { if node.IsTitleblock {
attrs = append(attrs, `class="title"`) attrs = append(attrs, `class="title"`)
@ -569,39 +666,42 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
attrs = append(attrs, fmt.Sprintf(`id="%s"`, id)) attrs = append(attrs, fmt.Sprintf(`id="%s"`, id))
} }
r.cr(w) r.cr(w)
r.out(w, tag(tagname, attrs, false)) r.tag(w, openTag, attrs)
} else { } else {
r.out(w, tag("/"+tagname, nil, false)) r.out(w, closeTag)
if !(node.Parent.Type == Item && node.Next == nil) { if !(node.Parent.Type == Item && node.Next == nil) {
r.cr(w) r.cr(w)
} }
} }
case HorizontalRule: case HorizontalRule:
r.cr(w) r.cr(w)
r.out(w, tag("hr", attrs, r.Flags&UseXHTML != 0)) r.outHRTag(w)
r.cr(w) r.cr(w)
case List: case List:
tagName := "ul" openTag := ulTag
closeTag := ulCloseTag
if node.ListFlags&ListTypeOrdered != 0 { if node.ListFlags&ListTypeOrdered != 0 {
tagName = "ol" openTag = olTag
closeTag = olCloseTag
} }
if node.ListFlags&ListTypeDefinition != 0 { if node.ListFlags&ListTypeDefinition != 0 {
tagName = "dl" openTag = dlTag
closeTag = dlCloseTag
} }
if entering { if entering {
if node.IsFootnotesList { if node.IsFootnotesList {
r.out(w, []byte("\n<div class=\"footnotes\">\n\n")) r.out(w, footnotesDivBytes)
r.out(w, tag("hr", attrs, r.Flags&UseXHTML != 0)) r.outHRTag(w)
r.cr(w) r.cr(w)
} }
r.cr(w) r.cr(w)
if node.Parent.Type == Item && node.Parent.Parent.Tight { if node.Parent.Type == Item && node.Parent.Parent.Tight {
r.cr(w) r.cr(w)
} }
r.out(w, tag(tagName, attrs, false)) r.tag(w, openTag[:len(openTag)-1], attrs)
r.cr(w) r.cr(w)
} else { } else {
r.out(w, tag("/"+tagName, nil, false)) r.out(w, closeTag)
//cr(w) //cr(w)
//if node.parent.Type != Item { //if node.parent.Type != Item {
// cr(w) // cr(w)
@ -613,16 +713,19 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
r.cr(w) r.cr(w)
} }
if node.IsFootnotesList { if node.IsFootnotesList {
r.out(w, []byte("\n</div>\n")) r.out(w, footnotesCloseDivBytes)
} }
} }
case Item: case Item:
tagName := "li" openTag := liTag
closeTag := liCloseTag
if node.ListFlags&ListTypeDefinition != 0 { if node.ListFlags&ListTypeDefinition != 0 {
tagName = "dd" openTag = ddTag
closeTag = ddCloseTag
} }
if node.ListFlags&ListTypeTerm != 0 { if node.ListFlags&ListTypeTerm != 0 {
tagName = "dt" openTag = dtTag
closeTag = dtCloseTag
} }
if entering { if entering {
if itemOpenCR(node) { if itemOpenCR(node) {
@ -633,7 +736,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug)) r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug))
break break
} }
r.out(w, tag(tagName, nil, false)) r.out(w, openTag)
} else { } else {
if node.ListData.RefLink != nil { if node.ListData.RefLink != nil {
slug := slugify(node.ListData.RefLink) slug := slugify(node.ListData.RefLink)
@ -641,32 +744,34 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug)) r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug))
} }
} }
r.out(w, tag("/"+tagName, nil, false)) r.out(w, closeTag)
r.cr(w) r.cr(w)
} }
case CodeBlock: case CodeBlock:
attrs = appendLanguageAttr(attrs, node.Info) attrs = appendLanguageAttr(attrs, node.Info)
r.cr(w) r.cr(w)
r.out(w, tag("pre", nil, false)) r.out(w, preTag)
r.out(w, tag("code", attrs, false)) r.tag(w, codeTag[:len(codeTag)-1], attrs)
r.out(w, escCode(node.Literal)) r.out(w, escCode(node.Literal))
r.out(w, tag("/code", nil, false)) r.out(w, codeCloseTag)
r.out(w, tag("/pre", nil, false)) r.out(w, preCloseTag)
if node.Parent.Type != Item { if node.Parent.Type != Item {
r.cr(w) r.cr(w)
} }
case Table: case Table:
if entering { if entering {
r.cr(w) r.cr(w)
r.out(w, tag("table", nil, false)) r.out(w, tableTag)
} else { } else {
r.out(w, tag("/table", nil, false)) r.out(w, tableCloseTag)
r.cr(w) r.cr(w)
} }
case TableCell: case TableCell:
tagName := "td" openTag := tdTag
closeTag := tdCloseTag
if node.IsHeader { if node.IsHeader {
tagName = "th" openTag = thTag
closeTag = thCloseTag
} }
if entering { if entering {
align := cellAlignment(node.Align) align := cellAlignment(node.Align)
@ -676,37 +781,37 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
if node.Prev == nil { if node.Prev == nil {
r.cr(w) r.cr(w)
} }
r.out(w, tag(tagName, attrs, false)) r.tag(w, openTag, attrs)
} else { } else {
r.out(w, tag("/"+tagName, nil, false)) r.out(w, closeTag)
r.cr(w) r.cr(w)
} }
case TableHead: case TableHead:
if entering { if entering {
r.cr(w) r.cr(w)
r.out(w, tag("thead", nil, false)) r.out(w, theadTag)
} else { } else {
r.out(w, tag("/thead", nil, false)) r.out(w, theadCloseTag)
r.cr(w) r.cr(w)
} }
case TableBody: case TableBody:
if entering { if entering {
r.cr(w) r.cr(w)
r.out(w, tag("tbody", nil, false)) r.out(w, tbodyTag)
// XXX: this is to adhere to a rather silly test. Should fix test. // XXX: this is to adhere to a rather silly test. Should fix test.
if node.FirstChild == nil { if node.FirstChild == nil {
r.cr(w) r.cr(w)
} }
} else { } else {
r.out(w, tag("/tbody", nil, false)) r.out(w, tbodyCloseTag)
r.cr(w) r.cr(w)
} }
case TableRow: case TableRow:
if entering { if entering {
r.cr(w) r.cr(w)
r.out(w, tag("tr", nil, false)) r.out(w, trTag)
} else { } else {
r.out(w, tag("/tr", nil, false)) r.out(w, trCloseTag)
r.cr(w) r.cr(w)
} }
default: default: