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+
123import { webNamespaces } from 'web-namespaces'
224import { find , html , svg } from 'property-information'
325
426/* eslint-env browser */
527
6- function transform ( node , options ) {
28+ /**
29+ * @param {HastNode } node
30+ * @param {Context } [ctx]
31+ */
32+ function transform ( node , ctx ) {
733 switch ( node . type ) {
834 case 'root' :
9- return root ( node , options )
35+ return root ( node , ctx )
1036 case 'text' :
11- return text ( node , options )
37+ return text ( node , ctx )
1238 case 'element' :
13- return element ( node , options )
39+ return element ( node , ctx )
1440 case 'doctype' :
15- return doctype ( node , options )
41+ return doctype ( node , ctx )
1642 case 'comment' :
17- return comment ( node , options )
43+ return comment ( node , ctx )
1844 default :
19- return element ( node , options )
45+ return element ( node , ctx )
2046 }
2147}
2248
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
2658 const { children = [ ] } = node
2759 const { length : childrenLength } = children
2860
29- let namespace = optionsNamespace
61+ let namespace = ctxNamespace
3062 let rootIsDocument = childrenLength === 0
3163
3264 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
3469
35- if ( tagName === 'html' ) {
3670 // If we have a root HTML node, we don’t need to render as a fragment.
3771 rootIsDocument = true
3872
3973 // 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
4276 }
4377 }
4478 }
4579
4680 // The root node will be a Document, DocumentFragment, or HTMLElement.
81+ /** @type {XMLDocument|DocumentFragment|HTMLHtmlElement } */
4782 let result
4883
4984 if ( rootIsDocument ) {
@@ -55,14 +90,20 @@ function root(node, options) {
5590 }
5691
5792 return appendAll ( result , children , {
58- ...options ,
93+ ...ctx ,
5994 fragment,
6095 namespace,
6196 impliedNamespace : namespace
6297 } )
6398}
6499
65- // Create a `doctype`.
100+ /**
101+ * Create a `doctype`.
102+ *
103+ * @param {HastDoctype } node
104+ * @param {Context } ctx
105+ * @returns {DocumentType }
106+ */
66107function doctype ( node , { doc} ) {
67108 return doc . implementation . createDocumentType (
68109 node . name || 'html' ,
@@ -71,21 +112,39 @@ function doctype(node, {doc}) {
71112 )
72113}
73114
74- // Create a `text`.
115+ /**
116+ * Create a `text`.
117+ *
118+ * @param {HastText } node
119+ * @param {Context } ctx
120+ * @returns {Text }
121+ */
75122function text ( node , { doc} ) {
76123 return doc . createTextNode ( node . value )
77124}
78125
79- // Create a `comment`.
126+ /**
127+ * Create a `comment`.
128+ *
129+ * @param {HastComment } node
130+ * @param {Context } ctx
131+ * @returns {Comment }
132+ */
80133function comment ( node , { doc} ) {
81134 return doc . createComment ( node . value )
82135}
83136
84- // Create an `element`.
137+ /**
138+ * Create an `element`.
139+ *
140+ * @param {HastElement } node
141+ * @param {Context } ctx
142+ * @returns {Element }
143+ */
85144// 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
89148 const {
90149 tagName = impliedNamespace === webNamespaces . svg ? 'g' : 'div' ,
91150 properties = { } ,
@@ -147,29 +206,45 @@ function element(node, options) {
147206 result . removeAttribute ( attribute )
148207 }
149208 } else if ( booleanish ) {
150- result . setAttribute ( attribute , value )
209+ result . setAttribute ( attribute , String ( value ) )
151210 } else if ( value === true ) {
152211 result . setAttribute ( attribute , '' )
153212 } else if ( value || value === 0 || value === '' ) {
154- result . setAttribute ( attribute , value )
213+ result . setAttribute ( attribute , String ( value ) )
155214 }
156215 }
157216
158- return appendAll ( result , children , { ...options , impliedNamespace} )
217+ return appendAll ( result , children , { ...ctx , impliedNamespace} )
159218}
160219
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 ) {
166233 // eslint-disable-next-line unicorn/prefer-dom-node-append
167- node . appendChild ( transform ( children [ i ] , options ) )
234+ node . appendChild ( transform ( children [ index ] , ctx ) )
168235 }
169236
170237 return node
171238}
172239
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 } )
175250}
0 commit comments