1
- from html import escape
2
1
from types import GeneratorType
3
2
from typing import Tuple , Union , Dict , List , FrozenSet , Generator , Iterable , Any , Callable
4
3
5
4
6
5
class SafeString :
7
- __slots__ = ("safe_str" ,)
8
-
9
6
def __init__ (self , safe_str : str ) -> None :
10
7
self .safe_str = safe_str
11
8
@@ -19,6 +16,17 @@ def __repr__(self) -> str:
19
16
return f"SafeString(safe_str='{ self .safe_str } ')"
20
17
21
18
19
+ def faster_escape (s : str ) -> str :
20
+ """
21
+ This is nearly duplicate of html.escape in the standard lib.
22
+ it's a little faster because:
23
+ - we don't check if some of the replacements are desired
24
+ - we don't re-assign a variable many times.
25
+ """
26
+ return s .replace (
27
+ "&" , "&" # Must be done first!
28
+ ).replace ("<" , "<" ).replace (">" , ">" ).replace ('"' , """ ).replace ('\' ' , "'" )
29
+
22
30
Node = Union [
23
31
str ,
24
32
SafeString ,
@@ -31,10 +39,9 @@ def __repr__(self) -> str:
31
39
TagTuple = Tuple [str , Tuple [Node , ...], str ]
32
40
33
41
_common_safe_attribute_names : FrozenSet [str ] = frozenset (
34
- {
42
+ (
35
43
"alt" ,
36
44
"autoplay" ,
37
- "autoplay" ,
38
45
"charset" ,
39
46
"checked" ,
40
47
"class" ,
@@ -80,13 +87,13 @@ def __repr__(self) -> str:
80
87
"type" ,
81
88
"value" ,
82
89
"width" ,
83
- }
90
+ )
84
91
)
85
92
86
93
87
94
def escape_attribute_key (k : str ) -> str :
88
95
return (
89
- escape ( k , True )
96
+ faster_escape ( k )
90
97
.replace ("=" , "=" )
91
98
.replace ("\\ " , "\" )
92
99
.replace ("`" , "`" )
@@ -134,7 +141,7 @@ def __call__(
134
141
)
135
142
136
143
if isinstance (val , str ):
137
- attrs += f' { key } ="{ escape (val , True )} "'
144
+ attrs += f' { key } ="{ faster_escape (val )} "'
138
145
elif isinstance (val , SafeString ):
139
146
attrs += f' { key } ="{ val .safe_str } "'
140
147
elif val is None :
@@ -278,7 +285,7 @@ def _render(nodes: Iterable[Node], append_to_list: Callable[[str], None]) -> Non
278
285
elif isinstance (node , SafeString ):
279
286
append_to_list (node .safe_str )
280
287
elif isinstance (node , str ):
281
- append_to_list (escape (node ))
288
+ append_to_list (faster_escape (node ))
282
289
elif isinstance (node , Tag ):
283
290
append_to_list (node .rendered )
284
291
elif isinstance (node , list ):
@@ -290,7 +297,7 @@ def _render(nodes: Iterable[Node], append_to_list: Callable[[str], None]) -> Non
290
297
291
298
292
299
_common_safe_css_props = frozenset (
293
- {
300
+ (
294
301
"color" ,
295
302
"border" ,
296
303
"margin" ,
@@ -498,7 +505,7 @@ def _render(nodes: Iterable[Node], append_to_list: Callable[[str], None]) -> Non
498
505
"word-wrap" ,
499
506
"writing-mode" ,
500
507
"z-index" ,
501
- }
508
+ )
502
509
)
503
510
504
511
@@ -511,12 +518,12 @@ def render_styles(
511
518
if isinstance (k , SafeString ):
512
519
k = k .safe_str
513
520
else :
514
- k = escape ( k , True )
521
+ k = faster_escape ( k )
515
522
516
523
if isinstance (v , SafeString ):
517
524
v = v .safe_str
518
525
elif isinstance (v , str ):
519
- v = escape ( v , True )
526
+ v = faster_escape ( v )
520
527
# note that ints and floats pass through these condition checks
521
528
522
529
ret += f"{ k } :{ v } ;"
0 commit comments