function applyDirectivesToNode(directives, templateNode, templateAttrs, transcludeFn, rootElement) {
      var terminalPriority = -Number.MAX_VALUE,
          preLinkingFns = [],
          postLinkingFns = [],
          newScopeDirective = null,
          newIsolatedScopeDirective = null,
          templateDirective = null,
          delayedLinkingFn = null,
          element = templateAttrs.$$element = jqLite(templateNode),
          directive,
          directiveName,
          template,
          transcludeDirective,
          childTranscludeFn = transcludeFn,
          controllerDirectives,
          linkingFn,
          directiveValue;

      // executes all directives on the current element
      for(var i = 0, ii = directives.length; i < ii; i++) {
        directive = directives[i];
        template = undefined;

        if (terminalPriority > directive.priority) {
          break; // prevent further processing of directives
        }

        if (directiveValue = directive.scope) {
          assertNoDuplicate('isolated scope', newIsolatedScopeDirective, directive, element);
          if (isObject(directiveValue)) {
            safeAddClass(element, 'ng-isolate-scope');
            newIsolatedScopeDirective = directive;
          }
          safeAddClass(element, 'ng-scope');
          newScopeDirective = newScopeDirective || directive;
        }

        directiveName = directive.name;

        if (directiveValue = directive.controller) {
          controllerDirectives = controllerDirectives || {};
          assertNoDuplicate("'" + directiveName + "' controller",
              controllerDirectives[directiveName], directive, element);
          controllerDirectives[directiveName] = directive;
        }

        if (directiveValue = directive.transclude) {
          assertNoDuplicate('transclusion', transcludeDirective, directive, element);
          transcludeDirective = directive;
          terminalPriority = directive.priority;
          if (directiveValue == 'element') {
            template = jqLite(templateNode);
            templateNode = (element = templateAttrs.$$element = jqLite(
                '<!-- ' + directiveName + ': ' + templateAttrs[directiveName]  + ' -->'))[0];
            replaceWith(rootElement, jqLite(template[0]), templateNode);
            childTranscludeFn = compile(template, transcludeFn, terminalPriority);
          } else {
            template = jqLite(JQLiteClone(templateNode));
            element.html(''); // clear contents
            childTranscludeFn = compile(template.contents(), transcludeFn);
          }
        }

        if (directiveValue = directive.template) {
          assertNoDuplicate('template', templateDirective, directive, element);
          templateDirective = directive;

          // include the contents of the original element into the template and replace the element
          var content = directiveValue.replace(CONTENT_REGEXP, element.html());
          templateNode = jqLite(content)[0];
          if (directive.replace) {
            replaceWith(rootElement, element, templateNode);

            var newTemplateAttrs = {$attr: {}};

            // combine directives from the original node and from the template:
            // - take the array of directives for this element
            // - split it into two parts, those that were already applied and those that weren't
            // - collect directives from the template, add them to the second group and sort them
            // - append the second group with new directives to the first group
            directives = directives.concat(
                collectDirectives(
                    templateNode,
                    directives.splice(i + 1, directives.length - (i + 1)),
                    newTemplateAttrs
                )
            );
            mergeTemplateAttributes(templateAttrs, newTemplateAttrs);

            ii = directives.length;
          } else {
            element.html(content);
          }
        }

        if (directive.templateUrl) {
          assertNoDuplicate('template', templateDirective, directive, element);
          templateDirective = directive;
          delayedLinkingFn = compileTemplateUrl(directives.splice(i, directives.length - i),
              /* directiveLinkingFn */ compositeLinkFn, element, templateAttrs, rootElement,
              directive.replace, childTranscludeFn);
          ii = directives.length;
        } else if (directive.compile) {
          try {
            linkingFn = directive.compile(element, templateAttrs, childTranscludeFn);
            if (isFunction(linkingFn)) {
              addLinkingFns(null, linkingFn);
            } else if (linkingFn) {
              addLinkingFns(linkingFn.pre, linkingFn.post);
            }
          } catch (e) {
            $exceptionHandler(e, startingTag(element));
          }
        }

        if (directive.terminal) {
          compositeLinkFn.terminal = true;
          terminalPriority = Math.max(terminalPriority, directive.priority);
        }

      }

      linkingFn = delayedLinkingFn || compositeLinkFn;
      linkingFn.scope = newScopeDirective && newScopeDirective.scope;
      linkingFn.transclude = transcludeDirective && childTranscludeFn;

      // if we have templateUrl, then we have to delay linking
      return linkingFn;

      ////////////////////

      function addLinkingFns(pre, post) {
        if (pre) {
          pre.require = directive.require;
          preLinkingFns.push(pre);
        }
        if (post) {
          post.require = directive.require;
          postLinkingFns.push(post);
        }
      }


      function getControllers(require, element) {
        var value, retrievalMethod = 'data', optional = false;
        if (isString(require)) {
          while((value = require.charAt(0)) == '^' || value == '?') {
            require = require.substr(1);
            if (value == '^') {
              retrievalMethod = 'inheritedData';
            }
            optional = optional || value == '?';
          }
          value = element[retrievalMethod]('$' + require + 'Controller');
          if (!value && !optional) {
            throw Error("No controller: " + require);
          }
          return value;
        } else if (isArray(require)) {
          value = [];
          forEach(require, function(require) {
            value.push(getControllers(require, element));
          });
        }
        return value;
      }


      /* directiveLinkingFn */
      function compositeLinkFn(/* nodesetLinkingFn */ childLinkingFn,
                               scope, linkNode, rootElement, boundTranscludeFn) {
        var attrs, element, i, ii, linkingFn, controller;

        if (templateNode === linkNode) {
          attrs = templateAttrs;
        } else {
          attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));
        }
        element = attrs.$$element;

        if (newScopeDirective && isObject(newScopeDirective.scope)) {
          forEach(newScopeDirective.scope, function(mode, name) {
            (LOCAL_MODE[mode] || wrongMode)(name, mode,
                scope.$parent || scope, scope, attrs);
          });
        }

        if (controllerDirectives) {
          forEach(controllerDirectives, function(directive) {
            var locals = {
              $scope: scope,
              $element: element,
              $attrs: attrs,
              $transclude: boundTranscludeFn
            };


            forEach(directive.inject || {}, function(mode, name) {
              (LOCAL_MODE[mode] || wrongMode)(name, mode,
                  newScopeDirective ? scope.$parent || scope : scope, locals, attrs);
            });

            controller = directive.controller;
            if (controller == '@') {
              controller = attrs[directive.name];
            }

            element.data(
                '$' + directive.name + 'Controller',
                $controller(controller, locals));
          });
        }

        // PRELINKING
        for(i = 0, ii = preLinkingFns.length; i < ii; i++) {
          try {
            linkingFn = preLinkingFns[i];
            linkingFn(scope, element, attrs,
                linkingFn.require && getControllers(linkingFn.require, element));
          } catch (e) {
            $exceptionHandler(e, startingTag(element));
          }
        }

        // RECURSION
        childLinkingFn && childLinkingFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);

        // POSTLINKING
        for(i = 0, ii = postLinkingFns.length; i < ii; i++) {
          try {
            linkingFn = postLinkingFns[i];
            linkingFn(scope, element, attrs,
                linkingFn.require && getControllers(linkingFn.require, element));
          } catch (e) {
            $exceptionHandler(e, startingTag(element));
          }
        }
      }
    }
Once the directives have been collected their compile functions is executed. This method
is responsible for inlining directive templates as well as terminating the application
of the directives if the terminal directive has been reached..

@param {Array} directives Array of collected directives to execute their compile function.
       this needs to be pre-sorted by priority order.
@param {Node} templateNode The raw DOM node to apply the compile functions to
@param {Object} templateAttrs The shared attribute function
@param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
       scope argument is auto-generated to the new child of the transcluded parent scope.
@param {DOMElement} rootElement If we are working on the root of the compile tree then this
       argument has the root jqLite array so that we can replace widgets on it.
@returns linkingFn