1
1
from __future__ import annotations
2
2
3
3
import re
4
- from collections .abc import Mapping
5
- from html import escape as html_escape
6
4
from itertools import chain
7
5
from typing import Any , Callable , Generic , Iterable , TypeVar , cast
8
- from warnings import warn
9
6
10
7
from lxml import etree
11
- from lxml .html import fragments_fromstring
8
+ from lxml .html import fragments_fromstring , tostring
12
9
13
10
import idom
14
11
from idom .core .types import VdomDict
@@ -62,7 +59,7 @@ def __repr__(self) -> str:
62
59
return f"{ type (self ).__name__ } ({ current } )"
63
60
64
61
65
- def vdom_to_html (value : str | VdomDict ) -> str :
62
+ def vdom_to_html (value : VdomDict ) -> str :
66
63
"""Convert a VDOM dictionary into an HTML string
67
64
68
65
Only the following keys are translated to HTML:
@@ -71,40 +68,12 @@ def vdom_to_html(value: str | VdomDict) -> str:
71
68
- ``attributes``
72
69
- ``children`` (must be strings or more VDOM dicts)
73
70
"""
74
-
75
- if isinstance (value , str ):
76
- return value
77
-
78
- try :
79
- tag = value ["tagName" ]
80
- except TypeError as error : # pragma: no cover
81
- raise TypeError (f"Expected a VDOM dictionary or string, not { value } " ) from error
82
-
83
- attributes = " " .join (
84
- _vdom_to_html_attr (k , v ) for k , v in value .get ("attributes" , {}).items ()
85
- )
86
-
87
- if attributes :
88
- assert tag , "Element frament may not contain attributes"
89
- attributes = f" { attributes } "
90
-
91
- children = "" .join (
92
- vdom_to_html (cast ("VdomDict | str" , c ))
93
- if isinstance (c , (dict , str ))
94
- else html_escape (str (c ))
95
- for c in value .get ("children" , ())
96
- )
97
-
71
+ temp_root = etree .Element ("__temp__" )
72
+ _add_vdom_to_etree (temp_root , value )
98
73
return (
99
- (
100
- f"<{ tag } { attributes } >{ children } </{ tag } >"
101
- if children
102
- # To be safe we mark elements without children as self-closing.
103
- # https://html.spec.whatwg.org/multipage/syntax.html#foreign-elements
104
- else (f"<{ tag } { attributes } />" if attributes else f"<{ tag } />" )
105
- )
106
- if tag
107
- else children
74
+ cast (bytes , tostring (temp_root )).decode ()
75
+ # strip out temp root <__temp__> element
76
+ [10 :- 11 ]
108
77
)
109
78
110
79
@@ -221,6 +190,32 @@ def _etree_to_vdom(
221
190
return vdom
222
191
223
192
193
+ def _add_vdom_to_etree (parent : etree ._Element , vdom : VdomDict ) -> None :
194
+ try :
195
+ tag = vdom ["tagName" ]
196
+ except TypeError as e :
197
+ raise TypeError (f"Expected a VdomDict, not { vdom } " ) from e
198
+ except KeyError as e :
199
+ raise TypeError (f"Expected a VdomDict, not { vdom } " ) from e
200
+
201
+ if tag :
202
+ element = etree .SubElement (parent , tag )
203
+ element .attrib .update (
204
+ _vdom_to_html_attr (k , v ) for k , v in vdom .get ("attributes" , {}).items ()
205
+ )
206
+ else :
207
+ element = parent
208
+
209
+ for c in vdom .get ("children" , []):
210
+ if isinstance (c , dict ):
211
+ _add_vdom_to_etree (element , cast (VdomDict , c ))
212
+ elif len (element ):
213
+ last_child = element [- 1 ]
214
+ last_child .tail = f"{ last_child .tail or '' } { c } "
215
+ else :
216
+ element .text = f"{ element .text or '' } { c } "
217
+
218
+
224
219
def _mutate_vdom (vdom : VdomDict ) -> None :
225
220
"""Performs any necessary mutations on the VDOM attributes to meet VDOM spec.
226
221
@@ -288,7 +283,7 @@ def _hypen_to_camel_case(string: str) -> str:
288
283
}
289
284
290
285
291
- def _vdom_to_html_attr (key : str , value : Any ) -> str :
286
+ def _vdom_to_html_attr (key : str , value : Any ) -> tuple [ str , str ] :
292
287
if key == "style" :
293
288
if isinstance (value , dict ):
294
289
value = ";" .join (
@@ -303,6 +298,10 @@ def _vdom_to_html_attr(key: str, value: Any) -> str:
303
298
else :
304
299
key = _CAMEL_TO_DASH_CASE_HTML_ATTRS .get (key , key )
305
300
301
+ assert not callable (
302
+ value
303
+ ), f"Could not convert callable attribute { key } ={ value } to HTML"
304
+
306
305
# Again, we lower the attribute name only to normalize - HTML is case-insensitive:
307
306
# http://w3c.github.io/html-reference/documents.html#case-insensitivity
308
- return f' { key .lower ()} =" { html_escape ( str (value )) } "'
307
+ return key .lower (), str (value )
0 commit comments