@@ -55,7 +55,7 @@ func (n NodeFunc) Render(w io.Writer) error {
55
55
return n (w )
56
56
}
57
57
58
- // Type satisfies nodeTypeDescriber.
58
+ // Type satisfies [ nodeTypeDescriber] .
59
59
func (n NodeFunc ) Type () NodeType {
60
60
return ElementType
61
61
}
@@ -67,6 +67,12 @@ func (n NodeFunc) String() string {
67
67
return b .String ()
68
68
}
69
69
70
+ var (
71
+ lt = []byte ("<" )
72
+ gt = []byte (">" )
73
+ ltSlash = []byte ("</" )
74
+ )
75
+
70
76
// El creates an element DOM [Node] with a name and child Nodes.
71
77
// See https://dev.w3.org/html5/spec-LC/syntax.html#elements-0 for how elements are rendered.
72
78
// No tags are ever omitted from normal tags, even though it's allowed for elements given at
@@ -75,76 +81,99 @@ func (n NodeFunc) String() string {
75
81
// Use this if no convenience creator exists in the html package.
76
82
func El (name string , children ... Node ) Node {
77
83
return NodeFunc (func (w io.Writer ) error {
78
- return render ( w , & name , children ... )
79
- })
80
- }
84
+ var err error
85
+
86
+ sw , ok := w .(io. StringWriter )
81
87
82
- func render (w2 io.Writer , name * string , children ... Node ) error {
83
- w := & statefulWriter {w : w2 }
88
+ if _ , err = w .Write (lt ); err != nil {
89
+ return err
90
+ }
84
91
85
- if name != nil {
86
- w .Write ([]byte ("<" + * name ))
92
+ if ok {
93
+ if _ , err = sw .WriteString (name ); err != nil {
94
+ return err
95
+ }
96
+ } else {
97
+ if _ , err = w .Write ([]byte (name )); err != nil {
98
+ return err
99
+ }
100
+ }
87
101
88
102
for _ , c := range children {
89
- renderChild (w , c , AttributeType )
103
+ if err = renderChild (w , c , AttributeType ); err != nil {
104
+ return err
105
+ }
90
106
}
91
107
92
- w .Write ([]byte (">" ))
108
+ if _ , err = w .Write (gt ); err != nil {
109
+ return err
110
+ }
93
111
94
- if isVoidElement (* name ) {
95
- return w . err
112
+ if isVoidElement (name ) {
113
+ return nil
96
114
}
97
- }
98
115
99
- for _ , c := range children {
100
- renderChild (w , c , ElementType )
101
- }
116
+ for _ , c := range children {
117
+ if err = renderChild (w , c , ElementType ); err != nil {
118
+ return err
119
+ }
120
+ }
102
121
103
- if name != nil {
104
- w .Write ([]byte ("</" + * name + ">" ))
105
- }
122
+ if _ , err = w .Write (ltSlash ); err != nil {
123
+ return err
124
+ }
125
+
126
+ if ok {
127
+ if _ , err = sw .WriteString (name ); err != nil {
128
+ return err
129
+ }
130
+ } else {
131
+ if _ , err = w .Write ([]byte (name )); err != nil {
132
+ return err
133
+ }
134
+ }
106
135
107
- return w .err
136
+ if _ , err = w .Write (gt ); err != nil {
137
+ return err
138
+ }
139
+
140
+ return nil
141
+ })
108
142
}
109
143
110
- // renderChild c to the given writer w if the node type is t .
111
- func renderChild (w * statefulWriter , c Node , t NodeType ) {
112
- if w . err != nil || c == nil {
113
- return
144
+ // renderChild c to the given writer w if the node type is desiredType .
145
+ func renderChild (w io. Writer , c Node , desiredType NodeType ) error {
146
+ if c == nil {
147
+ return nil
114
148
}
115
149
116
150
// Rendering groups like this is still important even though a group can render itself,
117
151
// since otherwise attributes will sometimes be ignored.
118
152
if g , ok := c .(Group ); ok {
119
153
for _ , groupC := range g {
120
- renderChild (w , groupC , t )
154
+ if err := renderChild (w , groupC , desiredType ); err != nil {
155
+ return err
156
+ }
121
157
}
122
- return
158
+ return nil
123
159
}
124
160
125
- switch t {
161
+ switch desiredType {
126
162
case ElementType :
127
- if p , ok := c .(nodeTypeDescriber ); ! ok || p .Type () == ElementType {
128
- w .err = c .Render (w .w )
163
+ if p , ok := c .(nodeTypeDescriber ); ! ok || p .Type () == desiredType {
164
+ if err := c .Render (w ); err != nil {
165
+ return err
166
+ }
129
167
}
130
168
case AttributeType :
131
- if p , ok := c .(nodeTypeDescriber ); ok && p .Type () == AttributeType {
132
- w .err = c .Render (w .w )
169
+ if p , ok := c .(nodeTypeDescriber ); ok && p .Type () == desiredType {
170
+ if err := c .Render (w ); err != nil {
171
+ return err
172
+ }
133
173
}
134
174
}
135
- }
136
175
137
- // statefulWriter only writes if no errors have occurred earlier in its lifetime.
138
- type statefulWriter struct {
139
- w io.Writer
140
- err error
141
- }
142
-
143
- func (w * statefulWriter ) Write (p []byte ) {
144
- if w .err != nil {
145
- return
146
- }
147
- _ , w .err = w .w .Write (p )
176
+ return nil
148
177
}
149
178
150
179
// voidElements don't have end tags and must be treated differently in the rendering.
@@ -173,44 +202,85 @@ func isVoidElement(name string) bool {
173
202
return ok
174
203
}
175
204
205
+ var (
206
+ space = []byte (" " )
207
+ equalQuote = []byte (`="` )
208
+ quote = []byte (`"` )
209
+ )
210
+
176
211
// Attr creates an attribute DOM [Node] with a name and optional value.
177
212
// If only a name is passed, it's a name-only (boolean) attribute (like "required").
178
213
// If a name and value are passed, it's a name-value attribute (like `class="header"`).
179
214
// More than one value make [Attr] panic.
180
215
// Use this if no convenience creator exists in the html package.
181
216
func Attr (name string , value ... string ) Node {
182
- switch len (value ) {
183
- case 0 :
184
- return & attr {name : name }
185
- case 1 :
186
- return & attr {name : name , value : & value [0 ]}
187
- default :
217
+ if len (value ) > 1 {
188
218
panic ("attribute must be just name or name and value pair" )
189
219
}
190
- }
191
220
192
- type attr struct {
193
- name string
194
- value * string
221
+ return attrFunc (func (w io.Writer ) error {
222
+ var err error
223
+
224
+ sw , ok := w .(io.StringWriter )
225
+
226
+ if _ , err = w .Write (space ); err != nil {
227
+ return err
228
+ }
229
+
230
+ // Attribute name
231
+ if ok {
232
+ if _ , err = sw .WriteString (name ); err != nil {
233
+ return err
234
+ }
235
+ } else {
236
+ if _ , err = w .Write ([]byte (name )); err != nil {
237
+ return err
238
+ }
239
+ }
240
+
241
+ if len (value ) == 0 {
242
+ return nil
243
+ }
244
+
245
+ if _ , err = w .Write (equalQuote ); err != nil {
246
+ return err
247
+ }
248
+
249
+ // Attribute value
250
+ if ok {
251
+ if _ , err = sw .WriteString (template .HTMLEscapeString (value [0 ])); err != nil {
252
+ return err
253
+ }
254
+ } else {
255
+ if _ , err = w .Write ([]byte (template .HTMLEscapeString (value [0 ]))); err != nil {
256
+ return err
257
+ }
258
+ }
259
+
260
+ if _ , err = w .Write (quote ); err != nil {
261
+ return err
262
+ }
263
+
264
+ return nil
265
+ })
195
266
}
196
267
268
+ // attrFunc is a render function that is also a [Node] of [AttributeType].
269
+ // It's basically the same as [NodeFunc], but for attributes.
270
+ type attrFunc func (io.Writer ) error
271
+
197
272
// Render satisfies [Node].
198
- func (a * attr ) Render (w io.Writer ) error {
199
- if a .value == nil {
200
- _ , err := w .Write ([]byte (" " + a .name ))
201
- return err
202
- }
203
- _ , err := w .Write ([]byte (" " + a .name + `="` + template .HTMLEscapeString (* a .value ) + `"` ))
204
- return err
273
+ func (a attrFunc ) Render (w io.Writer ) error {
274
+ return a (w )
205
275
}
206
276
207
277
// Type satisfies [nodeTypeDescriber].
208
- func (a * attr ) Type () NodeType {
278
+ func (a attrFunc ) Type () NodeType {
209
279
return AttributeType
210
280
}
211
281
212
282
// String satisfies [fmt.Stringer].
213
- func (a * attr ) String () string {
283
+ func (a attrFunc ) String () string {
214
284
var b strings.Builder
215
285
_ = a .Render (& b )
216
286
return b .String ()
@@ -219,6 +289,10 @@ func (a *attr) String() string {
219
289
// Text creates a text DOM [Node] that Renders the escaped string t.
220
290
func Text (t string ) Node {
221
291
return NodeFunc (func (w io.Writer ) error {
292
+ if w , ok := w .(io.StringWriter ); ok {
293
+ _ , err := w .WriteString (template .HTMLEscapeString (t ))
294
+ return err
295
+ }
222
296
_ , err := w .Write ([]byte (template .HTMLEscapeString (t )))
223
297
return err
224
298
})
@@ -227,6 +301,10 @@ func Text(t string) Node {
227
301
// Textf creates a text DOM [Node] that Renders the interpolated and escaped string format.
228
302
func Textf (format string , a ... interface {}) Node {
229
303
return NodeFunc (func (w io.Writer ) error {
304
+ if w , ok := w .(io.StringWriter ); ok {
305
+ _ , err := w .WriteString (template .HTMLEscapeString (fmt .Sprintf (format , a ... )))
306
+ return err
307
+ }
230
308
_ , err := w .Write ([]byte (template .HTMLEscapeString (fmt .Sprintf (format , a ... ))))
231
309
return err
232
310
})
@@ -235,6 +313,10 @@ func Textf(format string, a ...interface{}) Node {
235
313
// Raw creates a text DOM [Node] that just Renders the unescaped string t.
236
314
func Raw (t string ) Node {
237
315
return NodeFunc (func (w io.Writer ) error {
316
+ if w , ok := w .(io.StringWriter ); ok {
317
+ _ , err := w .WriteString (t )
318
+ return err
319
+ }
238
320
_ , err := w .Write ([]byte (t ))
239
321
return err
240
322
})
@@ -243,6 +325,10 @@ func Raw(t string) Node {
243
325
// Rawf creates a text DOM [Node] that just Renders the interpolated and unescaped string format.
244
326
func Rawf (format string , a ... interface {}) Node {
245
327
return NodeFunc (func (w io.Writer ) error {
328
+ if w , ok := w .(io.StringWriter ); ok {
329
+ _ , err := w .WriteString (fmt .Sprintf (format , a ... ))
330
+ return err
331
+ }
246
332
_ , err := fmt .Fprintf (w , format , a ... )
247
333
return err
248
334
})
@@ -271,7 +357,12 @@ func (g Group) String() string {
271
357
272
358
// Render satisfies [Node].
273
359
func (g Group ) Render (w io.Writer ) error {
274
- return render (w , nil , g ... )
360
+ for _ , c := range g {
361
+ if err := renderChild (w , c , ElementType ); err != nil {
362
+ return err
363
+ }
364
+ }
365
+ return nil
275
366
}
276
367
277
368
// If condition is true, return the given [Node]. Otherwise, return nil.
0 commit comments