From 931ee3a2ae105d278da53d8129d4de38e8bff7e9 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Thu, 12 Oct 2023 07:41:30 -0700 Subject: [PATCH 1/4] Move void elements map --- elem.go | 59 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/elem.go b/elem.go index ebd55de..e1d71a8 100644 --- a/elem.go +++ b/elem.go @@ -4,6 +4,28 @@ import ( "sort" ) +// 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, +} + type Attrs map[string]string type Element struct { @@ -12,36 +34,7 @@ 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, - } // Sort the keys for consistent order keys := make([]string, 0, len(e.Attrs)) @@ -72,3 +65,11 @@ func (e *Element) Render() string { return `<` + e.Tag + props + `>` + content + `` } + +func NewElement(tag string, attrs Attrs, children ...interface{}) *Element { + return &Element{ + Tag: tag, + Attrs: attrs, + Children: children, + } +} From 73a2e5c0b725f6ac852ed867615248472b1ec174 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Thu, 12 Oct 2023 07:54:25 -0700 Subject: [PATCH 2/4] Replace the map value type for space efficiency --- elem.go | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/elem.go b/elem.go index e1d71a8..f6fa113 100644 --- a/elem.go +++ b/elem.go @@ -7,23 +7,23 @@ import ( // 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, +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 @@ -59,8 +59,11 @@ func (e *Element) Render() string { } // 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 + if content == "" { + _, exists := voidElements[e.Tag] + if exists { + return `<` + e.Tag + props + `>` // No closing tag for void elements + } } return `<` + e.Tag + props + `>` + content + `` From 81b497a5562b9fd7efd3d8b808cadcf496573ab5 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Fri, 13 Oct 2023 08:39:50 -0700 Subject: [PATCH 3/4] Change to string builder --- elem.go | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/elem.go b/elem.go index f6fa113..13d1b53 100644 --- a/elem.go +++ b/elem.go @@ -2,6 +2,7 @@ package elem import ( "sort" + "strings" ) // List of HTML5 void elements. Void elements, also known as self-closing or empty elements, @@ -35,6 +36,11 @@ type Element struct { } func (e *Element) Render() string { + var 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)) @@ -43,30 +49,40 @@ 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 builder.String() } - 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() + builder.WriteString(c.Render()) } } - // Check if the tag is a void element and doesn't have any content - if content == "" { - _, exists := voidElements[e.Tag] - if exists { - return `<` + e.Tag + props + `>` // No closing tag for void elements - } - } + // Append closing tag + builder.WriteString(``) - return `<` + e.Tag + props + `>` + content + `` + return builder.String() } func NewElement(tag string, attrs Attrs, children ...interface{}) *Element { From b5ab6deaa00b75cc9d077bfb27d19ab584d1f822 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Fri, 13 Oct 2023 08:46:37 -0700 Subject: [PATCH 4/4] Pass the builder in RenderTo --- elem.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/elem.go b/elem.go index 13d1b53..b42249b 100644 --- a/elem.go +++ b/elem.go @@ -35,9 +35,7 @@ type Element struct { Children []interface{} // Can be either string (for text) or another Element } -func (e *Element) Render() string { - var builder strings.Builder - +func (e *Element) RenderTo(builder *strings.Builder) { // Start with opening tag builder.WriteString("<") builder.WriteString(e.Tag) @@ -61,7 +59,7 @@ func (e *Element) Render() string { // If it's a void element, close it and return if _, exists := voidElements[e.Tag]; exists { builder.WriteString(`>`) - return builder.String() + return } // Close opening tag @@ -73,7 +71,7 @@ func (e *Element) Render() string { case string: builder.WriteString(c) case *Element: - builder.WriteString(c.Render()) + c.RenderTo(builder) } } @@ -81,7 +79,11 @@ func (e *Element) Render() string { builder.WriteString(``) +} +func (e *Element) Render() string { + var builder strings.Builder + e.RenderTo(&builder) return builder.String() }