text: 'use strict';
  
  /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
   *
   * DOM-related variables:
   *
   * - "node" - DOM Node
   * - "element" - DOM Element or Node
   * - "$node" or "$element" - jqLite-wrapped node or element
   *
   *
   * Compiler related stuff:
   *
   * - "linkFn" - linking fn of a single directive
   * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
   * - "childLinkFn" -  function that aggregates all linking fns for child nodes of a particular node
   * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
   */
  
  
  /**
   * @ngdoc function
   * @name ng.$compile
   * @function
   *
   * @description
   * Compiles a piece of HTML string or DOM into a template and produces a template function, which
   * can then be used to link {@link ng.$rootScope.Scope scope} and the template together.
   *
   * The compilation is a process of walking the DOM tree and trying to match DOM elements to
   * {@link ng.$compileProvider#directive directives}. For each match it
   * executes corresponding template function and collects the
   * instance functions into a single template function which is then returned.
   *
   * The template function can then be used once to produce the view or as it is the case with
   * {@link ng.directive:ngRepeat repeater} many-times, in which
   * case each call results in a view that is a DOM clone of the original template.
   *
   <doc:example module="compile">
     <doc:source>
      <script>
        // declare a new module, and inject the $compileProvider
        angular.module('compile', [], function($compileProvider) {
          // configure new 'compile' directive by passing a directive
          // factory function. The factory function injects the '$compile'
          $compileProvider.directive('compile', function($compile) {
            // directive factory creates a link function
            return function(scope, element, attrs) {
              scope.$watch(
                function(scope) {
                   // watch the 'compile' expression for changes
                  return scope.$eval(attrs.compile);
                },
                function(value) {
                  // when the 'compile' expression changes
                  // assign it into the current DOM
                  element.html(value);
  
                  // compile the new DOM and link it to the current
                  // scope.
                  // NOTE: we only compile .childNodes so that
                  // we don't get into infinite loop compiling ourselves
                  $compile(element.contents())(scope);
                }
              );
            };
          })
        });
  
        function Ctrl($scope) {
          $scope.name = 'Angular';
          $scope.html = 'Hello {{name}}';
        }
      </script>
      <div ng-controller="Ctrl">
        <input ng-model="name"> <br>
        <textarea ng-model="html"></textarea> <br>
        <div compile="html"></div>
      </div>
     </doc:source>
     <doc:scenario>
       it('should auto compile', function() {
         expect(element('div[compile]').text()).toBe('Hello Angular');
         input('html').enter('{{name}}!');
         expect(element('div[compile]').text()).toBe('Angular!');
       });
     </doc:scenario>
   </doc:example>
  
   *
   *
   * @param {string|DOMElement} element Element or HTML string to compile into a template function.
   * @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives.
   * @param {number} maxPriority only apply directives lower then given priority (Only effects the
   *                 root element(s), not their children)
   * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template
   * (a DOM element/tree) to a scope. Where:
   *
   *  * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
   *  * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the `template`
   *  and call the `cloneAttachFn` function allowing the caller to attach the
   *  cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
   *  called as: <br> `cloneAttachFn(clonedElement, scope)` where:
   *
   *      * `clonedElement` - is a clone of the original `element` passed into the compiler.
   *      * `scope` - is the current scope with which the linking function is working with.
   *
   * Calling the linking function returns the element of the template. It is either the original element
   * passed in, or the clone of the element if the `cloneAttachFn` is provided.
   *
   * After linking the view is not updated until after a call to $digest which typically is done by
   * Angular automatically.
   *
   * If you need access to the bound view, there are two ways to do it:
   *
   * - If you are not asking the linking function to clone the template, create the DOM element(s)
   *   before you send them to the compiler and keep this reference around.
   *   <pre>
   *     var element = $compile('<p>{{total}}</p>')(scope);
   *   </pre>
   *
   * - if on the other hand, you need the element to be cloned, the view reference from the original
   *   example would not point to the clone, but rather to the original template that was cloned. In
   *   this case, you can access the clone via the cloneAttachFn:
   *   <pre>
   *     var templateHTML = angular.element('<p>{{total}}</p>'),
   *         scope = ....;
   *
   *     var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
   *       //attach the clone to DOM document at the right place
   *     });
   *
   *     //now we have reference to the cloned DOM via `clone`
   *   </pre>
   *
   *
   * For information on how the compiler works, see the
   * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
   */
  
  var $compileMinErr = minErr('$compile');
  
  /**
   * @ngdoc service
   * @name ng.$compileProvider
   * @function
   *
   * @description
   */
  $CompileProvider.$inject = ['$provide'];
  function $CompileProvider($provide) {
    var hasDirectives = {},
        Suffix = 'Directive',
        COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
        CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
        aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
        imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//;
  
    // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
    // The assumption is that future DOM event attribute names will begin with
    // 'on' and be composed of only English letters.
    var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
  
    /**
     * @ngdoc function
     * @name ng.$compileProvider#directive
     * @methodOf ng.$compileProvider
     * @function
     *
     * @description
     * Register a new directive with the compiler.
     *
     * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
     *    will match as <code>ng-bind</code>), or an object map of directives where the keys are the
     *    names and the values are the factories.
     * @param {function|Array} directiveFactory An injectable directive factory function. See
     *    {@link guide/directive} for more info.
     * @returns {ng.$compileProvider} Self for chaining.
     */
     this.directive = function registerDirective(name, directiveFactory) {
      assertNotHasOwnProperty(name, 'directive');
      if (isString(name)) {
        assertArg(directiveFactory, 'directiveFactory');
        if (!hasDirectives.hasOwnProperty(name)) {
          hasDirectives[name] = [];
          $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
            function($injector, $exceptionHandler) {
              var directives = [];
              forEach(hasDirectives[name], function(directiveFactory, index) {
                try {
                  var directive = $injector.invoke(directiveFactory);
                  if (isFunction(directive)) {
                    directive = { compile: valueFn(directive) };
                  } else if (!directive.compile && directive.link) {
                    directive.compile = valueFn(directive.link);
                  }
                  directive.priority = directive.priority || 0;
                  directive.index = index;
                  directive.name = directive.name || name;
                  directive.require = directive.require || (directive.controller && directive.name);
                  directive.restrict = directive.restrict || 'A';
                  directives.push(directive);
                } catch (e) {
                  $exceptionHandler(e);
                }
              });
              return directives;
            }]);
        }
        hasDirectives[name].push(directiveFactory);
      } else {
        forEach(name, reverseParams(registerDirective));
      }
      return this;
    };
  
  
    /**
     * @ngdoc function
     * @name ng.$compileProvider#aHrefSanitizationWhitelist
     * @methodOf ng.$compileProvider
     * @function
     *
     * @description
     * Retrieves or overrides the default regular expression that is used for whitelisting of safe
     * urls during a[href] sanitization.
     *
     * The sanitization is a security measure aimed at prevent XSS attacks via html links.
     *
     * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
     * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
     * regular expression. If a match is found, the original url is written into the dom. Otherwise,
     * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
     *
     * @param {RegExp=} regexp New regexp to whitelist urls with.
     * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
     *    chaining otherwise.
     */
    this.aHrefSanitizationWhitelist = function(regexp) {
      if (isDefined(regexp)) {
        aHrefSanitizationWhitelist = regexp;
        return this;
      }
      return aHrefSanitizationWhitelist;
    };
  
  
    /**
     * @ngdoc function
     * @name ng.$compileProvider#imgSrcSanitizationWhitelist
     * @methodOf ng.$compileProvider
     * @function
     *
     * @description
     * Retrieves or overrides the default regular expression that is used for whitelisting of safe
     * urls during img[src] sanitization.
     *
     * The sanitization is a security measure aimed at prevent XSS attacks via html links.
     *
     * Any url about to be assigned to img[src] via data-binding is first normalized and turned into an
     * absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` regular
     * expression. If a match is found, the original url is written into the dom. Otherwise, the
     * absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
     *
     * @param {RegExp=} regexp New regexp to whitelist urls with.
     * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
     *    chaining otherwise.
     */
    this.imgSrcSanitizationWhitelist = function(regexp) {
      if (isDefined(regexp)) {
        imgSrcSanitizationWhitelist = regexp;
        return this;
      }
      return imgSrcSanitizationWhitelist;
    };
  
  
    this.$get = [
              '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
              '$controller', '$rootScope', '$document', '$sce', '$animate',
      function($injector,   $interpolate,   $exceptionHandler,   $http,   $templateCache,   $parse,
               $controller,   $rootScope,   $document,   $sce,   $animate) {
  
      var Attributes = function(element, attr) {
        this.$$element = element;
        this.$attr = attr || {};
      };
  
      Attributes.prototype = {
        $normalize: directiveNormalize,
  
  
        /**
         * @ngdoc function
         * @name ng.$compile.directive.Attributes#$addClass
         * @methodOf ng.$compile.directive.Attributes
         * @function
         *
         * @description
         * Adds the CSS class value specified by the classVal parameter to the element. If animations
         * are enabled then an animation will be triggered for the class addition.
         *
         * @param {string} classVal The className value that will be added to the element
         */
        $addClass : function(classVal) {
          if(classVal && classVal.length > 0) {
            $animate.addClass(this.$$element, classVal);
          }
        },
  
        /**
         * @ngdoc function
         * @name ng.$compile.directive.Attributes#$removeClass
         * @methodOf ng.$compile.directive.Attributes
         * @function
         *
         * @description
         * Removes the CSS class value specified by the classVal parameter from the element. If animations
         * are enabled then an animation will be triggered for the class removal.
         *
         * @param {string} classVal The className value that will be removed from the element
         */
        $removeClass : function(classVal) {
          if(classVal && classVal.length > 0) {
            $animate.removeClass(this.$$element, classVal);
          }
        },
  
        /**
         * Set a normalized attribute on the element in a way such that all directives
         * can share the attribute. This function properly handles boolean attributes.
         * @param {string} key Normalized key. (ie ngAttribute)
         * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
         * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
         *     Defaults to true.
         * @param {string=} attrName Optional none normalized name. Defaults to key.
         */
        $set: function(key, value, writeAttr, attrName) {
          //special case for class attribute addition + removal
          //so that class changes can tap into the animation
          //hooks provided by the $animate service
          if(key == 'class') {
            value = value || '';
            var current = this.$$element.attr('class') || '';
            this.$removeClass(tokenDifference(current, value).join(' '));
            this.$addClass(tokenDifference(value, current).join(' '));
          } else {
            var booleanKey = getBooleanAttrName(this.$$element[0], key),
                normalizedVal,
                nodeName;
  
            if (booleanKey) {
              this.$$element.prop(key, value);
              attrName = booleanKey;
            }
  
            this[key] = value;
  
            // translate normalized key to actual key
            if (attrName) {
              this.$attr[key] = attrName;
            } else {
              attrName = this.$attr[key];
              if (!attrName) {
                this.$attr[key] = attrName = snake_case(key, '-');
              }
            }
  
            nodeName = nodeName_(this.$$element);
  
            // sanitize a[href] and img[src] values
            if ((nodeName === 'A' && key === 'href') ||
                (nodeName === 'IMG' && key === 'src')) {
              // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
              if (!msie || msie >= 8 ) {
                normalizedVal = urlResolve(value).href;
                if (normalizedVal !== '') {
                  if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
                      (key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
                    this[key] = value = 'unsafe:' + normalizedVal;
                  }
                }
              }
            }
  
            if (writeAttr !== false) {
              if (value === null || value === undefined) {
                this.$$element.removeAttr(attrName);
              } else {
                this.$$element.attr(attrName, value);
              }
            }
          }
  
          // fire observers
          var $$observers = this.$$observers;
          $$observers && forEach($$observers[key], function(fn) {
            try {
              fn(value);
            } catch (e) {
              $exceptionHandler(e);
            }
          });
  
          function tokenDifference(str1, str2) {
            var values = [],
                tokens1 = str1.split(/\s+/),
                tokens2 = str2.split(/\s+/);
  
            outer:
            for(var i=0;i<tokens1.length;i++) {
              var token = tokens1[i];
              for(var j=0;j<tokens2.length;j++) {
                if(token == tokens2[j]) continue outer;
              }
              values.push(token);
            }
            return values;
          };
        },
  
  
        /**
         * @ngdoc function
         * @name ng.$compile.directive.Attributes#$observe
         * @methodOf ng.$compile.directive.Attributes
         * @function
         *
         * @description
         * Observes an interpolated attribute.
         *
         * The observer function will be invoked once during the next `$digest` following
         * compilation. The observer is then invoked whenever the interpolated value
         * changes.
         *
         * @param {string} key Normalized key. (ie ngAttribute) .
         * @param {function(interpolatedValue)} fn Function that will be called whenever
                  the interpolated value of the attribute changes.
         *        See the {@link guide/directive#Attributes Directives} guide for more info.
         * @returns {function()} the `fn` parameter.
         */
        $observe: function(key, fn) {
          var attrs = this,
              $$observers = (attrs.$$observers || (attrs.$$observers = {})),
              listeners = ($$observers[key] || ($$observers[key] = []));
  
          listeners.push(fn);
          $rootScope.$evalAsync(function() {
            if (!listeners.$$inter) {
              // no one registered attribute interpolation function, so lets call it manually
              fn(attrs[key]);
            }
          });
          return fn;
        }
      };
  
      var startSymbol = $interpolate.startSymbol(),
          endSymbol = $interpolate.endSymbol(),
          denormalizeTemplate = (startSymbol == '{{' || endSymbol  == '}}')
              ? identity
              : function denormalizeTemplate(template) {
                return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
          },
          NG_ATTR_BINDING = /^ngAttr[A-Z]/;
  
  
      return compile;
  
      //================================
  
      function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) {
        if (!($compileNodes instanceof jqLite)) {
          // jquery always rewraps, whereas we need to preserve the original selector so that we can modify it.
          $compileNodes = jqLite($compileNodes);
        }
        // We can not compile top level text elements since text nodes can be merged and we will
        // not be able to attach scope data to them, so we will wrap them in <span>
        forEach($compileNodes, function(node, index){
          if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
            $compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0];
          }
        });
        var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext);
        return function publicLinkFn(scope, cloneConnectFn){
          assertArg(scope, 'scope');
          // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
          // and sometimes changes the structure of the DOM.
          var $linkNode = cloneConnectFn
            ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
            : $compileNodes;
  
          // Attach scope only to non-text nodes.
          for(var i = 0, ii = $linkNode.length; i<ii; i++) {
            var node = $linkNode[i];
            if (node.nodeType == 1 /* element */ || node.nodeType == 9 /* document */) {
              $linkNode.eq(i).data('$scope', scope);
            }
          }
          safeAddClass($linkNode, 'ng-scope');
          if (cloneConnectFn) cloneConnectFn($linkNode, scope);
          if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
          return $linkNode;
        };
      }
  
      function safeAddClass($element, className) {
        try {
          $element.addClass(className);
        } catch(e) {
          // ignore, since it means that we are trying to set class on
          // SVG element, where class name is read-only.
        }
      }
  
      /**
       * Compile function matches each node in nodeList against the directives. Once all directives
       * for a particular node are collected their compile functions are executed. The compile
       * functions return values - the linking functions - are combined into a composite linking
       * function, which is the a linking function for the node.
       *
       * @param {NodeList} nodeList an array of nodes or NodeList to compile
       * @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 the nodeList is the root of the compilation tree then the
       *        rootElement must be set the jqLite collection of the compile root. This is
       *        needed so that the jqLite collection items can be replaced with widgets.
       * @param {number=} max directive priority
       * @returns {?function} A composite linking function of all of the matched directives or null.
       */
      function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, previousCompileContext) {
        var linkFns = [],
            nodeLinkFn, childLinkFn, directives, attrs, linkFnFound;
  
        for(var i = 0; i < nodeList.length; i++) {
          attrs = new Attributes();
  
          // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
          directives = collectDirectives(nodeList[i], [], attrs, i == 0 ? maxPriority : undefined, ignoreDirective);
  
          nodeLinkFn = (directives.length)
              ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, null, [], [], previousCompileContext)
              : null;
  
          childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes || !nodeList[i].childNodes.length)
              ? null
              : compileNodes(nodeList[i].childNodes,
                   nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
  
          linkFns.push(nodeLinkFn);
          linkFns.push(childLinkFn);
          linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn);
          previousCompileContext = null; //use the previous context only for the first element in the virtual group
        }
  
        // return a linking function if we have found anything, null otherwise
        return linkFnFound ? compositeLinkFn : null;
  
        function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
          var nodeLinkFn, childLinkFn, node, childScope, childTranscludeFn, i, ii, n;
  
          // copy nodeList so that linking doesn't break due to live list updates.
          var stableNodeList = [];
          for (i = 0, ii = nodeList.length; i < ii; i++) {
            stableNodeList.push(nodeList[i]);
          }
  
          for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
            node = stableNodeList[n];
            nodeLinkFn = linkFns[i++];
            childLinkFn = linkFns[i++];
  
            if (nodeLinkFn) {
              if (nodeLinkFn.scope) {
                childScope = scope.$new(isObject(nodeLinkFn.scope));
                jqLite(node).data('$scope', childScope);
              } else {
                childScope = scope;
              }
              childTranscludeFn = nodeLinkFn.transclude;
              if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
                nodeLinkFn(childLinkFn, childScope, node, $rootElement,
                    (function(transcludeFn) {
                      return function(cloneFn) {
                        var transcludeScope = scope.$new();
                        transcludeScope.$$transcluded = true;
  
                        return transcludeFn(transcludeScope, cloneFn).
                            on('$destroy', bind(transcludeScope, transcludeScope.$destroy));
                      };
                    })(childTranscludeFn || transcludeFn)
                );
              } else {
                nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
              }
            } else if (childLinkFn) {
              childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
            }
          }
        }
      }
  
  
      /**
       * Looks for directives on the given node and adds them to the directive collection which is
       * sorted.
       *
       * @param node Node to search.
       * @param directives An array to which the directives are added to. This array is sorted before
       *        the function returns.
       * @param attrs The shared attrs object which is used to populate the normalized attributes.
       * @param {number=} maxPriority Max directive priority.
       */
      function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
        var nodeType = node.nodeType,
            attrsMap = attrs.$attr,
            match,
            className;
  
        switch(nodeType) {
          case 1: /* Element */
            // use the node name: <directive>
            addDirective(directives,
                directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective);
  
            // iterate over the attributes
            for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
                     j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
              var attrStartName = false;
              var attrEndName = false;
  
              attr = nAttrs[j];
              if (!msie || msie >= 8 || attr.specified) {
                name = attr.name;
                // support ngAttr attribute binding
                ngAttrName = directiveNormalize(name);
                if (NG_ATTR_BINDING.test(ngAttrName)) {
                  name = snake_case(ngAttrName.substr(6), '-');
                }
  
                var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
                if (ngAttrName === directiveNName + 'Start') {
                  attrStartName = name;
                  attrEndName = name.substr(0, name.length - 5) + 'end';
                  name = name.substr(0, name.length - 6);
                }
  
                nName = directiveNormalize(name.toLowerCase());
                attrsMap[nName] = name;
                attrs[nName] = value = trim((msie && name == 'href')
                  ? decodeURIComponent(node.getAttribute(name, 2))
                  : attr.value);
                if (getBooleanAttrName(node, nName)) {
                  attrs[nName] = true; // presence means true
                }
                addAttrInterpolateDirective(node, directives, value, nName);
                addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, attrEndName);
              }
            }
  
            // use class as directive
            className = node.className;
            if (isString(className) && className !== '') {
              while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
                nName = directiveNormalize(match[2]);
                if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
                  attrs[nName] = trim(match[3]);
                }
                className = className.substr(match.index + match[0].length);
              }
            }
            break;
          case 3: /* Text Node */
            addTextInterpolateDirective(directives, node.nodeValue);
            break;
          case 8: /* Comment */
            try {
              match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
              if (match) {
                nName = directiveNormalize(match[1]);
                if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
                  attrs[nName] = trim(match[2]);
                }
              }
            } catch (e) {
              // turns out that under some circumstances IE9 throws errors when one attempts to read comment's node value.
              // Just ignore it and continue. (Can't seem to reproduce in test case.)
            }
            break;
        }
  
        directives.sort(byPriority);
        return directives;
      }
  
      /**
       * Given a node with an directive-start it collects all of the siblings until it find directive-end.
       * @param node
       * @param attrStart
       * @param attrEnd
       * @returns {*}
       */
      function groupScan(node, attrStart, attrEnd) {
        var nodes = [];
        var depth = 0;
        if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
          var startNode = node;
          do {
            if (!node) {
              throw $compileMinErr('uterdir', "Unterminated attribute, found '{0}' but no matching '{1}' found.", attrStart, attrEnd);
            }
            if (node.nodeType == 1 /** Element **/) {
              if (node.hasAttribute(attrStart)) depth++;
              if (node.hasAttribute(attrEnd)) depth--;
            }
            nodes.push(node);
            node = node.nextSibling;
          } while (depth > 0);
        } else {
          nodes.push(node);
        }
  
        return jqLite(nodes);
      }
  
      /**
       * Wrapper for linking function which converts normal linking function into a grouped
       * linking function.
       * @param linkFn
       * @param attrStart
       * @param attrEnd
       * @returns {Function}
       */
      function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
        return function(scope, element, attrs, controllers) {
          element = groupScan(element[0], attrStart, attrEnd);
          return linkFn(scope, element, attrs, controllers);
        }
      }
  
      /**
       * Once the directives have been collected, their compile functions are 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} compileNode 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 {JQLite} jqCollection If we are working on the root of the compile tree then this
       *        argument has the root jqLite array so that we can replace nodes on it.
       * @param {Object=} originalReplaceDirective An optional directive that will be ignored when compiling
       *        the transclusion.
       * @param {Array.<Function>} preLinkFns
       * @param {Array.<Function>} postLinkFns
       * @param {Object} previousCompileContext Context used for previous compilation of the current node
       * @returns linkFn
       */
      function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection,
          originalReplaceDirective, preLinkFns, postLinkFns, previousCompileContext) {
        previousCompileContext = previousCompileContext || {};
  
        var terminalPriority = -Number.MAX_VALUE,
            newScopeDirective,
            newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
            templateDirective = previousCompileContext.templateDirective,
            $compileNode = templateAttrs.$$element = jqLite(compileNode),
            directive,
            directiveName,
            $template,
            transcludeDirective = previousCompileContext.transcludeDirective,
            replaceDirective = originalReplaceDirective,
            childTranscludeFn = transcludeFn,
            controllerDirectives,
            linkFn,
            directiveValue;
  
        // executes all directives on the current element
        for(var i = 0, ii = directives.length; i < ii; i++) {
          directive = directives[i];
          var attrStart = directive.$$start;
          var attrEnd = directive.$$end;
  
          // collect multiblock sections
          if (attrStart) {
            $compileNode = groupScan(compileNode, attrStart, attrEnd)
          }
          $template = undefined;
  
          if (terminalPriority > directive.priority) {
            break; // prevent further processing of directives
          }
  
          if (directiveValue = directive.scope) {
            newScopeDirective = newScopeDirective || directive;
  
            // skip the check for directives with async templates, we'll check the derived sync directive when
            // the template arrives
            if (!directive.templateUrl) {
              assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, $compileNode);
              if (isObject(directiveValue)) {
                safeAddClass($compileNode, 'ng-isolate-scope');
                newIsolateScopeDirective = directive;
              }
              safeAddClass($compileNode, 'ng-scope');
            }
          }
  
          directiveName = directive.name;
  
          if (!directive.templateUrl && directive.controller) {
            directiveValue = directive.controller;
            controllerDirectives = controllerDirectives || {};
            assertNoDuplicate("'" + directiveName + "' controller",
                controllerDirectives[directiveName], directive, $compileNode);
            controllerDirectives[directiveName] = directive;
          }
  
          if (directiveValue = directive.transclude) {
            // Special case ngRepeat so that we don't complain about duplicate transclusion, ngRepeat knows how to handle
            // this on its own.
            if (directiveName !== 'ngRepeat') {
              assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode);
              transcludeDirective = directive;
            }
  
            if (directiveValue == 'element') {
              terminalPriority = directive.priority;
              $template = groupScan(compileNode, attrStart, attrEnd);
              $compileNode = templateAttrs.$$element =
                  jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' '));
              compileNode = $compileNode[0];
              replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode);
  
              childTranscludeFn = compile($template, transcludeFn, terminalPriority,
                                          replaceDirective && replaceDirective.name, {
                                            newIsolateScopeDirective: newIsolateScopeDirective,
                                            transcludeDirective: transcludeDirective,
                                            templateDirective: templateDirective
                                          });
            } else {
              $template = jqLite(JQLiteClone(compileNode)).contents();
              $compileNode.html(''); // clear contents
              childTranscludeFn = compile($template, transcludeFn);
            }
          }
  
          if (directive.template) {
            assertNoDuplicate('template', templateDirective, directive, $compileNode);
            templateDirective = directive;
  
            directiveValue = (isFunction(directive.template))
                ? directive.template($compileNode, templateAttrs)
                : directive.template;
  
            directiveValue = denormalizeTemplate(directiveValue);
  
            if (directive.replace) {
              replaceDirective = directive;
              $template = jqLite('<div>' +
                                   trim(directiveValue) +
                                 '</div>').contents();
              compileNode = $template[0];
  
              if ($template.length != 1 || compileNode.nodeType !== 1) {
                throw $compileMinErr('tplrt', "Template for directive '{0}' must have exactly one root element. {1}", directiveName, '');
              }
  
              replaceWith(jqCollection, $compileNode, compileNode);
  
              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(
                      compileNode,
                      directives.splice(i + 1, directives.length - (i + 1)),
                      newTemplateAttrs
                  )
              );
              mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
  
              ii = directives.length;
            } else {
              $compileNode.html(directiveValue);
            }
          }
  
          if (directive.templateUrl) {
            assertNoDuplicate('template', templateDirective, directive, $compileNode);
            templateDirective = directive;
  
            if (directive.replace) {
              replaceDirective = directive;
            }
  
            nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
                templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, {
                  newIsolateScopeDirective: newIsolateScopeDirective,
                  transcludeDirective: transcludeDirective,
                  templateDirective: templateDirective
                });
            ii = directives.length;
          } else if (directive.compile) {
            try {
              linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
              if (isFunction(linkFn)) {
                addLinkFns(null, linkFn, attrStart, attrEnd);
              } else if (linkFn) {
                addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
              }
            } catch (e) {
              $exceptionHandler(e, startingTag($compileNode));
            }
          }
  
          if (directive.terminal) {
            nodeLinkFn.terminal = true;
            terminalPriority = Math.max(terminalPriority, directive.priority);
          }
  
        }
  
        nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope;
        nodeLinkFn.transclude = transcludeDirective && childTranscludeFn;
  
        // might be normal or delayed nodeLinkFn depending on if templateUrl is present
        return nodeLinkFn;
  
        ////////////////////
  
        function addLinkFns(pre, post, attrStart, attrEnd) {
          if (pre) {
            if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
            pre.require = directive.require;
            preLinkFns.push(pre);
          }
          if (post) {
            if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
            post.require = directive.require;
            postLinkFns.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 ($element[0].nodeType == 8 && $element[0].$$controller) { // Transclusion comment node
              value = value || $element[0].$$controller;
              $element[0].$$controller = null;
            }
  
            if (!value && !optional) {
              throw $compileMinErr('ctreq', "Controller '{0}', required by directive '{1}', can't be found!", require, directiveName);
            }
            return value;
          } else if (isArray(require)) {
            value = [];
            forEach(require, function(require) {
              value.push(getControllers(require, $element));
            });
          }
          return value;
        }
  
  
        function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
          var attrs, $element, i, ii, linkFn, controller;
  
          if (compileNode === linkNode) {
            attrs = templateAttrs;
          } else {
            attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));
          }
          $element = attrs.$$element;
  
          if (newIsolateScopeDirective) {
            var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
  
            var parentScope = scope.$parent || scope;
  
            forEach(newIsolateScopeDirective.scope, function(definition, scopeName) {
              var match = definition.match(LOCAL_REGEXP) || [],
                  attrName = match[3] || scopeName,
                  optional = (match[2] == '?'),
                  mode = match[1], // @, =, or &
                  lastValue,
                  parentGet, parentSet;
  
              scope.$$isolateBindings[scopeName] = mode + attrName;
  
              switch (mode) {
  
                case '@': {
                  attrs.$observe(attrName, function(value) {
                    scope[scopeName] = value;
                  });
                  attrs.$$observers[attrName].$$scope = parentScope;
                  if( attrs[attrName] ) {
                    // If the attribute has been provided then we trigger an interpolation to ensure the value is there for use in the link fn
                    scope[scopeName] = $interpolate(attrs[attrName])(parentScope);
                  }
                  break;
                }
  
                case '=': {
                  if (optional && !attrs[attrName]) {
                    return;
                  }
                  parentGet = $parse(attrs[attrName]);
                  parentSet = parentGet.assign || function() {
                    // reset the change, or we will throw this exception on every $digest
                    lastValue = scope[scopeName] = parentGet(parentScope);
                    throw $compileMinErr('nonassign', "Expression '{0}' used with directive '{1}' is non-assignable!",
                        attrs[attrName], newIsolateScopeDirective.name);
                  };
                  lastValue = scope[scopeName] = parentGet(parentScope);
                  scope.$watch(function parentValueWatch() {
                    var parentValue = parentGet(parentScope);
  
                    if (parentValue !== scope[scopeName]) {
                      // we are out of sync and need to copy
                      if (parentValue !== lastValue) {
                        // parent changed and it has precedence
                        lastValue = scope[scopeName] = parentValue;
                      } else {
                        // if the parent can be assigned then do so
                        parentSet(parentScope, parentValue = lastValue = scope[scopeName]);
                      }
                    }
                    return parentValue;
                  });
                  break;
                }
  
                case '&': {
                  parentGet = $parse(attrs[attrName]);
                  scope[scopeName] = function(locals) {
                    return parentGet(parentScope, locals);
                  };
                  break;
                }
  
                default: {
                  throw $compileMinErr('iscp', "Invalid isolate scope definition for directive '{0}'. Definition: {... {1}: '{2}' ...}",
                      newIsolateScopeDirective.name, scopeName, definition);
                }
              }
            });
          }
  
          if (controllerDirectives) {
            forEach(controllerDirectives, function(directive) {
              var locals = {
                $scope: scope,
                $element: $element,
                $attrs: attrs,
                $transclude: boundTranscludeFn
              }, controllerInstance;
  
              controller = directive.controller;
              if (controller == '@') {
                controller = attrs[directive.name];
              }
  
              controllerInstance = $controller(controller, locals);
  
              // Directives with element transclusion and a controller need to attach controller
              // to the comment node created by the compiler, but jQuery .data doesn't support
              // attaching data to comment nodes so instead we set it directly on the element and
              // remove it after we read it later.
              if ($element[0].nodeType == 8) { // Transclusion comment node
                $element[0].$$controller = controllerInstance;
              } else {
                $element.data('$' + directive.name + 'Controller', controllerInstance);
              }
              if (directive.controllerAs) {
                locals.$scope[directive.controllerAs] = controllerInstance;
              }
            });
          }
  
          // PRELINKING
          for(i = 0, ii = preLinkFns.length; i < ii; i++) {
            try {
              linkFn = preLinkFns[i];
              linkFn(scope, $element, attrs,
                  linkFn.require && getControllers(linkFn.require, $element));
            } catch (e) {
              $exceptionHandler(e, startingTag($element));
            }
          }
  
          // RECURSION
          childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);
  
          // POSTLINKING
          for(i = postLinkFns.length - 1; i >= 0; i--) {
            try {
              linkFn = postLinkFns[i];
              linkFn(scope, $element, attrs,
                  linkFn.require && getControllers(linkFn.require, $element));
            } catch (e) {
              $exceptionHandler(e, startingTag($element));
            }
          }
        }
      }
  
  
      /**
       * looks up the directive and decorates it with exception handling and proper parameters. We
       * call this the boundDirective.
       *
       * @param {string} name name of the directive to look up.
       * @param {string} location The directive must be found in specific format.
       *   String containing any of theses characters:
       *
       *   * `E`: element name
       *   * `A': attribute
       *   * `C`: class
       *   * `M`: comment
       * @returns true if directive was added.
       */
      function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, endAttrName) {
        if (name === ignoreDirective) return null;
        var match = null;
        if (hasDirectives.hasOwnProperty(name)) {
          for(var directive, directives = $injector.get(name + Suffix),
              i = 0, ii = directives.length; i<ii; i++) {
            try {
              directive = directives[i];
              if ( (maxPriority === undefined || maxPriority > directive.priority) &&
                   directive.restrict.indexOf(location) != -1) {
                if (startAttrName) {
                  directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
                }
                tDirectives.push(directive);
                match = directive;
              }
            } catch(e) { $exceptionHandler(e); }
          }
        }
        return match;
      }
  
  
      /**
       * When the element is replaced with HTML template then the new attributes
       * on the template need to be merged with the existing attributes in the DOM.
       * The desired effect is to have both of the attributes present.
       *
       * @param {object} dst destination attributes (original DOM)
       * @param {object} src source attributes (from the directive template)
       */
      function mergeTemplateAttributes(dst, src) {
        var srcAttr = src.$attr,
            dstAttr = dst.$attr,
            $element = dst.$$element;
  
        // reapply the old attributes to the new element
        forEach(dst, function(value, key) {
          if (key.charAt(0) != '$') {
            if (src[key]) {
              value += (key === 'style' ? ';' : ' ') + src[key];
            }
            dst.$set(key, value, true, srcAttr[key]);
          }
        });
  
        // copy the new attributes on the old attrs object
        forEach(src, function(value, key) {
          if (key == 'class') {
            safeAddClass($element, value);
            dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
          } else if (key == 'style') {
            $element.attr('style', $element.attr('style') + ';' + value);
            // `dst` will never contain hasOwnProperty as DOM parser won't let it.
            // You will get an "InvalidCharacterError: DOM Exception 5" error if you
            // have an attribute like "has-own-property" or "data-has-own-property", etc.
          } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
            dst[key] = value;
            dstAttr[key] = srcAttr[key];
          }
        });
      }
  
  
      function compileTemplateUrl(directives, $compileNode, tAttrs,
          $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
        var linkQueue = [],
            afterTemplateNodeLinkFn,
            afterTemplateChildLinkFn,
            beforeTemplateCompileNode = $compileNode[0],
            origAsyncDirective = directives.shift(),
            // The fact that we have to copy and patch the directive seems wrong!
            derivedSyncDirective = extend({}, origAsyncDirective, {
              templateUrl: null, transclude: null, replace: null
            }),
            templateUrl = (isFunction(origAsyncDirective.templateUrl))
                ? origAsyncDirective.templateUrl($compileNode, tAttrs)
                : origAsyncDirective.templateUrl;
  
        $compileNode.html('');
  
        $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}).
          success(function(content) {
            var compileNode, tempTemplateAttrs, $template;
  
            content = denormalizeTemplate(content);
  
            if (origAsyncDirective.replace) {
              $template = jqLite('<div>' + trim(content) + '</div>').contents();
              compileNode = $template[0];
  
              if ($template.length != 1 || compileNode.nodeType !== 1) {
                throw $compileMinErr('tplrt', "Template for directive '{0}' must have exactly one root element. {1}",
                    origAsyncDirective.name, templateUrl);
              }
  
              tempTemplateAttrs = {$attr: {}};
              replaceWith($rootElement, $compileNode, compileNode);
              collectDirectives(compileNode, directives, tempTemplateAttrs);
              mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
            } else {
              compileNode = beforeTemplateCompileNode;
              $compileNode.html(content);
            }
  
            directives.unshift(derivedSyncDirective);
  
            afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
                childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, previousCompileContext);
            forEach($rootElement, function(node, i) {
              if (node == compileNode) {
                $rootElement[i] = $compileNode[0];
              }
            });
            afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
  
  
            while(linkQueue.length) {
              var scope = linkQueue.shift(),
                  beforeTemplateLinkNode = linkQueue.shift(),
                  linkRootElement = linkQueue.shift(),
                  controller = linkQueue.shift(),
                  linkNode = $compileNode[0];
  
              if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
                // it was cloned therefore we have to clone as well.
                linkNode = JQLiteClone(compileNode);
                replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
              }
  
              afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller);
            }
            linkQueue = null;
          }).
          error(function(response, code, headers, config) {
            throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url);
          });
  
        return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) {
          if (linkQueue) {
            linkQueue.push(scope);
            linkQueue.push(node);
            linkQueue.push(rootElement);
            linkQueue.push(controller);
          } else {
            afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller);
          }
        };
      }
  
  
      /**
       * Sorting function for bound directives.
       */
      function byPriority(a, b) {
        var diff = b.priority - a.priority;
        if (diff !== 0) return diff;
        if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
        return a.index - b.index;
      }
  
  
      function assertNoDuplicate(what, previousDirective, directive, element) {
        if (previousDirective) {
          throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}',
              previousDirective.name, directive.name, what, startingTag(element));
        }
      }
  
  
      function addTextInterpolateDirective(directives, text) {
        var interpolateFn = $interpolate(text, true);
        if (interpolateFn) {
          directives.push({
            priority: 0,
            compile: valueFn(function textInterpolateLinkFn(scope, node) {
              var parent = node.parent(),
                  bindings = parent.data('$binding') || [];
              bindings.push(interpolateFn);
              safeAddClass(parent.data('$binding', bindings), 'ng-binding');
              scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
                node[0].nodeValue = value;
              });
            })
          });
        }
      }
  
  
      function getTrustedContext(node, attrNormalizedName) {
        // maction[xlink:href] can source SVG.  It's not limited to <maction>.
        if (attrNormalizedName == "xlinkHref" ||
            (nodeName_(node) != "IMG" && (attrNormalizedName == "src" ||
                                          attrNormalizedName == "ngSrc"))) {
          return $sce.RESOURCE_URL;
        }
      }
  
  
      function addAttrInterpolateDirective(node, directives, value, name) {
        var interpolateFn = $interpolate(value, true);
  
        // no interpolation found -> ignore
        if (!interpolateFn) return;
  
  
        if (name === "multiple" && nodeName_(node) === "SELECT") {
          throw $compileMinErr("selmulti", "Binding to the 'multiple' attribute is not supported. Element: {0}",
              startingTag(node));
        }
  
        directives.push({
          priority: -100,
          compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) {
            var $$observers = (attr.$$observers || (attr.$$observers = {}));
  
            if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
              throw $compileMinErr('nodomevents',
                  "Interpolations for HTML DOM event attributes are disallowed.  Please use the ng- " +
                  "versions (such as ng-click instead of onclick) instead.");
            }
  
            // we need to interpolate again, in case the attribute value has been updated
            // (e.g. by another directive's compile function)
            interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name));
  
            // if attribute was updated so that there is no interpolation going on we don't want to
            // register any observers
            if (!interpolateFn) return;
  
            // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the actual attr value
            attr[name] = interpolateFn(scope);
            ($$observers[name] || ($$observers[name] = [])).$$inter = true;
            (attr.$$observers && attr.$$observers[name].$$scope || scope).
              $watch(interpolateFn, function interpolateFnWatchAction(value) {
                attr.$set(name, value);
              });
          })
        });
      }
  
  
      /**
       * This is a special jqLite.replaceWith, which can replace items which
       * have no parents, provided that the containing jqLite collection is provided.
       *
       * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
       *    in the root of the tree.
       * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep the shell,
       *    but replace its DOM node reference.
       * @param {Node} newNode The new DOM node.
       */
      function replaceWith($rootElement, elementsToRemove, newNode) {
        var firstElementToRemove = elementsToRemove[0],
            removeCount = elementsToRemove.length,
            parent = firstElementToRemove.parentNode,
            i, ii;
  
        if ($rootElement) {
          for(i = 0, ii = $rootElement.length; i < ii; i++) {
            if ($rootElement[i] == firstElementToRemove) {
              $rootElement[i++] = newNode;
              for (var j = i, j2 = j + removeCount - 1,
                       jj = $rootElement.length;
                   j < jj; j++, j2++) {
                if (j2 < jj) {
                  $rootElement[j] = $rootElement[j2];
                } else {
                  delete $rootElement[j];
                }
              }
              $rootElement.length -= removeCount - 1;
              break;
            }
          }
        }
  
        if (parent) {
          parent.replaceChild(newNode, firstElementToRemove);
        }
        var fragment = document.createDocumentFragment();
        fragment.appendChild(firstElementToRemove);
        newNode[jqLite.expando] = firstElementToRemove[jqLite.expando];
        for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
          var element = elementsToRemove[k];
          jqLite(element).remove(); // must do this way to clean up expando
          fragment.appendChild(element);
          delete elementsToRemove[k];
        }
  
        elementsToRemove[0] = newNode;
        elementsToRemove.length = 1
      }
    }];
  }
  
  var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
  /**
   * Converts all accepted directives format into proper directive name.
   * All of these will become 'myDirective':
   *   my:Directive
   *   my-directive
   *   x-my-directive
   *   data-my:directive
   *
   * Also there is special case for Moz prefix starting with upper case letter.
   * @param name Name to normalize
   */
  function directiveNormalize(name) {
    return camelCase(name.replace(PREFIX_REGEXP, ''));
  }
  
  /**
   * @ngdoc object
   * @name ng.$compile.directive.Attributes
   * @description
   *
   * A shared object between directive compile / linking functions which contains normalized DOM element
   * attributes. The the values reflect current binding state `{{ }}`. The normalization is needed
   * since all of these are treated as equivalent in Angular:
   *
   *          <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
   */
  
  /**
   * @ngdoc property
   * @name ng.$compile.directive.Attributes#$attr
   * @propertyOf ng.$compile.directive.Attributes
   * @returns {object} A map of DOM element attribute names to the normalized name. This is
   *          needed to do reverse lookup from normalized name back to actual name.
   */
  
  
  /**
   * @ngdoc function
   * @name ng.$compile.directive.Attributes#$set
   * @methodOf ng.$compile.directive.Attributes
   * @function
   *
   * @description
   * Set DOM element attribute value.
   *
   *
   * @param {string} name Normalized element attribute name of the property to modify. The name is
   *          revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
   *          property to the original name.
   * @param {string} value Value to set the attribute to. The value can be an interpolated string.
   */
  
  
  
  /**
   * Closure compiler type information
   */
  
  function nodesetLinkingFn(
    /* angular.Scope */ scope,
    /* NodeList */ nodeList,
    /* Element */ rootElement,
    /* function(Function) */ boundTranscludeFn
  ){}
  
  function directiveLinkingFn(
    /* nodesetLinkingFn */ nodesetLinkingFn,
    /* angular.Scope */ scope,
    /* Node */ node,
    /* Element */ rootElement,
    /* function(Function) */ boundTranscludeFn
  ){}
  