@@ -387,8 +387,10 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
387
387
indent , indentation_level = " " , 1
388
388
389
389
# Keep track of the template state
390
- forloop_iterables : "list[str]" = []
391
- autoescape_modes : "list[bool]" = ["default_on" ]
390
+ nested_if_statements : "list[str]" = []
391
+ nested_for_loops : "list[str]" = []
392
+ nested_while_loops : "list[str]" = []
393
+ nested_autoescape_modes : "list[str]" = []
392
394
last_token_was_block = False
393
395
394
396
# Resolve tokens
@@ -414,7 +416,10 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
414
416
if token .startswith (r"{{ " ):
415
417
last_token_was_block = False
416
418
417
- autoescape = autoescape_modes [- 1 ] in ("on" , "default_on" )
419
+ if nested_autoescape_modes :
420
+ autoescape = nested_autoescape_modes [- 1 ][14 :- 3 ] == "on"
421
+ else :
422
+ autoescape = True
418
423
419
424
# Expression should be escaped with language-specific function
420
425
if autoescape :
@@ -436,6 +441,8 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
436
441
if token .startswith (r"{% if " ):
437
442
function_string += indent * indentation_level + f"{ token [3 :- 3 ]} :\n "
438
443
indentation_level += 1
444
+
445
+ nested_if_statements .append (token )
439
446
elif token .startswith (r"{% elif " ):
440
447
indentation_level -= 1
441
448
function_string += indent * indentation_level + f"{ token [3 :- 3 ]} :\n "
@@ -447,30 +454,51 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
447
454
elif token == r"{% endif %}" :
448
455
indentation_level -= 1
449
456
457
+ if not nested_if_statements :
458
+ raise SyntaxError ("No matching {% if ... %} block for {% endif %}" )
459
+
460
+ nested_if_statements .pop ()
461
+
450
462
# Token is a for loop
451
463
elif token .startswith (r"{% for " ):
452
464
function_string += indent * indentation_level + f"{ token [3 :- 3 ]} :\n "
453
465
indentation_level += 1
454
466
455
- forloop_iterables .append (token [ 3 : - 3 ]. split ( " in " , 1 )[ 1 ] )
467
+ nested_for_loops .append (token )
456
468
elif token == r"{% empty %}" :
457
469
indentation_level -= 1
470
+ last_forloop_iterable = nested_for_loops [- 1 ][3 :- 3 ].split (" in " , 1 )[1 ]
458
471
459
472
function_string += (
460
- indent * indentation_level + f"if not { forloop_iterables [ - 1 ] } :\n "
473
+ indent * indentation_level + f"if not { last_forloop_iterable } :\n "
461
474
)
462
475
indentation_level += 1
463
476
elif token == r"{% endfor %}" :
464
477
indentation_level -= 1
465
- forloop_iterables .pop ()
478
+
479
+ if not nested_for_loops :
480
+ raise SyntaxError (
481
+ "No matching {% for ... %} block for {% endfor %}"
482
+ )
483
+
484
+ nested_for_loops .pop ()
466
485
467
486
# Token is a while loop
468
487
elif token .startswith (r"{% while " ):
469
488
function_string += indent * indentation_level + f"{ token [3 :- 3 ]} :\n "
470
489
indentation_level += 1
490
+
491
+ nested_while_loops .append (token )
471
492
elif token == r"{% endwhile %}" :
472
493
indentation_level -= 1
473
494
495
+ if not nested_while_loops :
496
+ raise SyntaxError (
497
+ "No matching {% while ... %} block for {% endwhile %}"
498
+ )
499
+
500
+ nested_while_loops .pop ()
501
+
474
502
# Token is a Python code
475
503
elif token .startswith (r"{% exec " ):
476
504
expression = token [8 :- 3 ]
@@ -481,11 +509,16 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
481
509
mode = token [14 :- 3 ]
482
510
if mode not in ("on" , "off" ):
483
511
raise ValueError (f"Unknown autoescape mode: { mode } " )
484
- autoescape_modes .append (mode )
512
+
513
+ nested_autoescape_modes .append (token )
514
+
485
515
elif token == r"{% endautoescape %}" :
486
- if autoescape_modes == ["default_on" ]:
487
- raise ValueError ("No autoescape mode to end" )
488
- autoescape_modes .pop ()
516
+ if not nested_autoescape_modes :
517
+ raise SyntaxError (
518
+ "No matching {% autoescape ... %} block for {% endautoescape %}"
519
+ )
520
+
521
+ nested_autoescape_modes .pop ()
489
522
490
523
else :
491
524
raise ValueError (
@@ -498,6 +531,21 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
498
531
# Continue with the rest of the template
499
532
template = template [token_match .end () :]
500
533
534
+ # Checking for unclosed blocks
535
+ if len (nested_if_statements ) > 0 :
536
+ last_if_statement = nested_if_statements [- 1 ]
537
+ raise SyntaxError (f"Missing {{% endif %}} for { last_if_statement } " )
538
+
539
+ if len (nested_for_loops ) > 0 :
540
+ last_for_loop = nested_for_loops [- 1 ]
541
+ raise SyntaxError (f"Missing {{% endfor %}} for { last_for_loop } " )
542
+
543
+ if len (nested_while_loops ) > 0 :
544
+ last_while_loop = nested_while_loops [- 1 ]
545
+ raise SyntaxError (f"Missing {{% endwhile %}} for { last_while_loop } " )
546
+
547
+ # No check for unclosed autoescape blocks, as they are optional and do not result in errors
548
+
501
549
# Add the text after the last token (if any)
502
550
text_after_last_token = template
503
551
0 commit comments