99 lines
1.8 KiB
Go
99 lines
1.8 KiB
Go
|
|
package html
|
||
|
|
|
||
|
|
import "strings"
|
||
|
|
|
||
|
|
// Node is anything renderable.
|
||
|
|
type Node interface {
|
||
|
|
Render(ctx *Context) string
|
||
|
|
}
|
||
|
|
|
||
|
|
// voidElements is the set of HTML elements that must not have a closing tag.
|
||
|
|
var voidElements = map[string]bool{
|
||
|
|
"area": true,
|
||
|
|
"base": true,
|
||
|
|
"br": true,
|
||
|
|
"col": true,
|
||
|
|
"embed": true,
|
||
|
|
"hr": true,
|
||
|
|
"img": true,
|
||
|
|
"input": true,
|
||
|
|
"link": true,
|
||
|
|
"meta": true,
|
||
|
|
"source": true,
|
||
|
|
"track": true,
|
||
|
|
"wbr": true,
|
||
|
|
}
|
||
|
|
|
||
|
|
// escapeAttr escapes a string for use in an HTML attribute value.
|
||
|
|
func escapeAttr(s string) string {
|
||
|
|
s = strings.ReplaceAll(s, "&", "&")
|
||
|
|
s = strings.ReplaceAll(s, "\"", """)
|
||
|
|
s = strings.ReplaceAll(s, "'", "'")
|
||
|
|
s = strings.ReplaceAll(s, "<", "<")
|
||
|
|
s = strings.ReplaceAll(s, ">", ">")
|
||
|
|
return s
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- rawNode ---
|
||
|
|
|
||
|
|
type rawNode struct {
|
||
|
|
content string
|
||
|
|
}
|
||
|
|
|
||
|
|
// Raw creates a node that renders without escaping (escape hatch for trusted content).
|
||
|
|
func Raw(content string) Node {
|
||
|
|
return &rawNode{content: content}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (n *rawNode) Render(_ *Context) string {
|
||
|
|
return n.content
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- elNode ---
|
||
|
|
|
||
|
|
type elNode struct {
|
||
|
|
tag string
|
||
|
|
children []Node
|
||
|
|
attrs map[string]string
|
||
|
|
}
|
||
|
|
|
||
|
|
// El creates an HTML element node with children.
|
||
|
|
func El(tag string, children ...Node) Node {
|
||
|
|
return &elNode{
|
||
|
|
tag: tag,
|
||
|
|
children: children,
|
||
|
|
attrs: make(map[string]string),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (n *elNode) Render(ctx *Context) string {
|
||
|
|
var b strings.Builder
|
||
|
|
|
||
|
|
b.WriteByte('<')
|
||
|
|
b.WriteString(n.tag)
|
||
|
|
|
||
|
|
for key, val := range n.attrs {
|
||
|
|
b.WriteByte(' ')
|
||
|
|
b.WriteString(key)
|
||
|
|
b.WriteString(`="`)
|
||
|
|
b.WriteString(escapeAttr(val))
|
||
|
|
b.WriteByte('"')
|
||
|
|
}
|
||
|
|
|
||
|
|
b.WriteByte('>')
|
||
|
|
|
||
|
|
if voidElements[n.tag] {
|
||
|
|
return b.String()
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, child := range n.children {
|
||
|
|
b.WriteString(child.Render(ctx))
|
||
|
|
}
|
||
|
|
|
||
|
|
b.WriteString("</")
|
||
|
|
b.WriteString(n.tag)
|
||
|
|
b.WriteByte('>')
|
||
|
|
|
||
|
|
return b.String()
|
||
|
|
}
|