/**

* @license AngularJS v1.1.5
* (c) 2010-2012 Google, Inc. http://angularjs.org
* License: MIT
*/

(function(window, angular, undefined) { 'use strict';

/**

* @ngdoc overview
* @name ngSanitize
* @description
*/

/*

* HTML Parser By Misko Hevery (misko@hevery.com)
* based on:  HTML Parser By John Resig (ejohn.org)
* Original code by Erik Arvidsson, Mozilla Public License
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
*
* // Use like so:
* htmlParser(htmlString, {
*     start: function(tag, attrs, unary) {},
*     end: function(tag) {},
*     chars: function(text) {},
*     comment: function(text) {}
* });
*
*/

/**

* @ngdoc service
* @name ngSanitize.$sanitize
* @function
*
* @description
*   The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
*   then serialized back to properly escaped html string. This means that no unsafe input can make
*   it into the returned string, however, since our parser is more strict than a typical browser
*   parser, it's possible that some obscure input, which would be recognized as valid HTML by a
*   browser, won't make it through the sanitizer.
*
* @param {string} html Html input.
* @returns {string} Sanitized html.
*
* @example
  <doc:example module="ngSanitize">
    <doc:source>
      <script>
        function Ctrl($scope) {
          $scope.snippet =
            '<p style="color:blue">an html\n' +
            '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
            'snippet</p>';
        }
      </script>
      <div ng-controller="Ctrl">
         Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
          <table>
            <tr>
              <td>Filter</td>
              <td>Source</td>
              <td>Rendered</td>
            </tr>
            <tr id="html-filter">
              <td>html filter</td>
              <td>
                <pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre>
              </td>
              <td>
                <div ng-bind-html="snippet"></div>
              </td>
            </tr>
            <tr id="escaped-html">
              <td>no filter</td>
              <td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
              <td><div ng-bind="snippet"></div></td>
            </tr>
            <tr id="html-unsafe-filter">
              <td>unsafe html filter</td>
              <td><pre>&lt;div ng-bind-html-unsafe="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
              <td><div ng-bind-html-unsafe="snippet"></div></td>
            </tr>
          </table>
        </div>
    </doc:source>
    <doc:scenario>
      it('should sanitize the html snippet ', function() {
        expect(using('#html-filter').element('div').html()).
          toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
      });

      it('should escape snippet without any filter', function() {
        expect(using('#escaped-html').element('div').html()).
          toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
               "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
               "snippet&lt;/p&gt;");
      });

      it('should inline raw snippet if filtered as unsafe', function() {
        expect(using('#html-unsafe-filter').element("div").html()).
          toBe("<p style=\"color:blue\">an html\n" +
               "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
               "snippet</p>");
      });

      it('should update', function() {
        input('snippet').enter('new <b>text</b>');
        expect(using('#html-filter').binding('snippet')).toBe('new <b>text</b>');
        expect(using('#escaped-html').element('div').html()).toBe("new &lt;b&gt;text&lt;/b&gt;");
        expect(using('#html-unsafe-filter').binding("snippet")).toBe('new <b>text</b>');
      });
    </doc:scenario>
  </doc:example>
*/

var $sanitize = function(html) {

var buf = [];
  htmlParser(html, htmlSanitizeWriter(buf));
  return buf.join('');

};

// Regular Expressions for parsing tags and attributes var START_TAG_REGEXP = /^<s*(+)((?:s++(?:s*=s*(?:(?:”*“)|(?:'[^']*')|[^>s]+))?)*)s*(/?)s*>/,

END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
BEGIN_TAG_REGEXP = /^</,
BEGING_END_TAGE_REGEXP = /^<\s*\//,
COMMENT_REGEXP = /<!--(.*?)-->/g,
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
URI_REGEXP = /^((ftp|https?):\/\/|mailto:|tel:|#)/,
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character)

// Good source of info about elements and attributes // dev.w3.org/html5/spec/Overview.html#semantics // simon.html5.org/html-elements

// Safe Void Elements - HTML5 // dev.w3.org/html5/spec/Overview.html#void-elements var voidElements = makeMap(“area,br,col,hr,img,wbr”);

// Elements that you can, intentionally, leave open (and which close themselves) // dev.w3.org/html5/spec/Overview.html#optional-tags var optionalEndTagBlockElements = makeMap(“colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr”),

optionalEndTagInlineElements = makeMap("rp,rt"),
optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements);

// Safe Block Elements - HTML5 var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap(“address,article,aside,” +

"blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6," +
"header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));

// Inline Elements - HTML5 var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap(“a,abbr,acronym,b,bdi,bdo,” +

"big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small," +
"span,strike,strong,sub,sup,time,tt,u,var"));

// Special Elements (can contain anything) var specialElements = makeMap(“script,style”);

var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements);

//Attributes that have href and hence need to be sanitized var uriAttrs = makeMap(“background,cite,href,longdesc,src,usemap”); var validAttrs = angular.extend({}, uriAttrs, makeMap(

