text: 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
  