1
+ /**
2
+ * @typedef {import('hast').Parent } HastParent
3
+ * @typedef {import('hast').Root } HastRoot
4
+ * @typedef {import('hast').DocType } HastDoctype
5
+ * @typedef {import('hast').Element } HastElement
6
+ * @typedef {import('hast').Text } HastText
7
+ * @typedef {import('hast').Comment } HastComment
8
+ * @typedef {HastParent['children'][number] } HastChild
9
+ * @typedef {HastChild|HastRoot } HastNode
10
+ *
11
+ * @typedef Options
12
+ * @property {boolean } [fragment=false] Whether a DOM fragment should be returned
13
+ * @property {Document } [document] Document interface to use (default: `globalThis.document`)
14
+ * @property {string } [namespace] `namespace` to use to create elements
15
+ *
16
+ * @typedef Context
17
+ * @property {Document } doc
18
+ * @property {boolean } [fragment=false]
19
+ * @property {string } [namespace]
20
+ * @property {string } [impliedNamespace]
21
+ */
22
+
1
23
import { webNamespaces } from 'web-namespaces'
2
24
import { find , html , svg } from 'property-information'
3
25
4
26
/* eslint-env browser */
5
27
6
- function transform ( node , options ) {
28
+ /**
29
+ * @param {HastNode } node
30
+ * @param {Context } [ctx]
31
+ */
32
+ function transform ( node , ctx ) {
7
33
switch ( node . type ) {
8
34
case 'root' :
9
- return root ( node , options )
35
+ return root ( node , ctx )
10
36
case 'text' :
11
- return text ( node , options )
37
+ return text ( node , ctx )
12
38
case 'element' :
13
- return element ( node , options )
39
+ return element ( node , ctx )
14
40
case 'doctype' :
15
- return doctype ( node , options )
41
+ return doctype ( node , ctx )
16
42
case 'comment' :
17
- return comment ( node , options )
43
+ return comment ( node , ctx )
18
44
default :
19
- return element ( node , options )
45
+ return element ( node , ctx )
20
46
}
21
47
}
22
48
23
- // Create a document.
24
- function root ( node , options ) {
25
- const { doc, fragment, namespace : optionsNamespace } = options
49
+ /**
50
+ * Create a document.
51
+ *
52
+ * @param {HastRoot } node
53
+ * @param {Context } ctx
54
+ * @returns {XMLDocument|DocumentFragment|HTMLHtmlElement }
55
+ */
56
+ function root ( node , ctx ) {
57
+ const { doc, fragment, namespace : ctxNamespace } = ctx
26
58
const { children = [ ] } = node
27
59
const { length : childrenLength } = children
28
60
29
- let namespace = optionsNamespace
61
+ let namespace = ctxNamespace
30
62
let rootIsDocument = childrenLength === 0
31
63
32
64
for ( let i = 0 ; i < childrenLength ; i += 1 ) {
33
- const { tagName, properties = { } } = children [ i ]
65
+ const child = children [ i ]
66
+
67
+ if ( child . type === 'element' && child . tagName === 'html' ) {
68
+ const { properties = { } } = child
34
69
35
- if ( tagName === 'html' ) {
36
70
// If we have a root HTML node, we don’t need to render as a fragment.
37
71
rootIsDocument = true
38
72
39
73
// Take namespace of the first child.
40
- if ( typeof optionsNamespace === 'undefined' ) {
41
- namespace = properties . xmlns || webNamespaces . html
74
+ if ( typeof ctxNamespace === 'undefined' ) {
75
+ namespace = String ( properties . xmlns || '' ) || webNamespaces . html
42
76
}
43
77
}
44
78
}
45
79
46
80
// The root node will be a Document, DocumentFragment, or HTMLElement.
81
+ /** @type {XMLDocument|DocumentFragment|HTMLHtmlElement } */
47
82
let result
48
83
49
84
if ( rootIsDocument ) {
@@ -55,14 +90,20 @@ function root(node, options) {
55
90
}
56
91
57
92
return appendAll ( result , children , {
58
- ...options ,
93
+ ...ctx ,
59
94
fragment,
60
95
namespace,
61
96
impliedNamespace : namespace
62
97
} )
63
98
}
64
99
65
- // Create a `doctype`.
100
+ /**
101
+ * Create a `doctype`.
102
+ *
103
+ * @param {HastDoctype } node
104
+ * @param {Context } ctx
105
+ * @returns {DocumentType }
106
+ */
66
107
function doctype ( node , { doc} ) {
67
108
return doc . implementation . createDocumentType (
68
109
node . name || 'html' ,
@@ -71,21 +112,39 @@ function doctype(node, {doc}) {
71
112
)
72
113
}
73
114
74
- // Create a `text`.
115
+ /**
116
+ * Create a `text`.
117
+ *
118
+ * @param {HastText } node
119
+ * @param {Context } ctx
120
+ * @returns {Text }
121
+ */
75
122
function text ( node , { doc} ) {
76
123
return doc . createTextNode ( node . value )
77
124
}
78
125
79
- // Create a `comment`.
126
+ /**
127
+ * Create a `comment`.
128
+ *
129
+ * @param {HastComment } node
130
+ * @param {Context } ctx
131
+ * @returns {Comment }
132
+ */
80
133
function comment ( node , { doc} ) {
81
134
return doc . createComment ( node . value )
82
135
}
83
136
84
- // Create an `element`.
137
+ /**
138
+ * Create an `element`.
139
+ *
140
+ * @param {HastElement } node
141
+ * @param {Context } ctx
142
+ * @returns {Element }
143
+ */
85
144
// eslint-disable-next-line complexity
86
- function element ( node , options ) {
87
- const { namespace, doc} = options
88
- let impliedNamespace = options . impliedNamespace || namespace
145
+ function element ( node , ctx ) {
146
+ const { namespace, doc} = ctx
147
+ let impliedNamespace = ctx . impliedNamespace || namespace
89
148
const {
90
149
tagName = impliedNamespace === webNamespaces . svg ? 'g' : 'div' ,
91
150
properties = { } ,
@@ -147,29 +206,45 @@ function element(node, options) {
147
206
result . removeAttribute ( attribute )
148
207
}
149
208
} else if ( booleanish ) {
150
- result . setAttribute ( attribute , value )
209
+ result . setAttribute ( attribute , String ( value ) )
151
210
} else if ( value === true ) {
152
211
result . setAttribute ( attribute , '' )
153
212
} else if ( value || value === 0 || value === '' ) {
154
- result . setAttribute ( attribute , value )
213
+ result . setAttribute ( attribute , String ( value ) )
155
214
}
156
215
}
157
216
158
- return appendAll ( result , children , { ...options , impliedNamespace} )
217
+ return appendAll ( result , children , { ...ctx , impliedNamespace} )
159
218
}
160
219
161
- // Add all children.
162
- function appendAll ( node , children , options ) {
163
- const childrenLength = children . length
164
-
165
- for ( let i = 0 ; i < childrenLength ; i += 1 ) {
220
+ /**
221
+ * Add all children.
222
+ *
223
+ * @template {Node} N
224
+ * @param {N } node
225
+ * @param {Array.<HastChild> } children
226
+ * @param {Context } ctx
227
+ * @returns {N }
228
+ */
229
+ function appendAll ( node , children , ctx ) {
230
+ let index = - 1
231
+
232
+ while ( ++ index < children . length ) {
166
233
// eslint-disable-next-line unicorn/prefer-dom-node-append
167
- node . appendChild ( transform ( children [ i ] , options ) )
234
+ node . appendChild ( transform ( children [ index ] , ctx ) )
168
235
}
169
236
170
237
return node
171
238
}
172
239
173
- export function toDom ( hast , options = { } ) {
174
- return transform ( hast , { ...options , doc : options . document || document } )
240
+ /**
241
+ * Transform a hast tree to a DOM tree
242
+ *
243
+ * @param {HastNode } node
244
+ * @param {Options } [options]
245
+ * @returns {Node }
246
+ */
247
+ export function toDom ( node , options = { } ) {
248
+ const { document : doc = document , ...rest } = options
249
+ return transform ( node , { doc, ...rest } )
175
250
}
0 commit comments