'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
'scope,scrolling,shape,span,start,summary,target,title,type,'+
'valign,value,vspace,width'));

function makeMap(str) {

var obj = {}, items = str.split(','), i;
for (i = 0; i < items.length; i++) obj[items[i]] = true;
return obj;

}

/**

* @example
* htmlParser(htmlString, {
*     start: function(tag, attrs, unary) {},
*     end: function(tag) {},
*     chars: function(text) {},
*     comment: function(text) {}
* });
*
* @param {string} html string
* @param {object} handler
*/

function htmlParser( html, handler ) {

var index, chars, match, stack = [], last = html;
stack.last = function() { return stack[ stack.length - 1 ]; };

while ( html ) {
  chars = true;

  // Make sure we're not in a script or style element
  if ( !stack.last() || !specialElements[ stack.last() ] ) {

    // Comment
    if ( html.indexOf("<!--") === 0 ) {
      index = html.indexOf("-->");

      if ( index >= 0 ) {
        if (handler.comment) handler.comment( html.substring( 4, index ) );
        html = html.substring( index + 3 );
        chars = false;
      }

    // end tag
    } else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
      match = html.match( END_TAG_REGEXP );

      if ( match ) {
        html = html.substring( match[0].length );
        match[0].replace( END_TAG_REGEXP, parseEndTag );
        chars = false;
      }

    // start tag
    } else if ( BEGIN_TAG_REGEXP.test(html) ) {
      match = html.match( START_TAG_REGEXP );

      if ( match ) {
        html = html.substring( match[0].length );
        match[0].replace( START_TAG_REGEXP, parseStartTag );
        chars = false;
      }
    }

    if ( chars ) {
      index = html.indexOf("<");

      var text = index < 0 ? html : html.substring( 0, index );
      html = index < 0 ? "" : html.substring( index );

      if (handler.chars) handler.chars( decodeEntities(text) );
    }

  } else {
    html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){
      text = text.
        replace(COMMENT_REGEXP, "$1").
        replace(CDATA_REGEXP, "$1");

      if (handler.chars) handler.chars( decodeEntities(text) );

      return "";
    });

    parseEndTag( "", stack.last() );
  }

  if ( html == last ) {
    throw "Parse Error: " + html;
  }
  last = html;
}

// Clean up any remaining tags
parseEndTag();

function parseStartTag( tag, tagName, rest, unary ) {
  tagName = angular.lowercase(tagName);
  if ( blockElements[ tagName ] ) {
    while ( stack.last() && inlineElements[ stack.last() ] ) {
      parseEndTag( "", stack.last() );
    }
  }

  if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
    parseEndTag( "", tagName );
  }

  unary = voidElements[ tagName ] || !!unary;

  if ( !unary )
    stack.push( tagName );

  var attrs = {};

  rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) {
    var value = doubleQuotedValue
      || singleQoutedValue
      || unqoutedValue
      || '';

    attrs[name] = decodeEntities(value);
  });
  if (handler.start) handler.start( tagName, attrs, unary );
}

function parseEndTag( tag, tagName ) {
  var pos = 0, i;
  tagName = angular.lowercase(tagName);
  if ( tagName )
    // Find the closest opened tag of the same type
    for ( pos = stack.length - 1; pos >= 0; pos-- )
      if ( stack[ pos ] == tagName )
        break;

  if ( pos >= 0 ) {
    // Close all the open elements, up the stack
    for ( i = stack.length - 1; i >= pos; i-- )
      if (handler.end) handler.end( stack[ i ] );

    // Remove the open elements from the stack
    stack.length = pos;
  }
}

}

/**

* decodes all entities into regular string
* @param value
* @returns {string} A string with decoded entities.
*/

var hiddenPre=document.createElement(“pre”); function decodeEntities(value) {

hiddenPre.innerHTML=value.replace(/</g,"&lt;");
return hiddenPre.innerText || hiddenPre.textContent || '';

}

/**

* Escapes all potentially dangerous characters, so that the
* resulting string can be safely inserted into attribute or
* element text.
* @param value
* @returns escaped text
*/

function encodeEntities(value) {

return value.
  replace(/&/g, '&amp;').
  replace(NON_ALPHANUMERIC_REGEXP, function(value){
    return '&#' + value.charCodeAt(0) + ';';
  }).
  replace(/</g, '&lt;').
  replace(/>/g, '&gt;');

}

/**

* create an HTML/XML writer which writes to buffer
* @param {Array} buf use buf.jain('') to get out sanitized html string
* @returns {object} in the form of {
*     start: function(tag, attrs, unary) {},
*     end: function(tag) {},
*     chars: function(text) {},
*     comment: function(text) {}
* }
*/

