Improved performance
This commit is contained in:
101
Render.go
101
Render.go
@ -10,8 +10,9 @@ var (
|
||||
headerEnd = []string{"</h1>", "</h2>", "</h3>", "</h4>", "</h5>", "</h6>"}
|
||||
)
|
||||
|
||||
// renderer represents a Markdown to HTML renderer.
|
||||
type renderer struct {
|
||||
out strings.Builder
|
||||
out []byte
|
||||
paragraphLevel int
|
||||
quoteLevel int
|
||||
listLevel int
|
||||
@ -29,15 +30,17 @@ func Render(markdown string) string {
|
||||
lineStart = 0
|
||||
)
|
||||
|
||||
r.out = make([]byte, 0, nextPowerOf2(uint32(len(markdown)+4)))
|
||||
|
||||
for {
|
||||
if i > len(markdown) {
|
||||
r.closeAll()
|
||||
|
||||
for range r.quoteLevel {
|
||||
r.out.WriteString("</blockquote>")
|
||||
r.WriteString("</blockquote>")
|
||||
}
|
||||
|
||||
return r.out.String()
|
||||
return string(r.out)
|
||||
}
|
||||
|
||||
if i != len(markdown) && markdown[i] != '\n' {
|
||||
@ -56,15 +59,15 @@ func Render(markdown string) string {
|
||||
func (r *renderer) processLine(line string) {
|
||||
if r.inCodeBlock {
|
||||
if strings.HasPrefix(line, "```") {
|
||||
r.out.WriteString("</code></pre>")
|
||||
r.WriteString("</code></pre>")
|
||||
r.inCodeBlock = false
|
||||
r.codeLines = 0
|
||||
} else {
|
||||
if r.codeLines != 0 {
|
||||
r.out.WriteByte('\n')
|
||||
r.WriteByte('\n')
|
||||
}
|
||||
|
||||
r.out.WriteString(html.EscapeString(line))
|
||||
r.WriteString(html.EscapeString(line))
|
||||
r.codeLines++
|
||||
}
|
||||
|
||||
@ -82,13 +85,13 @@ func (r *renderer) processLine(line string) {
|
||||
r.closeParagraphs()
|
||||
|
||||
for range newQuoteLevel - r.quoteLevel {
|
||||
r.out.WriteString("<blockquote>")
|
||||
r.WriteString("<blockquote>")
|
||||
}
|
||||
} else if newQuoteLevel < r.quoteLevel {
|
||||
r.closeParagraphs()
|
||||
|
||||
for range r.quoteLevel - newQuoteLevel {
|
||||
r.out.WriteString("</blockquote>")
|
||||
r.WriteString("</blockquote>")
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,9 +108,9 @@ func (r *renderer) processLine(line string) {
|
||||
space := strings.IndexByte(line, ' ')
|
||||
|
||||
if space > 0 && space <= 6 {
|
||||
r.out.WriteString(headerStart[space-1])
|
||||
r.WriteString(headerStart[space-1])
|
||||
r.writeText(line[space+1:])
|
||||
r.out.WriteString(headerEnd[space-1])
|
||||
r.WriteString(headerEnd[space-1])
|
||||
}
|
||||
|
||||
return
|
||||
@ -117,13 +120,13 @@ func (r *renderer) processLine(line string) {
|
||||
line = strings.TrimSpace(line[1:])
|
||||
|
||||
if r.listLevel == 0 {
|
||||
r.out.WriteString("<ul>")
|
||||
r.WriteString("<ul>")
|
||||
r.listLevel++
|
||||
}
|
||||
|
||||
r.out.WriteString("<li>")
|
||||
r.WriteString("<li>")
|
||||
r.writeText(line)
|
||||
r.out.WriteString("</li>")
|
||||
r.WriteString("</li>")
|
||||
return
|
||||
|
||||
case '`':
|
||||
@ -132,11 +135,11 @@ func (r *renderer) processLine(line string) {
|
||||
|
||||
if !r.inCodeBlock {
|
||||
if language != "" {
|
||||
r.out.WriteString("<pre><code class=\"language-")
|
||||
r.out.WriteString(html.EscapeString(language))
|
||||
r.out.WriteString("\">")
|
||||
r.WriteString("<pre><code class=\"language-")
|
||||
r.WriteString(html.EscapeString(language))
|
||||
r.WriteString("\">")
|
||||
} else {
|
||||
r.out.WriteString("<pre><code>")
|
||||
r.WriteString("<pre><code>")
|
||||
}
|
||||
|
||||
r.inCodeBlock = true
|
||||
@ -150,7 +153,7 @@ func (r *renderer) processLine(line string) {
|
||||
line = line[1:]
|
||||
|
||||
if r.tableLevel == 0 {
|
||||
r.out.WriteString("<table><thead>")
|
||||
r.WriteString("<table><thead>")
|
||||
r.tableLevel++
|
||||
}
|
||||
|
||||
@ -160,30 +163,30 @@ func (r *renderer) processLine(line string) {
|
||||
pipe := strings.IndexByte(line, '|')
|
||||
|
||||
if pipe == -1 {
|
||||
r.out.WriteString("</tr>")
|
||||
r.WriteString("</tr>")
|
||||
return
|
||||
}
|
||||
|
||||
content := strings.TrimSpace(line[:pipe])
|
||||
|
||||
if strings.HasPrefix(content, "---") {
|
||||
r.out.WriteString("</thead><tbody>")
|
||||
r.WriteString("</thead><tbody>")
|
||||
r.tableHeaderWritten = true
|
||||
return
|
||||
}
|
||||
|
||||
if column == 0 {
|
||||
r.out.WriteString("<tr>")
|
||||
r.WriteString("<tr>")
|
||||
}
|
||||
|
||||
if r.tableHeaderWritten {
|
||||
r.out.WriteString("<td>")
|
||||
r.WriteString("<td>")
|
||||
r.writeText(content)
|
||||
r.out.WriteString("</td>")
|
||||
r.WriteString("</td>")
|
||||
} else {
|
||||
r.out.WriteString("<th>")
|
||||
r.WriteString("<th>")
|
||||
r.writeText(content)
|
||||
r.out.WriteString("</th>")
|
||||
r.WriteString("</th>")
|
||||
}
|
||||
|
||||
line = line[pipe+1:]
|
||||
@ -192,13 +195,13 @@ func (r *renderer) processLine(line string) {
|
||||
}
|
||||
|
||||
if r.paragraphLevel == 0 {
|
||||
r.out.WriteString("<p>")
|
||||
r.WriteString("<p>")
|
||||
r.paragraphLevel++
|
||||
r.writeText(line)
|
||||
return
|
||||
}
|
||||
|
||||
r.out.WriteByte(' ')
|
||||
r.WriteByte(' ')
|
||||
r.writeText(line)
|
||||
}
|
||||
|
||||
@ -212,7 +215,7 @@ func (r *renderer) closeAll() {
|
||||
// closeParagraphs closes open paragraphs.
|
||||
func (r *renderer) closeParagraphs() {
|
||||
for range r.paragraphLevel {
|
||||
r.out.WriteString("</p>")
|
||||
r.WriteString("</p>")
|
||||
}
|
||||
|
||||
r.paragraphLevel = 0
|
||||
@ -221,7 +224,7 @@ func (r *renderer) closeParagraphs() {
|
||||
// closeLists closes open lists.
|
||||
func (r *renderer) closeLists() {
|
||||
for range r.listLevel {
|
||||
r.out.WriteString("</ul>")
|
||||
r.WriteString("</ul>")
|
||||
}
|
||||
|
||||
r.listLevel = 0
|
||||
@ -230,7 +233,7 @@ func (r *renderer) closeLists() {
|
||||
// closeTables closes open tables.
|
||||
func (r *renderer) closeTables() {
|
||||
for range r.tableLevel {
|
||||
r.out.WriteString("</tbody></table>")
|
||||
r.WriteString("</tbody></table>")
|
||||
}
|
||||
|
||||
r.tableLevel = 0
|
||||
@ -253,7 +256,7 @@ func (r *renderer) writeText(markdown string) {
|
||||
|
||||
for {
|
||||
if i == len(markdown) {
|
||||
r.out.WriteString(html.EscapeString(markdown[tokenStart:]))
|
||||
r.WriteString(html.EscapeString(markdown[tokenStart:]))
|
||||
return
|
||||
}
|
||||
|
||||
@ -261,7 +264,7 @@ func (r *renderer) writeText(markdown string) {
|
||||
|
||||
switch c {
|
||||
case '[':
|
||||
r.out.WriteString(html.EscapeString(markdown[tokenStart:i]))
|
||||
r.WriteString(html.EscapeString(markdown[tokenStart:i]))
|
||||
tokenStart = i
|
||||
textStart = i
|
||||
case ']':
|
||||
@ -279,11 +282,11 @@ func (r *renderer) writeText(markdown string) {
|
||||
linkText := markdown[textStart+1 : textEnd]
|
||||
linkURL := markdown[urlStart+1 : i]
|
||||
|
||||
r.out.WriteString("<a href=\"")
|
||||
r.out.WriteString(sanitizeURL(linkURL))
|
||||
r.out.WriteString("\">")
|
||||
r.out.WriteString(html.EscapeString(linkText))
|
||||
r.out.WriteString("</a>")
|
||||
r.WriteString("<a href=\"")
|
||||
r.WriteString(sanitizeURL(linkURL))
|
||||
r.WriteString("\">")
|
||||
r.WriteString(html.EscapeString(linkText))
|
||||
r.WriteString("</a>")
|
||||
|
||||
textStart = -1
|
||||
textEnd = -1
|
||||
@ -307,3 +310,27 @@ func sanitizeURL(linkURL string) string {
|
||||
|
||||
return html.EscapeString(linkURL)
|
||||
}
|
||||
|
||||
// WriteByte adds a single byte to the output.
|
||||
func (r *renderer) WriteByte(b byte) error {
|
||||
r.out = append(r.out, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteString adds a string to the output.
|
||||
func (r *renderer) WriteString(text string) (int, error) {
|
||||
r.out = append(r.out, text...)
|
||||
return len(text), nil
|
||||
}
|
||||
|
||||
// nextPowerOf2 calculates the next 32-bit power of 2.
|
||||
func nextPowerOf2(x uint32) uint32 {
|
||||
x--
|
||||
x |= x >> 1
|
||||
x |= x >> 2
|
||||
x |= x >> 4
|
||||
x |= x >> 8
|
||||
x |= x >> 16
|
||||
x++
|
||||
return x
|
||||
}
|
||||
|
Reference in New Issue
Block a user