from __future__ import absolute_import from copy import copy from django import template from django.template import Context from django.template.base import Node, VariableNode, TextNode, NodeList from django.template.defaulttags import IfNode from django.template.loader import get_template from django.template.loader_tags import ExtendsNode, BlockNode, BlockContext from compressor.exceptions import TemplateSyntaxError, TemplateDoesNotExist from compressor.templatetags.compress import CompressorNode def handle_extendsnode(extendsnode, block_context=None, original=None): """Create a copy of Node tree of a derived template replacing all blocks tags with the nodes of appropriate blocks. Also handles {{ block.super }} tags. """ if block_context is None: block_context = BlockContext() blocks = dict((n.name, n) for n in extendsnode.nodelist.get_nodes_by_type(BlockNode)) block_context.add_blocks(blocks) # Note: we pass an empty context when we find the parent, this breaks # inheritance using variables ({% extends template_var %}) but a refactor # will be needed to support that use-case with multiple offline contexts. context = Context() if original is not None: context.template = original compiled_parent = extendsnode.get_parent(context) parent_nodelist = compiled_parent.nodelist # If the parent template has an ExtendsNode it is not the root. for node in parent_nodelist: # The ExtendsNode has to be the first non-text node. if not isinstance(node, TextNode): if isinstance(node, ExtendsNode): return handle_extendsnode(node, block_context, original) break # Add blocks of the root template to block context. blocks = dict((n.name, n) for n in parent_nodelist.get_nodes_by_type(BlockNode)) block_context.add_blocks(blocks) block_stack = [] new_nodelist = remove_block_nodes(parent_nodelist, block_stack, block_context) return new_nodelist def remove_block_nodes(nodelist, block_stack, block_context): new_nodelist = NodeList() for node in nodelist: if isinstance(node, VariableNode): var_name = node.filter_expression.token.strip() if var_name == 'block.super': if not block_stack: continue node = block_context.get_block(block_stack[-1].name) if not node: continue if isinstance(node, BlockNode): expanded_block = expand_blocknode(node, block_stack, block_context) new_nodelist.extend(expanded_block) else: # IfNode has nodelist as a @property so we can not modify it if isinstance(node, IfNode): node = copy(node) for i, (condition, sub_nodelist) in enumerate(node.conditions_nodelists): sub_nodelist = remove_block_nodes(sub_nodelist, block_stack, block_context) node.conditions_nodelists[i] = (condition, sub_nodelist) else: for attr in node.child_nodelists: sub_nodelist = getattr(node, attr, None) if sub_nodelist: sub_nodelist = remove_block_nodes(sub_nodelist, block_stack, block_context) node = copy(node) setattr(node, attr, sub_nodelist) new_nodelist.append(node) return new_nodelist def expand_blocknode(node, block_stack, block_context): popped_block = block = block_context.pop(node.name) if block is None: block = node block_stack.append(block) expanded_nodelist = remove_block_nodes(block.nodelist, block_stack, block_context) block_stack.pop() if popped_block is not None: block_context.push(node.name, popped_block) return expanded_nodelist class DjangoParser(object): def __init__(self, charset): self.charset = charset def parse(self, template_name): try: return get_template(template_name).template except template.TemplateSyntaxError as e: raise TemplateSyntaxError(str(e)) except template.TemplateDoesNotExist as e: raise TemplateDoesNotExist(str(e)) def process_template(self, template, context): return True def get_init_context(self, offline_context): return offline_context def process_node(self, template, context, node): pass def render_nodelist(self, template, context, node): context.template = template return node.nodelist.render(context) def render_node(self, template, context, node): return node.render(context, forced=True) def get_nodelist(self, node, original=None): if isinstance(node, ExtendsNode): try: return handle_extendsnode(node, block_context=None, original=original) except template.TemplateSyntaxError as e: raise TemplateSyntaxError(str(e)) except template.TemplateDoesNotExist as e: raise TemplateDoesNotExist(str(e)) # Check if node is an ```if``` switch with true and false branches nodelist = [] if isinstance(node, Node): for attr in node.child_nodelists: nodelist += getattr(node, attr, []) else: nodelist = getattr(node, 'nodelist', []) return nodelist def walk_nodes(self, node, original=None): if original is None: original = node for node in self.get_nodelist(node, original): if isinstance(node, CompressorNode) and node.is_offline_compression_enabled(forced=True): yield node else: for node in self.walk_nodes(node, original): yield node