-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrender.go
113 lines (102 loc) · 2.58 KB
/
render.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package html
import (
"context"
"fmt"
"io"
"reflect"
"strings"
"time"
"github.com/sym01/htmlsanitizer"
)
type (
RenderedHTML interface{ io.WriterTo }
HTML interface {
Render(context.Context) RenderedHTML
}
contextWriter struct {
writer io.Writer
context context.Context
}
ContextWriter interface {
io.Writer
context.Context
}
)
var _ ContextWriter = (*contextWriter)(nil)
func (w *contextWriter) Write(p []byte) (int, error) { return w.writer.Write(p) }
func (w *contextWriter) Value(key interface{}) interface{} { return w.context.Value(key) }
func (w *contextWriter) Deadline() (deadline time.Time, ok bool) { return w.context.Deadline() }
func (w *contextWriter) Done() <-chan struct{} { return w.context.Done() }
func (w *contextWriter) Err() error { return w.context.Err() }
func RenderContext(w io.Writer, c context.Context, child any) (int64, error) {
return Render(&contextWriter{writer: w, context: c}, child)
}
var htmlEscaper = strings.NewReplacer(
`&`, "&",
`'`, "'", // "'" is shorter than "'" and apos was not in HTML until HTML5.
`<`, "<",
`>`, ">",
`"`, """, // """ is shorter than """.
)
func Render(w io.Writer, child any) (int64, error) {
if child == nil {
return 0, nil
}
cw, ok := w.(ContextWriter)
if !ok {
cw = &contextWriter{writer: w, context: context.Background()}
}
switch child := child.(type) {
case Fragment:
nn := int64(0)
for _, child := range child {
n, err := Render(cw, child)
nn += n
if err != nil {
return nn, err
}
}
return nn, nil
case RenderedHTML:
return child.WriteTo(cw)
case HTML:
if child := child.Render(cw); child != nil {
return child.WriteTo(cw)
} else {
return 0, nil
}
case func() any:
return Render(cw, child())
case func(context.Context) any:
return Render(cw, child(cw))
case string:
n, err := htmlEscaper.WriteString(htmlsanitizer.NewWriter(cw), child)
return int64(n), err
case io.Reader:
return io.Copy(htmlsanitizer.NewWriter(cw), child)
default:
ty := reflect.ValueOf(child)
if ty.Kind() == reflect.Slice {
if ty.IsNil() {
return 0, nil
}
nn := int64(0)
for i := 0; i < ty.Len(); i++ {
n, err := Render(cw, ty.Index(i).Interface())
nn += n
if err != nil {
return nn, err
}
}
return nn, nil
} else if ty.Kind() == reflect.Pointer {
if ty.IsNil() {
return 0, nil
}
return Render(cw, ty.Elem().Interface())
} else {
n, err := fmt.Fprint(htmlsanitizer.NewWriter(cw), child)
return int64(n), err
}
}
}