-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhtmlsupport.go
More file actions
161 lines (148 loc) · 4.07 KB
/
htmlsupport.go
File metadata and controls
161 lines (148 loc) · 4.07 KB
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package gotmx
import (
"bufio"
"bytes"
"io"
"golang.org/x/net/html"
)
func getInnerHTML(node *html.Node) (string, error) {
buf := bufferPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufferPool.Put(buf)
w := bufio.NewWriter(buf)
if err := renderInnerHTML(w, node); err != nil {
return "", err
}
if err := w.Flush(); err != nil {
return "", err
}
return buf.String(), nil
}
func renderInnerHTML(writer io.Writer, node *html.Node) error {
for child := node.FirstChild; child != nil; child = child.NextSibling {
switch child.Type {
case html.TextNode:
if _, err := io.WriteString(writer, child.Data); err != nil {
return err
}
case html.ElementNode:
if err := renderElementNode(writer, child); err != nil {
return err
}
default:
// everything else we ignore for now
}
}
return nil
}
func renderElementNode(writer io.Writer, n *html.Node) error {
isVoid, err := renderOpeningTag(writer, n)
if err != nil {
return err
}
if isVoid {
return nil
}
if err = renderInnerHTML(writer, n); err != nil {
return err
}
return renderClosingTag(writer, n)
}
func renderOpeningTag(writer io.Writer, n *html.Node) (bool, error) {
if _, err := io.WriteString(writer, "<"); err != nil {
return false, err
}
if _, err := io.WriteString(writer, n.Data); err != nil {
return false, err
}
if err := renderAttributes(writer, n); err != nil {
return false, err
}
// if the element is a void element (an element that cannot have children), then we can close it now and return
// because there are no children
isVoidElement := voidElements[n.Data]
if isVoidElement {
if n.FirstChild != nil {
return true, &VoidElementChildError{Element: n.Data}
}
_, err := io.WriteString(writer, " />")
return true, err
}
_, err := io.WriteString(writer, ">")
if err != nil {
return false, err
}
return false, nil
}
// ----------------------------------------------------------------------------------------------------------------------
func renderClosingTag(writer io.Writer, n *html.Node) error {
if _, err := io.WriteString(writer, "</"); err != nil {
return err
}
if _, err := io.WriteString(writer, n.Data); err != nil {
return err
}
_, err := io.WriteString(writer, ">")
return err
}
func renderAttributes(writer io.Writer, node *html.Node) error {
for _, a := range node.Attr {
if _, err := io.WriteString(writer, " "); err != nil {
return err
}
if _, err := io.WriteString(writer, a.Key); err != nil {
return err
}
if _, err := io.WriteString(writer, `="`); err != nil {
return err
}
if _, err := io.WriteString(writer, a.Val); err != nil {
return err
}
if _, err := io.WriteString(writer, `"`); err != nil {
return err
}
}
return nil
}
// Section 12.1.2, "Elements", gives this list of void elements. Void elements
// are those that can't have any contents.
// Note: copied from golang html package
var voidElements = map[string]bool{
"area": true,
"base": true,
"br": true,
"col": true,
"embed": true,
"hr": true,
"img": true,
"input": true,
"keygen": true, // "keygen" has been removed from the spec, but are kept here for backwards compatibility.
"link": true,
"meta": true,
"param": true,
"source": true,
"track": true,
"wbr": true,
}
// Note: copied from golang html package
func childTextNodesAreLiteral(n *html.Node) bool {
// Per WHATWG HTML 13.3, if the parent of the current node is a style,
// script, xmp, iframe, noembed, noframes, or plaintext element, and the
// current node is a text node, append the value of the node's data
// literally. The specification is not explicit about it, but we only
// enforce this if we are in the HTML namespace (i.e. when the namespace is
// "").
// NOTE: we also always include noscript elements, although the
// specification states that they should only be rendered as such if
// scripting is enabled for the node (which is not something we track).
if n.Namespace != "" {
return false
}
switch n.Data {
case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp":
return true
default:
return false
}
}