function htmlSanitizeWriter(buf){

var ignore = false;
var out = angular.bind(buf, buf.push);
return {
  start: function(tag, attrs, unary){
    tag = angular.lowercase(tag);
    if (!ignore && specialElements[tag]) {
      ignore = tag;
    }
    if (!ignore && validElements[tag] == true) {
      out('<');
      out(tag);
      angular.forEach(attrs, function(value, key){
        var lkey=angular.lowercase(key);
        if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
          out(' ');
          out(key);
          out('="');
          out(encodeEntities(value));
          out('"');
        }
      });
      out(unary ? '/>' : '>');
    }
  },
  end: function(tag){
      tag = angular.lowercase(tag);
      if (!ignore && validElements[tag] == true) {
        out('</');
        out(tag);
        out('>');
      }
      if (tag == ignore) {
        ignore = false;
      }
    },
  chars: function(chars){
      if (!ignore) {
        out(encodeEntities(chars));
      }
    }
};

}

// define ngSanitize module and register $sanitize service angular.module('ngSanitize', []).value('$sanitize', $sanitize);

/**

* @ngdoc directive
* @name ngSanitize.directive:ngBindHtml
*
* @description
* Creates a binding that will sanitize the result of evaluating the `expression` with the
* {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element.
*
* See {@link ngSanitize.$sanitize $sanitize} docs for examples.
*
* @element ANY
* @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
*/

angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) {

return function(scope, element, attr) {
  element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
  scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) {
    value = $sanitize(value);
    element.html(value || '');
  });
};

}]);

/**

* @ngdoc filter
* @name ngSanitize.filter:linky
* @function
*
* @description
*   Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
*   plain email address links.
*
* @param {string} text Input text.
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
* @returns {string} Html-linkified text.
*
* @usage
  <span ng-bind-html="linky_expression | linky"></span>
*
* @example
  <doc:example module="ngSanitize">
    <doc:source>
      <script>
        function Ctrl($scope) {
          $scope.snippet =
            'Pretty text with some links:\n'+
            'http://angularjs.org/,\n'+
            'mailto:us@somewhere.org,\n'+
            'another@somewhere.org,\n'+
            'and one more: ftp://127.0.0.1/.';
          $scope.snippetWithTarget = 'http://angularjs.org/';
        }
      </script>
      <div ng-controller="Ctrl">
      Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
      <table>
        <tr>
          <td>Filter</td>
          <td>Source</td>
          <td>Rendered</td>
        </tr>
        <tr id="linky-filter">
          <td>linky filter</td>
          <td>
            <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
          </td>
          <td>
            <div ng-bind-html="snippet | linky"></div>
          </td>
        </tr>
        <tr id="linky-target">
         <td>linky target</td>
         <td>
           <pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
         </td>
         <td>
           <div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
         </td>
        </tr>
        <tr id="escaped-html">
          <td>no filter</td>
          <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
          <td><div ng-bind="snippet"></div></td>
        </tr>
      </table>
    </doc:source>
    <doc:scenario>
      it('should linkify the snippet with urls', function() {
        expect(using('#linky-filter').binding('snippet | linky')).
          toBe('Pretty text with some links:&#10;' +
               '<a href="http://angularjs.org/">http://angularjs.org/</a>,&#10;' +
               '<a href="mailto:us@somewhere.org">us@somewhere.org</a>,&#10;' +
               '<a href="mailto:another@somewhere.org">another@somewhere.org</a>,&#10;' +
               'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
      });

      it ('should not linkify snippet without the linky filter', function() {
        expect(using('#escaped-html').binding('snippet')).
          toBe("Pretty text with some links:\n" +
               "http://angularjs.org/,\n" +
               "mailto:us@somewhere.org,\n" +
               "another@somewhere.org,\n" +
               "and one more: ftp://127.0.0.1/.");
      });

      it('should update', function() {
        input('snippet').enter('new http://link.');
        expect(using('#linky-filter').binding('snippet | linky')).
          toBe('new <a href="http://link">http://link</a>.');
        expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
      });

      it('should work with the target property', function() {
       expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")).
         toBe('<a target="_blank" href="http://angularjs.org/">http://angularjs.org/</a>');
      });
    </doc:scenario>
  </doc:example>
*/

angular.module('ngSanitize').filter('linky', function() {

var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
    MAILTO_REGEXP = /^mailto:/;

return function(text, target) {
  if (!text) return text;
  var match;
  var raw = text;
  var html = [];
  // TODO(vojta): use $sanitize instead
  var writer = htmlSanitizeWriter(html);
  var url;
  var i;
  var properties = {};
  if (angular.isDefined(target)) {
    properties.target = target;
  }
  while ((match = raw.match(LINKY_URL_REGEXP))) {
    // We can not end in these as they are sometimes found at the end of the sentence
    url = match[0];
    // if we did not match ftp/http/mailto then assume mailto
    if (match[2] == match[3]) url = 'mailto:' + url;
    i = match.index;
    writer.chars(raw.substr(0, i));
    properties.href = url;
    writer.start('a', properties);
    writer.chars(match[0].replace(MAILTO_REGEXP, ''));
    writer.end('a');
    raw = raw.substring(i + match[0].length);
  }
  writer.chars(raw);
  return html.join('');
};

});

})(window, window.angular);