Skip to content

Commit

Permalink
Merge pull request #9 from chasefleming/chasefleming/render-perf-impr…
Browse files Browse the repository at this point in the history
…ovements

Performance Improvements
  • Loading branch information
chasefleming authored Oct 13, 2023
2 parents e530f38 + b5ab6de commit a3c2f33
Showing 1 changed file with 62 additions and 40 deletions.
102 changes: 62 additions & 40 deletions elem.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,31 @@ package elem

import (
"sort"
"strings"
)

// List of HTML5 void elements. Void elements, also known as self-closing or empty elements,
// are elements that don't have a closing tag because they can't contain any content.
// For example, the <img> tag cannot wrap text or other tags, it stands alone, so it doesn't have a closing tag.
var voidElements = map[string]struct{}{
"area": {},
"base": {},
"br": {},
"col": {},
"command": {},
"embed": {},
"hr": {},
"img": {},
"input": {},
"keygen": {},
"link": {},
"meta": {},
"param": {},
"source": {},
"track": {},
"wbr": {},
}

type Attrs map[string]string

type Element struct {
Expand All @@ -12,36 +35,10 @@ type Element struct {
Children []interface{} // Can be either string (for text) or another Element
}

func NewElement(tag string, attrs Attrs, children ...interface{}) *Element {
return &Element{
Tag: tag,
Attrs: attrs,
Children: children,
}
}

func (e *Element) Render() string {
// List of HTML5 void elements. Void elements, also known as self-closing or empty elements,
// are elements that don't have a closing tag because they can't contain any content.
// For example, the <img> tag cannot wrap text or other tags, it stands alone, so it doesn't have a closing tag.
var voidElements = map[string]bool{
"area": true,
"base": true,
"br": true,
"col": true,
"command": true,
"embed": true,
"hr": true,
"img": true,
"input": true,
"keygen": true,
"link": true,
"meta": true,
"param": true,
"source": true,
"track": true,
"wbr": true,
}
func (e *Element) RenderTo(builder *strings.Builder) {
// Start with opening tag
builder.WriteString("<")
builder.WriteString(e.Tag)

// Sort the keys for consistent order
keys := make([]string, 0, len(e.Attrs))
Expand All @@ -50,25 +47,50 @@ func (e *Element) Render() string {
}
sort.Strings(keys)

props := ""
// Append the attributes to the builder
for _, k := range keys {
props += ` ` + k + `="` + e.Attrs[k] + `"`
builder.WriteString(` `)
builder.WriteString(k)
builder.WriteString(`="`)
builder.WriteString(e.Attrs[k])
builder.WriteString(`"`)
}

// If it's a void element, close it and return
if _, exists := voidElements[e.Tag]; exists {
builder.WriteString(`>`)
return
}

content := ""
// Close opening tag
builder.WriteString(`>`)

// Build the content (either child text or nested elements)
for _, child := range e.Children {
switch c := child.(type) {
case string:
content += c
builder.WriteString(c)
case *Element:
content += c.Render()
c.RenderTo(builder)
}
}

// Check if the tag is a void element and doesn't have any content
if content == "" && voidElements[e.Tag] {
return `<` + e.Tag + props + `>` // No closing tag for void elements
}
// Append closing tag
builder.WriteString(`</`)
builder.WriteString(e.Tag)
builder.WriteString(`>`)
}

func (e *Element) Render() string {
var builder strings.Builder
e.RenderTo(&builder)
return builder.String()
}

return `<` + e.Tag + props + `>` + content + `</` + e.Tag + `>`
func NewElement(tag string, attrs Attrs, children ...interface{}) *Element {
return &Element{
Tag: tag,
Attrs: attrs,
Children: children,
}
}

0 comments on commit a3c2f33

Please sign in to comment.