text: 'use strict';
  
  var MAX_DIGITS = 22;
  var DECIMAL_SEP = '.';
  var ZERO_CHAR = '0';
  
  /**
   * @ngdoc filter
   * @name currency
   * @kind function
   *
   * @description
   * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
   * symbol for current locale is used.
   *
   * @param {number} amount Input to filter.
   * @param {string=} symbol Currency symbol or identifier to be displayed.
   * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
   * @returns {string} Formatted number.
   *
   *
   * @example
     <example module="currencyExample">
       <file name="index.html">
         <script>
           angular.module('currencyExample', [])
             .controller('ExampleController', ['$scope', function($scope) {
               $scope.amount = 1234.56;
             }]);
         </script>
         <div ng-controller="ExampleController">
           <input type="number" ng-model="amount" aria-label="amount"> <br>
           default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
           custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span>
           no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span>
         </div>
       </file>
       <file name="protractor.js" type="protractor">
         it('should init with 1234.56', function() {
           expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
           expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
           expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
         });
         it('should update', function() {
           if (browser.params.browser == 'safari') {
             // Safari does not understand the minus key. See
             // https://github.com/angular/protractor/issues/481
             return;
           }
           element(by.model('amount')).clear();
           element(by.model('amount')).sendKeys('-1234');
           expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00');
           expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00');
           expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234');
         });
       </file>
     </example>
   */
  currencyFilter.$inject = ['$locale'];
  function currencyFilter($locale) {
    var formats = $locale.NUMBER_FORMATS;
    return function(amount, currencySymbol, fractionSize) {
      if (isUndefined(currencySymbol)) {
        currencySymbol = formats.CURRENCY_SYM;
      }
  
      if (isUndefined(fractionSize)) {
        fractionSize = formats.PATTERNS[1].maxFrac;
      }
  
      // if null or undefined pass it through
      return (amount == null)
          ? amount
          : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
              replace(/\u00A4/g, currencySymbol);
    };
  }
  
  /**
   * @ngdoc filter
   * @name number
   * @kind function
   *
   * @description
   * Formats a number as text.
   *
   * If the input is null or undefined, it will just be returned.
   * If the input is infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
   * If the input is not a number an empty string is returned.
   *
   *
   * @param {number|string} number Number to format.
   * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
   * If this is not provided then the fraction size is computed from the current locale's number
   * formatting pattern. In the case of the default locale, it will be 3.
   * @returns {string} Number rounded to fractionSize and places a “,” after each third digit.
   *
   * @example
     <example module="numberFilterExample">
       <file name="index.html">
         <script>
           angular.module('numberFilterExample', [])
             .controller('ExampleController', ['$scope', function($scope) {
               $scope.val = 1234.56789;
             }]);
         </script>
         <div ng-controller="ExampleController">
           <label>Enter number: <input ng-model='val'></label><br>
           Default formatting: <span id='number-default'>{{val | number}}</span><br>
           No fractions: <span>{{val | number:0}}</span><br>
           Negative number: <span>{{-val | number:4}}</span>
         </div>
       </file>
       <file name="protractor.js" type="protractor">
         it('should format numbers', function() {
           expect(element(by.id('number-default')).getText()).toBe('1,234.568');
           expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
           expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
         });
  
         it('should update', function() {
           element(by.model('val')).clear();
           element(by.model('val')).sendKeys('3374.333');
           expect(element(by.id('number-default')).getText()).toBe('3,374.333');
           expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
           expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
        });
       </file>
     </example>
   */
  numberFilter.$inject = ['$locale'];
  function numberFilter($locale) {
    var formats = $locale.NUMBER_FORMATS;
    return function(number, fractionSize) {
  
      // if null or undefined pass it through
      return (number == null)
          ? number
          : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
                         fractionSize);
    };
  }
  
  /**
   * Parse a number (as a string) into three components that can be used
   * for formatting the number.
   *
   * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/)
   *
   * @param  {string} numStr The number to parse
   * @return {object} An object describing this number, containing the following keys:
   *  - d : an array of digits containing leading zeros as necessary
   *  - i : the number of the digits in `d` that are to the left of the decimal point
   *  - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d`
   *
   */
  function parse(numStr) {
    var exponent = 0, digits, numberOfIntegerDigits;
    var i, j, zeros;
  
    // Decimal point?
    if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) {
      numStr = numStr.replace(DECIMAL_SEP, '');
    }
  
    // Exponential form?
    if ((i = numStr.search(/e/i)) > 0) {
      // Work out the exponent.
      if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i;
      numberOfIntegerDigits += +numStr.slice(i + 1);
      numStr = numStr.substring(0, i);
    } else if (numberOfIntegerDigits < 0) {
      // There was no decimal point or exponent so it is an integer.
      numberOfIntegerDigits = numStr.length;
    }
  
    // Count the number of leading zeros.
    for (i = 0; numStr.charAt(i) == ZERO_CHAR; i++);
  
    if (i == (zeros = numStr.length)) {
      // The digits are all zero.
      digits = [0];
      numberOfIntegerDigits = 1;
    } else {
      // Count the number of trailing zeros
      zeros--;
      while (numStr.charAt(zeros) == ZERO_CHAR) zeros--;
  
      // Trailing zeros are insignificant so ignore them
      numberOfIntegerDigits -= i;
      digits = [];
      // Convert string to array of digits without leading/trailing zeros.
      for (j = 0; i <= zeros; i++, j++) {
        digits[j] = +numStr.charAt(i);
      }
    }
  
    // If the number overflows the maximum allowed digits then use an exponent.
    if (numberOfIntegerDigits > MAX_DIGITS) {
      digits = digits.splice(0, MAX_DIGITS - 1);
      exponent = numberOfIntegerDigits - 1;
      numberOfIntegerDigits = 1;
    }
  
    return { d: digits, e: exponent, i: numberOfIntegerDigits };
  }
  
  /**
   * Round the parsed number to the specified number of decimal places
   * This function changed the parsedNumber in-place
   */
  function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) {
      var digits = parsedNumber.d;
      var fractionLen = digits.length - parsedNumber.i;
  
      // determine fractionSize if it is not specified; `+fractionSize` converts it to a number
      fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize;
  
      // The index of the digit to where rounding is to occur
      var roundAt = fractionSize + parsedNumber.i;
      var digit = digits[roundAt];
  
      if (roundAt > 0) {
        digits.splice(roundAt);
      } else {
        // We rounded to zero so reset the parsedNumber
        parsedNumber.i = 1;
        digits.length = roundAt = fractionSize + 1;
        for (var i=0; i < roundAt; i++) digits[i] = 0;
      }
  
      if (digit >= 5) digits[roundAt - 1]++;
  
      // Pad out with zeros to get the required fraction length
      for (; fractionLen < fractionSize; fractionLen++) digits.push(0);
  
  
      // Do any carrying, e.g. a digit was rounded up to 10
      var carry = digits.reduceRight(function(carry, d, i, digits) {
        d = d + carry;
        digits[i] = d % 10;
        return Math.floor(d / 10);
      }, 0);
      if (carry) {
        digits.unshift(carry);
        parsedNumber.i++;
      }
  }
  
  /**
   * Format a number into a string
   * @param  {number} number       The number to format
   * @param  {{
   *           minFrac, // the minimum number of digits required in the fraction part of the number
   *           maxFrac, // the maximum number of digits required in the fraction part of the number
   *           gSize,   // number of digits in each group of separated digits
   *           lgSize,  // number of digits in the last group of digits before the decimal separator
   *           negPre,  // the string to go in front of a negative number (e.g. `-` or `(`))
   *           posPre,  // the string to go in front of a positive number
   *           negSuf,  // the string to go after a negative number (e.g. `)`)
   *           posSuf   // the string to go after a positive number
   *         }} pattern
   * @param  {string} groupSep     The string to separate groups of number (e.g. `,`)
   * @param  {string} decimalSep   The string to act as the decimal separator (e.g. `.`)
   * @param  {[type]} fractionSize The size of the fractional part of the number
   * @return {string}              The number formatted as a string
   */
  function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
  
    if (!(isString(number) || isNumber(number)) || isNaN(number)) return '';
  
    var isInfinity = !isFinite(number);
    var isZero = false;
    var numStr = Math.abs(number) + '',
        formattedText = '',
        parsedNumber;
  
    if (isInfinity) {
      formattedText = '\u221e';
    } else {
      parsedNumber = parse(numStr);
  
      roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac);
  
      var digits = parsedNumber.d;
      var integerLen = parsedNumber.i;
      var exponent = parsedNumber.e;
      var decimals = [];
      isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true);
  
      // pad zeros for small numbers
      while (integerLen < 0) {
        digits.unshift(0);
        integerLen++;
      }
  
      // extract decimals digits
      if (integerLen > 0) {
        decimals = digits.splice(integerLen);
      } else {
        decimals = digits;
        digits = [0];
      }
  
      // format the integer digits with grouping separators
      var groups = [];
      if (digits.length > pattern.lgSize) {
        groups.unshift(digits.splice(-pattern.lgSize).join(''));
      }
      while (digits.length > pattern.gSize) {
        groups.unshift(digits.splice(-pattern.gSize).join(''));
      }
      if (digits.length) {
        groups.unshift(digits.join(''));
      }
      formattedText = groups.join(groupSep);
  
      // append the decimal digits
      if (decimals.length) {
        formattedText += decimalSep + decimals.join('');
      }
  
      if (exponent) {
        formattedText += 'e+' + exponent;
      }
    }
    if (number < 0 && !isZero) {
      return pattern.negPre + formattedText + pattern.negSuf;
    } else {
      return pattern.posPre + formattedText + pattern.posSuf;
    }
  }
  
  function padNumber(num, digits, trim) {
    var neg = '';
    if (num < 0) {
      neg =  '-';
      num = -num;
    }
    num = '' + num;
    while (num.length < digits) num = ZERO_CHAR + num;
    if (trim) {
      num = num.substr(num.length - digits);
    }
    return neg + num;
  }
  
  
  function dateGetter(name, size, offset, trim) {
    offset = offset || 0;
    return function(date) {
      var value = date['get' + name]();
      if (offset > 0 || value > -offset) {
        value += offset;
      }
      if (value === 0 && offset == -12) value = 12;
      return padNumber(value, size, trim);
    };
  }
  
  function dateStrGetter(name, shortForm) {
    return function(date, formats) {
      var value = date['get' + name]();
      var get = uppercase(shortForm ? ('SHORT' + name) : name);
  
      return formats[get][value];
    };
  }
  
  function timeZoneGetter(date, formats, offset) {
    var zone = -1 * offset;
    var paddedZone = (zone >= 0) ? "+" : "";
  
    paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
                  padNumber(Math.abs(zone % 60), 2);
  
    return paddedZone;
  }
  
  function getFirstThursdayOfYear(year) {
      // 0 = index of January
      var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay();
      // 4 = index of Thursday (+1 to account for 1st = 5)
      // 11 = index of *next* Thursday (+1 account for 1st = 12)
      return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst);
  }
  
  function getThursdayThisWeek(datetime) {
      return new Date(datetime.getFullYear(), datetime.getMonth(),
        // 4 = index of Thursday
        datetime.getDate() + (4 - datetime.getDay()));
  }
  
  function weekGetter(size) {
     return function(date) {
        var firstThurs = getFirstThursdayOfYear(date.getFullYear()),
           thisThurs = getThursdayThisWeek(date);
  
        var diff = +thisThurs - +firstThurs,
           result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week
  
        return padNumber(result, size);
     };
  }
  
  function ampmGetter(date, formats) {
    return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
  }
  
  function eraGetter(date, formats) {
    return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
  }
  
  function longEraGetter(date, formats) {
    return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
  }
  
  var DATE_FORMATS = {
    yyyy: dateGetter('FullYear', 4),
      yy: dateGetter('FullYear', 2, 0, true),
       y: dateGetter('FullYear', 1),
    MMMM: dateStrGetter('Month'),
     MMM: dateStrGetter('Month', true),
      MM: dateGetter('Month', 2, 1),
       M: dateGetter('Month', 1, 1),
      dd: dateGetter('Date', 2),
       d: dateGetter('Date', 1),
      HH: dateGetter('Hours', 2),
       H: dateGetter('Hours', 1),
      hh: dateGetter('Hours', 2, -12),
       h: dateGetter('Hours', 1, -12),
      mm: dateGetter('Minutes', 2),
       m: dateGetter('Minutes', 1),
      ss: dateGetter('Seconds', 2),
       s: dateGetter('Seconds', 1),
       // while ISO 8601 requires fractions to be prefixed with `.` or `,`
       // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
     sss: dateGetter('Milliseconds', 3),
    EEEE: dateStrGetter('Day'),
     EEE: dateStrGetter('Day', true),
       a: ampmGetter,
       Z: timeZoneGetter,
      ww: weekGetter(2),
       w: weekGetter(1),
       G: eraGetter,
       GG: eraGetter,
       GGG: eraGetter,
       GGGG: longEraGetter
  };
  
  var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,
      NUMBER_STRING = /^\-?\d+$/;
  
  /**
   * @ngdoc filter
   * @name date
   * @kind function
   *
   * @description
   *   Formats `date` to a string based on the requested `format`.
   *
   *   `format` string can be composed of the following elements:
   *
   *   * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
   *   * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
   *   * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
   *   * `'MMMM'`: Month in year (January-December)
   *   * `'MMM'`: Month in year (Jan-Dec)
   *   * `'MM'`: Month in year, padded (01-12)
   *   * `'M'`: Month in year (1-12)
   *   * `'dd'`: Day in month, padded (01-31)
   *   * `'d'`: Day in month (1-31)
   *   * `'EEEE'`: Day in Week,(Sunday-Saturday)
   *   * `'EEE'`: Day in Week, (Sun-Sat)
   *   * `'HH'`: Hour in day, padded (00-23)
   *   * `'H'`: Hour in day (0-23)
   *   * `'hh'`: Hour in AM/PM, padded (01-12)
   *   * `'h'`: Hour in AM/PM, (1-12)
   *   * `'mm'`: Minute in hour, padded (00-59)
   *   * `'m'`: Minute in hour (0-59)
   *   * `'ss'`: Second in minute, padded (00-59)
   *   * `'s'`: Second in minute (0-59)
   *   * `'sss'`: Millisecond in second, padded (000-999)
   *   * `'a'`: AM/PM marker
   *   * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
   *   * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
   *   * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
   *   * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
   *   * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
   *
   *   `format` string can also be one of the following predefined
   *   {@link guide/i18n localizable formats}:
   *
   *   * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
   *     (e.g. Sep 3, 2010 12:05:08 PM)
   *   * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US  locale (e.g. 9/3/10 12:05 PM)
   *   * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US  locale
   *     (e.g. Friday, September 3, 2010)
   *   * `'longDate'`: equivalent to `'MMMM d, y'` for en_US  locale (e.g. September 3, 2010)
   *   * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US  locale (e.g. Sep 3, 2010)
   *   * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
   *   * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM)
   *   * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM)
   *
   *   `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
   *   `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
   *   (e.g. `"h 'o''clock'"`).
   *
   * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
   *    number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its
   *    shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
   *    specified in the string input, the time is considered to be in the local timezone.
   * @param {string=} format Formatting rules (see Description). If not specified,
   *    `mediumDate` is used.
   * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
   *    continental US time zone abbreviations, but for general use, use a time zone offset, for
   *    example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
   *    If not specified, the timezone of the browser will be used.
   * @returns {string} Formatted string or the input if input is not recognized as date/millis.
   *
   * @example
     <example>
       <file name="index.html">
         <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
             <span>{{1288323623006 | date:'medium'}}</span><br>
         <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
            <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
         <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
            <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
         <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>:
            <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br>
       </file>
       <file name="protractor.js" type="protractor">
         it('should format date', function() {
           expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
              toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
           expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
              toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
           expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
              toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
           expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
              toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
         });
       </file>
     </example>
   */
  dateFilter.$inject = ['$locale'];
  function dateFilter($locale) {
  
  
    var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
                       // 1        2       3         4          5          6          7          8  9     10      11
    function jsonStringToDate(string) {
      var match;
      if (match = string.match(R_ISO8601_STR)) {
        var date = new Date(0),
            tzHour = 0,
            tzMin  = 0,
            dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
            timeSetter = match[8] ? date.setUTCHours : date.setHours;
  
        if (match[9]) {
          tzHour = toInt(match[9] + match[10]);
          tzMin = toInt(match[9] + match[11]);
        }
        dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
        var h = toInt(match[4] || 0) - tzHour;
        var m = toInt(match[5] || 0) - tzMin;
        var s = toInt(match[6] || 0);
        var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
        timeSetter.call(date, h, m, s, ms);
        return date;
      }
      return string;
    }
  
  
    return function(date, format, timezone) {
      var text = '',
          parts = [],
          fn, match;
  
      format = format || 'mediumDate';
      format = $locale.DATETIME_FORMATS[format] || format;
      if (isString(date)) {
        date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
      }
  
      if (isNumber(date)) {
        date = new Date(date);
      }
  
      if (!isDate(date) || !isFinite(date.getTime())) {
        return date;
      }
  
      while (format) {
        match = DATE_FORMATS_SPLIT.exec(format);
        if (match) {
          parts = concat(parts, match, 1);
          format = parts.pop();
        } else {
          parts.push(format);
          format = null;
        }
      }
  
      var dateTimezoneOffset = date.getTimezoneOffset();
      if (timezone) {
        dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
        date = convertTimezoneToLocal(date, timezone, true);
      }
      forEach(parts, function(value) {
        fn = DATE_FORMATS[value];
        text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
                   : value === "''" ? "'" : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
      });
  
      return text;
    };
  }
  
  
  /**
   * @ngdoc filter
   * @name json
   * @kind function
   *
   * @description
   *   Allows you to convert a JavaScript object into JSON string.
   *
   *   This filter is mostly useful for debugging. When using the double curly {{value}} notation
   *   the binding is automatically converted to JSON.
   *
   * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
   * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
   * @returns {string} JSON string.
   *
   *
   * @example
     <example>
       <file name="index.html">
         <pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
         <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
       </file>
       <file name="protractor.js" type="protractor">
         it('should jsonify filtered objects', function() {
           expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n  "name": ?"value"\n}/);
           expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n    "name": ?"value"\n}/);
         });
       </file>
     </example>
   *
   */
  function jsonFilter() {
    return function(object, spacing) {
      if (isUndefined(spacing)) {
          spacing = 2;
      }
      return toJson(object, spacing);
    };
  }
  
  
  /**
   * @ngdoc filter
   * @name lowercase
   * @kind function
   * @description
   * Converts string to lowercase.
   * @see angular.lowercase
   */
  var lowercaseFilter = valueFn(lowercase);
  
  
  /**
   * @ngdoc filter
   * @name uppercase
   * @kind function
   * @description
   * Converts string to uppercase.
   * @see angular.uppercase
   */
  var uppercaseFilter = valueFn(uppercase);
  