1
2
3
4
5 package html
6
7 import (
8 "bufio"
9 "errors"
10 "fmt"
11 "io"
12 "strings"
13 )
14
15 type writer interface {
16 io.Writer
17 io.ByteWriter
18 WriteString(string) (int, error)
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 func Render(w io.Writer, n *Node) error {
46 if x, ok := w.(writer); ok {
47 return render(x, n)
48 }
49 buf := bufio.NewWriter(w)
50 if err := render(buf, n); err != nil {
51 return err
52 }
53 return buf.Flush()
54 }
55
56
57
58 var plaintextAbort = errors.New("html: internal error (plaintext abort)")
59
60 func render(w writer, n *Node) error {
61 err := render1(w, n)
62 if err == plaintextAbort {
63 err = nil
64 }
65 return err
66 }
67
68 func render1(w writer, n *Node) error {
69
70 switch n.Type {
71 case ErrorNode:
72 return errors.New("html: cannot render an ErrorNode node")
73 case TextNode:
74 return escape(w, n.Data)
75 case DocumentNode:
76 for c := n.FirstChild; c != nil; c = c.NextSibling {
77 if err := render1(w, c); err != nil {
78 return err
79 }
80 }
81 return nil
82 case ElementNode:
83
84 case CommentNode:
85 if _, err := w.WriteString("<!--"); err != nil {
86 return err
87 }
88 if err := escapeComment(w, n.Data); err != nil {
89 return err
90 }
91 if _, err := w.WriteString("-->"); err != nil {
92 return err
93 }
94 return nil
95 case DoctypeNode:
96 if _, err := w.WriteString("<!DOCTYPE "); err != nil {
97 return err
98 }
99 if err := escape(w, n.Data); err != nil {
100 return err
101 }
102 if n.Attr != nil {
103 var p, s string
104 for _, a := range n.Attr {
105 switch a.Key {
106 case "public":
107 p = a.Val
108 case "system":
109 s = a.Val
110 }
111 }
112 if p != "" {
113 if _, err := w.WriteString(" PUBLIC "); err != nil {
114 return err
115 }
116 if err := writeQuoted(w, p); err != nil {
117 return err
118 }
119 if s != "" {
120 if err := w.WriteByte(' '); err != nil {
121 return err
122 }
123 if err := writeQuoted(w, s); err != nil {
124 return err
125 }
126 }
127 } else if s != "" {
128 if _, err := w.WriteString(" SYSTEM "); err != nil {
129 return err
130 }
131 if err := writeQuoted(w, s); err != nil {
132 return err
133 }
134 }
135 }
136 return w.WriteByte('>')
137 case RawNode:
138 _, err := w.WriteString(n.Data)
139 return err
140 default:
141 return errors.New("html: unknown node type")
142 }
143
144
145 if err := w.WriteByte('<'); err != nil {
146 return err
147 }
148 if _, err := w.WriteString(n.Data); err != nil {
149 return err
150 }
151 for _, a := range n.Attr {
152 if err := w.WriteByte(' '); err != nil {
153 return err
154 }
155 if a.Namespace != "" {
156 if _, err := w.WriteString(a.Namespace); err != nil {
157 return err
158 }
159 if err := w.WriteByte(':'); err != nil {
160 return err
161 }
162 }
163 if _, err := w.WriteString(a.Key); err != nil {
164 return err
165 }
166 if _, err := w.WriteString(`="`); err != nil {
167 return err
168 }
169 if err := escape(w, a.Val); err != nil {
170 return err
171 }
172 if err := w.WriteByte('"'); err != nil {
173 return err
174 }
175 }
176 if voidElements[n.Data] {
177 if n.FirstChild != nil {
178 return fmt.Errorf("html: void element <%s> has child nodes", n.Data)
179 }
180 _, err := w.WriteString("/>")
181 return err
182 }
183 if err := w.WriteByte('>'); err != nil {
184 return err
185 }
186
187
188 if c := n.FirstChild; c != nil && c.Type == TextNode && strings.HasPrefix(c.Data, "\n") {
189 switch n.Data {
190 case "pre", "listing", "textarea":
191 if err := w.WriteByte('\n'); err != nil {
192 return err
193 }
194 }
195 }
196
197
198 if childTextNodesAreLiteral(n) {
199 for c := n.FirstChild; c != nil; c = c.NextSibling {
200 if c.Type == TextNode {
201 if _, err := w.WriteString(c.Data); err != nil {
202 return err
203 }
204 } else {
205 if err := render1(w, c); err != nil {
206 return err
207 }
208 }
209 }
210 if n.Data == "plaintext" {
211
212
213 return plaintextAbort
214 }
215 } else {
216 for c := n.FirstChild; c != nil; c = c.NextSibling {
217 if err := render1(w, c); err != nil {
218 return err
219 }
220 }
221 }
222
223
224 if _, err := w.WriteString("</"); err != nil {
225 return err
226 }
227 if _, err := w.WriteString(n.Data); err != nil {
228 return err
229 }
230 return w.WriteByte('>')
231 }
232
233 func childTextNodesAreLiteral(n *Node) bool {
234
235
236
237
238
239
240
241
242
243 if n.Namespace != "" {
244 return false
245 }
246 switch n.Data {
247 case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp":
248 return true
249 default:
250 return false
251 }
252 }
253
254
255
256
257
258 func writeQuoted(w writer, s string) error {
259 var q byte = '"'
260 if strings.Contains(s, `"`) {
261 q = '\''
262 }
263 if err := w.WriteByte(q); err != nil {
264 return err
265 }
266 if _, err := w.WriteString(s); err != nil {
267 return err
268 }
269 if err := w.WriteByte(q); err != nil {
270 return err
271 }
272 return nil
273 }
274
275
276
277 var voidElements = map[string]bool{
278 "area": true,
279 "base": true,
280 "br": true,
281 "col": true,
282 "embed": true,
283 "hr": true,
284 "img": true,
285 "input": true,
286 "keygen": true,
287 "link": true,
288 "meta": true,
289 "param": true,
290 "source": true,
291 "track": true,
292 "wbr": true,
293 }
294
View as plain text