@@ -46,30 +46,149 @@ class HtmlStringToVdom extends Base {
4646 * @param {Object|Object[] } opts
4747 * @param {String } [opts.type=text/html]
4848 * @param {String } opts.value
49+ * @param {Array } [opts.values] Dynamic values to replace placeholders
4950 * @returns {Object|Object[] } The vdom object or array of vdom objects
5051 */
5152 createVdom ( opts ) {
5253 let arrayParam = true ;
5354
5455 if ( ! Array . isArray ( opts ) ) {
5556 arrayParam = false ;
56- opts = [ opts ]
57+ opts = [ opts ] ;
5758 }
5859
5960 const
6061 me = this ,
6162 { mimeTypes} = HtmlStringToVdom ,
6263 returnValue = [ ] ;
6364
64- for ( const { type= 'text/html' , value} of opts ) {
65+ for ( const { type = 'text/html' , value, values = [ ] } of opts ) {
6566 if ( ! mimeTypes . includes ( type ) ) {
66- throw new Error ( `Invalid mimeType: ${ type } . Supported values are: ${ mimeTypes . join ( ', ' ) } ` )
67+ throw new Error ( `Invalid mimeType: ${ type } . Supported values are: ${ mimeTypes . join ( ', ' ) } ` ) ;
6768 }
6869
69- const tree = me . domParser . parseFromString ( value , type ) ;
70+ const doc = me . domParser . parseFromString ( value , type ) ;
71+
72+ // If the parser returns an error document, handle it
73+ if ( doc . querySelector ( 'parsererror' ) ) {
74+ console . error ( 'Error parsing HTML string:' , doc . querySelector ( 'parsererror' ) . textContent ) ;
75+ returnValue . push ( {
76+ tag : 'div' ,
77+ html : 'Error parsing HTML'
78+ } ) ;
79+ continue ;
80+ }
81+
82+ let nodes = Array . from ( doc . body . childNodes ) ;
83+
84+ // If there are no nodes in the body, check the head (e.g., for SVG)
85+ if ( nodes . length === 0 && doc . head . childNodes . length > 0 ) {
86+ nodes = Array . from ( doc . head . childNodes ) ;
87+ }
88+
89+ if ( nodes . length === 1 ) {
90+ returnValue . push ( me . domNodeToVdom ( nodes [ 0 ] , values ) ) ;
91+ } else {
92+ const fragment = [ ] ;
93+ for ( const node of nodes ) {
94+ // Ignore whitespace-only text nodes between elements
95+ if ( node . nodeType === 3 && node . textContent . trim ( ) === '' ) {
96+ continue ;
97+ }
98+ fragment . push ( me . domNodeToVdom ( node , values ) ) ;
99+ }
100+ returnValue . push ( fragment ) ;
101+ }
102+ }
103+
104+ return arrayParam ? returnValue : returnValue [ 0 ] ;
105+ }
106+
107+ /**
108+ * Recursively converts a DOM node to a VDOM object.
109+ * @param {Node } node The DOM node to convert.
110+ * @param {Array } values The array of dynamic values.
111+ * @returns {Object|String } The VDOM object or a string for text nodes.
112+ * @private
113+ */
114+ domNodeToVdom ( node , values ) {
115+ // Text Node
116+ if ( node . nodeType === 3 ) { // TEXT_NODE
117+ const text = node . textContent . trim ( ) ;
118+ const match = text . match ( / ^ _ _ D Y N A M I C _ V A L U E _ ( \d + ) _ _ $ / ) ;
119+
120+ // If the text node is exclusively a placeholder, return the raw value
121+ if ( match ) {
122+ return values [ parseInt ( match [ 1 ] , 10 ) ] ;
123+ }
124+
125+ // For regular text nodes (which might still contain placeholders for string values)
126+ return node . textContent . replace ( / _ _ D Y N A M I C _ V A L U E _ ( \d + ) _ _ / g, ( m , index ) => {
127+ return values [ parseInt ( index , 10 ) ] ;
128+ } ) ;
70129 }
71130
72- return arrayParam ? returnValue : returnValue [ 0 ]
131+ // Element Node
132+ if ( node . nodeType === 1 ) { // ELEMENT_NODE
133+ const vdom = {
134+ tag : node . tagName . toLowerCase ( )
135+ } ;
136+
137+ // Attributes
138+ if ( node . hasAttributes ( ) ) {
139+ for ( const attr of node . attributes ) {
140+ let attrName = attr . name ;
141+ let attrValue = attr . value ;
142+
143+ // Replace placeholders in attribute values
144+ attrValue = attrValue . replace ( / _ _ D Y N A M I C _ V A L U E _ ( \d + ) _ _ / g, ( match , index ) => {
145+ return values [ parseInt ( index , 10 ) ] ;
146+ } ) ;
147+
148+ if ( attrName === 'class' ) {
149+ attrName = 'cls' ;
150+ } else if ( attrName === 'style' ) {
151+ vdom . style = this . parseStyle ( attrValue ) ;
152+ continue ;
153+ }
154+ vdom [ attrName ] = attrValue ;
155+ }
156+ }
157+
158+ // Children
159+ if ( node . hasChildNodes ( ) ) {
160+ vdom . cn = [ ] ;
161+ for ( const child of node . childNodes ) {
162+ // Ignore whitespace-only text nodes that are not placeholders
163+ if ( child . nodeType === 3 && child . textContent . trim ( ) === '' ) {
164+ continue ;
165+ }
166+ const childVdom = this . domNodeToVdom ( child , values ) ;
167+ vdom . cn . push ( childVdom ) ;
168+ }
169+ }
170+
171+ return vdom ;
172+ }
173+
174+ return null ; // Should not happen for valid HTML
175+ }
176+
177+ /**
178+ * Parses a style attribute string into an object.
179+ * @param {String } styleString The style string (e.g., "color: red; font-size: 16px").
180+ * @returns {Object } The style object.
181+ * @private
182+ */
183+ parseStyle ( styleString ) {
184+ const style = { } ;
185+ styleString . split ( ';' ) . forEach ( declaration => {
186+ if ( declaration . trim ( ) !== '' ) {
187+ const [ property , value ] = declaration . split ( ':' ) ;
188+ style [ property . trim ( ) ] = value . trim ( ) ;
189+ }
190+ } ) ;
191+ return style ;
73192 }
74193}
75194
0 commit comments