@@ -14,6 +14,7 @@ use quote::quote;
14
14
15
15
use crate :: config:: WhitespaceHandling ;
16
16
use crate :: heritage:: { Context , Heritage } ;
17
+ use crate :: html:: write_escaped_str;
17
18
use crate :: input:: { Source , TemplateInput } ;
18
19
use crate :: { CompileError , MsgValidEscapers , CRATE } ;
19
20
@@ -1162,8 +1163,76 @@ impl<'a> Generator<'a> {
1162
1163
}
1163
1164
1164
1165
fn write_expr ( & mut self , ws : Ws , s : & ' a WithSpan < ' a , Expr < ' a > > ) {
1166
+ // In here, we inspect in the expression if it is a literal, and if it is, whether it
1167
+ // can be escaped at compile time. We use an IIFE to make the code more readable
1168
+ // (immediate returns, try expressions).
1169
+ let writable = ( || -> Option < Writable < ' a > > {
1170
+ enum InputKind < ' a > {
1171
+ StrLit ( & ' a str ) ,
1172
+ CharLit ( & ' a str ) ,
1173
+ }
1174
+ enum OutputKind {
1175
+ Html ,
1176
+ Text ,
1177
+ }
1178
+
1179
+ // for now, we only escape strings and chars at compile time
1180
+ let lit = match & * * s {
1181
+ Expr :: StrLit ( input) => InputKind :: StrLit ( input) ,
1182
+ Expr :: CharLit ( input) => InputKind :: CharLit ( input) ,
1183
+ _ => return None ,
1184
+ } ;
1185
+
1186
+ // we only optimize for known escapers
1187
+ let output = match self . input . escaper . strip_prefix ( CRATE ) ? {
1188
+ "::filters::Html" => OutputKind :: Html ,
1189
+ "::filters::Text" => OutputKind :: Text ,
1190
+ _ => return None ,
1191
+ } ;
1192
+
1193
+ // the input could be string escaped if it contains any backslashes
1194
+ let escaped = match lit {
1195
+ InputKind :: StrLit ( s) => s,
1196
+ InputKind :: CharLit ( s) => s,
1197
+ } ;
1198
+ let unescaped = if escaped. find ( '\\' ) . is_none ( ) {
1199
+ // if the literal does not contain any backslashes, then it does not need unescaping
1200
+ Cow :: Borrowed ( escaped)
1201
+ } else {
1202
+ // convert the input into a TokenStream and extract the first token
1203
+ Cow :: Owned ( match lit {
1204
+ InputKind :: StrLit ( escaped) => {
1205
+ let input = format ! ( r#""{escaped}""# ) ;
1206
+ let input = input. parse ( ) . ok ( ) ?;
1207
+ let input = syn:: parse2 :: < syn:: LitStr > ( input) . ok ( ) ?;
1208
+ input. value ( )
1209
+ }
1210
+ InputKind :: CharLit ( escaped) => {
1211
+ let input = format ! ( r#"'{escaped}'"# ) ;
1212
+ let input = input. parse ( ) . ok ( ) ?;
1213
+ let input = syn:: parse2 :: < syn:: LitChar > ( input) . ok ( ) ?;
1214
+ input. value ( ) . to_string ( )
1215
+ }
1216
+ } )
1217
+ } ;
1218
+
1219
+ // escape the un-string-escaped input using the selected escaper
1220
+ Some ( Writable :: Lit ( match output {
1221
+ OutputKind :: Text => unescaped,
1222
+ OutputKind :: Html => {
1223
+ let mut escaped = String :: with_capacity ( unescaped. len ( ) + 20 ) ;
1224
+ write_escaped_str ( & mut escaped, & unescaped) . ok ( ) ?;
1225
+ match escaped == unescaped {
1226
+ true => unescaped,
1227
+ false => Cow :: Owned ( escaped) ,
1228
+ }
1229
+ }
1230
+ } ) )
1231
+ } ) ( )
1232
+ . unwrap_or ( Writable :: Expr ( s) ) ;
1233
+
1165
1234
self . handle_ws ( ws) ;
1166
- self . buf_writable . push ( Writable :: Expr ( s ) ) ;
1235
+ self . buf_writable . push ( writable ) ;
1167
1236
}
1168
1237
1169
1238
// Write expression buffer and empty
@@ -1174,7 +1243,7 @@ impl<'a> Generator<'a> {
1174
1243
) -> Result < usize , CompileError > {
1175
1244
let mut size_hint = 0 ;
1176
1245
let items = mem:: take ( & mut self . buf_writable . buf ) ;
1177
- let mut it = items. into_iter ( ) . enumerate ( ) . peekable ( ) ;
1246
+ let mut it = items. iter ( ) . enumerate ( ) . peekable ( ) ;
1178
1247
1179
1248
while let Some ( ( _, Writable :: Lit ( s) ) ) = it. peek ( ) {
1180
1249
size_hint += buf. write_writer ( s) ;
@@ -1267,20 +1336,23 @@ impl<'a> Generator<'a> {
1267
1336
assert ! ( rws. is_empty( ) ) ;
1268
1337
self . next_ws = Some ( lws) ;
1269
1338
}
1270
- WhitespaceHandling :: Preserve => self . buf_writable . push ( Writable :: Lit ( lws) ) ,
1339
+ WhitespaceHandling :: Preserve => {
1340
+ self . buf_writable . push ( Writable :: Lit ( Cow :: Borrowed ( lws) ) )
1341
+ }
1271
1342
WhitespaceHandling :: Minimize => {
1272
- self . buf_writable
1273
- . push ( Writable :: Lit ( match lws. contains ( '\n' ) {
1343
+ self . buf_writable . push ( Writable :: Lit ( Cow :: Borrowed (
1344
+ match lws. contains ( '\n' ) {
1274
1345
true => "\n " ,
1275
1346
false => " " ,
1276
- } ) ) ;
1347
+ } ,
1348
+ ) ) ) ;
1277
1349
}
1278
1350
}
1279
1351
}
1280
1352
1281
1353
if !val. is_empty ( ) {
1282
1354
self . skip_ws = WhitespaceHandling :: Preserve ;
1283
- self . buf_writable . push ( Writable :: Lit ( val) ) ;
1355
+ self . buf_writable . push ( Writable :: Lit ( Cow :: Borrowed ( val) ) ) ;
1284
1356
}
1285
1357
1286
1358
if !rws. is_empty ( ) {
@@ -2031,17 +2103,18 @@ impl<'a> Generator<'a> {
2031
2103
WhitespaceHandling :: Preserve => {
2032
2104
let val = self . next_ws . unwrap ( ) ;
2033
2105
if !val. is_empty ( ) {
2034
- self . buf_writable . push ( Writable :: Lit ( val) ) ;
2106
+ self . buf_writable . push ( Writable :: Lit ( Cow :: Borrowed ( val) ) ) ;
2035
2107
}
2036
2108
}
2037
2109
WhitespaceHandling :: Minimize => {
2038
2110
let val = self . next_ws . unwrap ( ) ;
2039
2111
if !val. is_empty ( ) {
2040
- self . buf_writable
2041
- . push ( Writable :: Lit ( match val. contains ( '\n' ) {
2112
+ self . buf_writable . push ( Writable :: Lit ( Cow :: Borrowed (
2113
+ match val. contains ( '\n' ) {
2042
2114
true => "\n " ,
2043
2115
false => " " ,
2044
- } ) ) ;
2116
+ } ,
2117
+ ) ) ) ;
2045
2118
}
2046
2119
}
2047
2120
WhitespaceHandling :: Suppress => { }
@@ -2481,7 +2554,7 @@ impl<'a> Deref for WritableBuffer<'a> {
2481
2554
2482
2555
#[ derive( Debug ) ]
2483
2556
enum Writable < ' a > {
2484
- Lit ( & ' a str ) ,
2557
+ Lit ( Cow < ' a , str > ) ,
2485
2558
Expr ( & ' a WithSpan < ' a , Expr < ' a > > ) ,
2486
2559
}
2487
2560
0 commit comments