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(``) +} + +func (e *Element) Render() string { + var builder strings.Builder + e.RenderTo(&builder) + return builder.String() +} - return `<` + e.Tag + props + `>` + content + `` +func NewElement(tag string, attrs Attrs, children ...interface{}) *Element { + return &Element{ + Tag: tag, + Attrs: attrs, + Children: children, + } }