@@ -219,7 +219,7 @@ def _resolve_includes(template: str):
219219
220220def _check_for_unsupported_nested_blocks (template : str ):
221221 if _find_block (template ) is not None :
222- raise ValueError ("Nested blocks are not supported" )
222+ raise SyntaxError ("Nested blocks are not supported" )
223223
224224
225225def _resolve_includes_blocks_and_extends (template : str ):
@@ -248,7 +248,7 @@ def _resolve_includes_blocks_and_extends(template: str):
248248 endblock_match = _find_named_endblock (template , block_name )
249249
250250 if endblock_match is None :
251- raise ValueError ( r "Missing {% endblock %} for block: " + block_name )
251+ raise SyntaxError ( "Missing {% endblock %} for block: " + block_name )
252252
253253 block_content = template [block_match .end () : endblock_match .start ()]
254254
@@ -366,13 +366,13 @@ def _token_is_on_own_line(text_before_token: str) -> bool:
366366 return _LSTRIP_BLOCK_PATTERN .search (text_before_token ) is not None
367367
368368
369- def _create_template_function ( # pylint: disable=,too-many-locals,too-many-branches,too-many-statements
369+ def _create_template_rendering_function ( # pylint: disable=,too-many-locals,too-many-branches,too-many-statements
370370 template : str ,
371371 language : str = Language .HTML ,
372372 * ,
373373 trim_blocks : bool = True ,
374374 lstrip_blocks : bool = True ,
375- function_name : str = "_ " ,
375+ function_name : str = "__template_rendering_function " ,
376376 context_name : str = "context" ,
377377 dry_run : bool = False ,
378378) -> "Generator[str] | str" :
@@ -387,8 +387,10 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
387387 indent , indentation_level = " " , 1
388388
389389 # 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]" = []
392394 last_token_was_block = False
393395
394396 # Resolve tokens
@@ -405,16 +407,21 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
405407 if last_token_was_block and text_before_token .startswith ("\n " ):
406408 text_before_token = text_before_token [1 :]
407409
408- if text_before_token :
409- function_string += (
410- indent * indentation_level + f"yield { repr (text_before_token )} \n "
411- )
410+ if text_before_token :
411+ function_string += (
412+ indent * indentation_level + f"yield { repr (text_before_token )} \n "
413+ )
414+ else :
415+ function_string += indent * indentation_level + "pass\n "
412416
413417 # Token is an expression
414418 if token .startswith (r"{{ " ):
415419 last_token_was_block = False
416420
417- autoescape = autoescape_modes [- 1 ] in ("on" , "default_on" )
421+ if nested_autoescape_modes :
422+ autoescape = nested_autoescape_modes [- 1 ][14 :- 3 ] == "on"
423+ else :
424+ autoescape = True
418425
419426 # Expression should be escaped with language-specific function
420427 if autoescape :
@@ -436,6 +443,8 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
436443 if token .startswith (r"{% if " ):
437444 function_string += indent * indentation_level + f"{ token [3 :- 3 ]} :\n "
438445 indentation_level += 1
446+
447+ nested_if_statements .append (token )
439448 elif token .startswith (r"{% elif " ):
440449 indentation_level -= 1
441450 function_string += indent * indentation_level + f"{ token [3 :- 3 ]} :\n "
@@ -447,30 +456,49 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
447456 elif token == r"{% endif %}" :
448457 indentation_level -= 1
449458
459+ if not nested_if_statements :
460+ raise SyntaxError ("Missing {% if ... %} block for {% endif %}" )
461+
462+ nested_if_statements .pop ()
463+
450464 # Token is a for loop
451465 elif token .startswith (r"{% for " ):
452466 function_string += indent * indentation_level + f"{ token [3 :- 3 ]} :\n "
453467 indentation_level += 1
454468
455- forloop_iterables .append (token [ 3 : - 3 ]. split ( " in " , 1 )[ 1 ] )
469+ nested_for_loops .append (token )
456470 elif token == r"{% empty %}" :
457471 indentation_level -= 1
472+ last_forloop_iterable = nested_for_loops [- 1 ][3 :- 3 ].split (" in " , 1 )[1 ]
458473
459474 function_string += (
460- indent * indentation_level + f"if not { forloop_iterables [ - 1 ] } :\n "
475+ indent * indentation_level + f"if not { last_forloop_iterable } :\n "
461476 )
462477 indentation_level += 1
463478 elif token == r"{% endfor %}" :
464479 indentation_level -= 1
465- forloop_iterables .pop ()
480+
481+ if not nested_for_loops :
482+ raise SyntaxError ("Missing {% for ... %} block for {% endfor %}" )
483+
484+ nested_for_loops .pop ()
466485
467486 # Token is a while loop
468487 elif token .startswith (r"{% while " ):
469488 function_string += indent * indentation_level + f"{ token [3 :- 3 ]} :\n "
470489 indentation_level += 1
490+
491+ nested_while_loops .append (token )
471492 elif token == r"{% endwhile %}" :
472493 indentation_level -= 1
473494
495+ if not nested_while_loops :
496+ raise SyntaxError (
497+ "Missing {% while ... %} block for {% endwhile %}"
498+ )
499+
500+ nested_while_loops .pop ()
501+
474502 # Token is a Python code
475503 elif token .startswith (r"{% exec " ):
476504 expression = token [8 :- 3 ]
@@ -481,23 +509,41 @@ def _create_template_function( # pylint: disable=,too-many-locals,too-many-bran
481509 mode = token [14 :- 3 ]
482510 if mode not in ("on" , "off" ):
483511 raise ValueError (f"Unknown autoescape mode: { mode } " )
484- autoescape_modes .append (mode )
512+
513+ nested_autoescape_modes .append (token )
514+
485515 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+ "Missing {% autoescape ... %} block for {% endautoescape %}"
519+ )
520+
521+ nested_autoescape_modes .pop ()
489522
490523 else :
491- raise ValueError (
492- f"Unknown token type: { token } at { token_match .start ()} "
493- )
524+ raise SyntaxError (f"Unknown token type: { token } " )
494525
495526 else :
496- raise ValueError (f"Unknown token type: { token } at { token_match . start () } " )
527+ raise SyntaxError (f"Unknown token type: { token } " )
497528
498529 # Continue with the rest of the template
499530 template = template [token_match .end () :]
500531
532+ # Checking for unclosed blocks
533+ if len (nested_if_statements ) > 0 :
534+ last_if_statement = nested_if_statements [- 1 ]
535+ raise SyntaxError ("Missing {% endif %} for " + last_if_statement )
536+
537+ if len (nested_for_loops ) > 0 :
538+ last_for_loop = nested_for_loops [- 1 ]
539+ raise SyntaxError ("Missing {% endfor %} for " + last_for_loop )
540+
541+ if len (nested_while_loops ) > 0 :
542+ last_while_loop = nested_while_loops [- 1 ]
543+ raise SyntaxError ("Missing {% endwhile %} for " + last_while_loop )
544+
545+ # No check for unclosed autoescape blocks, as they are optional and do not result in errors
546+
501547 # Add the text after the last token (if any)
502548 text_after_last_token = template
503549
@@ -557,7 +603,9 @@ def __init__(self, template_string: str, *, language: str = Language.HTML) -> No
557603 :param str template_string: String containing the template to be rendered
558604 :param str language: Language for autoescaping. Defaults to HTML
559605 """
560- self ._template_function = _create_template_function (template_string , language )
606+ self ._template_function = _create_template_rendering_function (
607+ template_string , language
608+ )
561609
562610 def render_iter (
563611 self , context : dict = None , * , chunk_size : int = None
0 commit comments