diff --git a/elem.go b/elem.go
index ebd55de..b42249b 100644
--- a/elem.go
+++ b/elem.go
@@ -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 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 {
@@ -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 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))
@@ -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,
+ }
}