Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions elem.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@ func (n NoneNode) RenderWithOptions(opts RenderOptions) string {
type TextNode string

func (t TextNode) RenderTo(builder *strings.Builder, opts RenderOptions) {
builder.WriteString(string(t))
builder.WriteString(EscapeNodeContents(string(t)))
}

func (t TextNode) Render() string {
return string(t)
return EscapeNodeContents(string(t))
}

func (t TextNode) RenderWithOptions(opts RenderOptions) string {
return string(t)
return EscapeNodeContents(string(t))
}

type RawNode string
Expand Down
3 changes: 2 additions & 1 deletion elements.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ func U(attrs attrs.Props, children ...Node) *Element {
return newElement("u", attrs, children...)
}

// Text creates a TextNode.
// Text creates a TextNode, content is automatically escaped to ensure safe rendering.
// For unescaped HTML, use Raw function instead.
func Text(content string) TextNode {
return TextNode(content)
}
Expand Down
18 changes: 18 additions & 0 deletions elements_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,24 @@ func TestNoneInDiv(t *testing.T) {
assert.Equal(t, expected, actual)
}

func TestText(t *testing.T) {
expected := `<p>Hello, World!</p>`
el := P(nil, Text("Hello, World!"))
assert.Equal(t, expected, el.Render())
}

func TestTextWithEscaping(t *testing.T) {
expected := `<p>Hello, &lt;em&gt;World!&lt;/em&gt;</p>`
el := P(nil, Text("Hello, <em>World!</em>"))
assert.Equal(t, expected, el.Render())
}

func TestTextWithNoQuotesEscaping(t *testing.T) {
expected := `<p>'Hello,' "World!"</p>`
el := P(nil, Text(`'Hello,' "World!"`))
assert.Equal(t, expected, el.Render())
}

func TestRaw(t *testing.T) {
rawHTML := `<div class="test"><p>Test paragraph</p></div>`
el := Raw(rawHTML)
Expand Down
16 changes: 16 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
package elem

import "strings"

// nodeContentReplacer handles escaping of HTML special characters in text nodes
// note that single and double quotes (', ") do not need escaping in HTML5 text nodes
var nodeContentReplacer = strings.NewReplacer(
"&", "&amp;",
"<", "&lt;",
// technically, > does not need escaping in HTML5, but we do it for consistency
">", "&gt;",
)

// If conditionally renders one of the provided elements based on the condition
func If[T any](condition bool, ifTrue, ifFalse T) T {
if condition {
Expand All @@ -16,3 +27,8 @@ func TransformEach[T any](items []T, fn func(T) Node) []Node {
}
return nodes
}

// EscapeNodeContents escapes HTML5 special characters in a string to ensure safe rendering as a text node
func EscapeNodeContents(s string) string {
return nodeContentReplacer.Replace(s)
}