2  * @license AngularJS v1.5.0
 
   3  * (c) 2010-2016 Google, Inc. http://angularjs.org
 
   6 (function(window, angular, undefined) {'use strict';
 
   8 // NOTE: ADVANCED_OPTIMIZATIONS mode.
 
  10 // This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
 
  11 // constructs incompatible with that mode.
 
  13 var $interpolateMinErr = window['angular']['$interpolateMinErr'];
 
  15 var noop = window['angular']['noop'],
 
  16     isFunction = window['angular']['isFunction'],
 
  17     toJson = window['angular']['toJson'];
 
  19 function stringify(value) {
 
  20   if (value == null /* null/undefined */) { return ''; }
 
  21   switch (typeof value) {
 
  22     case 'string':     return value;
 
  23     case 'number':     return '' + value;
 
  24     default:           return toJson(value);
 
  28 // Convert an index into the string into line/column for use in error messages
 
  29 // As such, this doesn't have to be efficient.
 
  30 function indexToLineAndColumn(text, index) {
 
  31   var lines = text.split(/\n/g);
 
  32   for (var i=0; i < lines.length; i++) {
 
  34     if (index >= line.length) {
 
  37       return { line: i + 1, column: index + 1 };
 
  41 var PARSE_CACHE_FOR_TEXT_LITERALS = Object.create(null);
 
  43 function parseTextLiteral(text) {
 
  44   var cachedFn = PARSE_CACHE_FOR_TEXT_LITERALS[text];
 
  45   if (cachedFn != null) {
 
  48   function parsedFn(context) { return text; }
 
  49   parsedFn['$$watchDelegate'] = function watchDelegate(scope, listener, objectEquality) {
 
  50     var unwatch = scope['$watch'](noop,
 
  51         function textLiteralWatcher() {
 
  52           if (isFunction(listener)) { listener.call(null, text, text, scope); }
 
  58   PARSE_CACHE_FOR_TEXT_LITERALS[text] = parsedFn;
 
  59   parsedFn['exp'] = text; // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
 
  60   parsedFn['expressions'] = []; // Require this to call $compile.$$addBindingInfo() which allows Protractor to find elements by binding.
 
  64 function subtractOffset(expressionFn, offset) {
 
  68   function minusOffset(value) {
 
  69     return (value == void 0) ? value : value - offset;
 
  71   function parsedFn(context) { return minusOffset(expressionFn(context)); }
 
  73   parsedFn['$$watchDelegate'] = function watchDelegate(scope, listener, objectEquality) {
 
  74     unwatch = scope['$watch'](expressionFn,
 
  75         function pluralExpressionWatchListener(newValue, oldValue) {
 
  76           if (isFunction(listener)) { listener.call(null, minusOffset(newValue), minusOffset(oldValue), scope); }
 
  84 // NOTE: ADVANCED_OPTIMIZATIONS mode.
 
  86 // This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
 
  87 // constructs incompatible with that mode.
 
  89 /* global $interpolateMinErr: false */
 
  90 /* global isFunction: false */
 
  91 /* global noop: false */
 
  97 function MessageSelectorBase(expressionFn, choices) {
 
  99   this.expressionFn = expressionFn;
 
 100   this.choices = choices;
 
 101   if (choices["other"] === void 0) {
 
 102     throw $interpolateMinErr('reqother', '“other” is a required option.');
 
 104   this.parsedFn = function(context) { return self.getResult(context); };
 
 105   this.parsedFn['$$watchDelegate'] = function $$watchDelegate(scope, listener, objectEquality) {
 
 106     return self.watchDelegate(scope, listener, objectEquality);
 
 108   this.parsedFn['exp'] = expressionFn['exp'];
 
 109   this.parsedFn['expressions'] = expressionFn['expressions'];
 
 112 MessageSelectorBase.prototype.getMessageFn = function getMessageFn(value) {
 
 113   return this.choices[this.categorizeValue(value)];
 
 116 MessageSelectorBase.prototype.getResult = function getResult(context) {
 
 117   return this.getMessageFn(this.expressionFn(context))(context);
 
 120 MessageSelectorBase.prototype.watchDelegate = function watchDelegate(scope, listener, objectEquality) {
 
 121   var watchers = new MessageSelectorWatchers(this, scope, listener, objectEquality);
 
 122   return function() { watchers.cancelWatch(); };
 
 129 function MessageSelectorWatchers(msgSelector, scope, listener, objectEquality) {
 
 132   this.msgSelector = msgSelector;
 
 133   this.listener = listener;
 
 134   this.objectEquality = objectEquality;
 
 135   this.lastMessage = void 0;
 
 136   this.messageFnWatcher = noop;
 
 137   var expressionFnListener = function(newValue, oldValue) { return self.expressionFnListener(newValue, oldValue); };
 
 138   this.expressionFnWatcher = scope['$watch'](msgSelector.expressionFn, expressionFnListener, objectEquality);
 
 141 MessageSelectorWatchers.prototype.expressionFnListener = function expressionFnListener(newValue, oldValue) {
 
 143   this.messageFnWatcher();
 
 144   var messageFnListener = function(newMessage, oldMessage) { return self.messageFnListener(newMessage, oldMessage); };
 
 145   var messageFn = this.msgSelector.getMessageFn(newValue);
 
 146   this.messageFnWatcher = this.scope['$watch'](messageFn, messageFnListener, this.objectEquality);
 
 149 MessageSelectorWatchers.prototype.messageFnListener = function messageFnListener(newMessage, oldMessage) {
 
 150   if (isFunction(this.listener)) {
 
 151     this.listener.call(null, newMessage, newMessage === oldMessage ? newMessage : this.lastMessage, this.scope);
 
 153   this.lastMessage = newMessage;
 
 156 MessageSelectorWatchers.prototype.cancelWatch = function cancelWatch() {
 
 157   this.expressionFnWatcher();
 
 158   this.messageFnWatcher();
 
 163  * @extends MessageSelectorBase
 
 166 function SelectMessage(expressionFn, choices) {
 
 167   MessageSelectorBase.call(this, expressionFn, choices);
 
 170 function SelectMessageProto() {}
 
 171 SelectMessageProto.prototype = MessageSelectorBase.prototype;
 
 173 SelectMessage.prototype = new SelectMessageProto();
 
 174 SelectMessage.prototype.categorizeValue = function categorizeSelectValue(value) {
 
 175   return (this.choices[value] !== void 0) ? value : "other";
 
 180  * @extends MessageSelectorBase
 
 183 function PluralMessage(expressionFn, choices, offset, pluralCat) {
 
 184   MessageSelectorBase.call(this, expressionFn, choices);
 
 185   this.offset = offset;
 
 186   this.pluralCat = pluralCat;
 
 189 function PluralMessageProto() {}
 
 190 PluralMessageProto.prototype = MessageSelectorBase.prototype;
 
 192 PluralMessage.prototype = new PluralMessageProto();
 
 193 PluralMessage.prototype.categorizeValue = function categorizePluralValue(value) {
 
 196   } else if (this.choices[value] !== void 0) {
 
 199     var category = this.pluralCat(value - this.offset);
 
 200     return (this.choices[category] !== void 0) ? category : "other";
 
 204 // NOTE: ADVANCED_OPTIMIZATIONS mode.
 
 206 // This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
 
 207 // constructs incompatible with that mode.
 
 209 /* global $interpolateMinErr: false */
 
 210 /* global isFunction: false */
 
 211 /* global parseTextLiteral: false */
 
 217 function InterpolationParts(trustedContext, allOrNothing) {
 
 218   this.trustedContext = trustedContext;
 
 219   this.allOrNothing = allOrNothing;
 
 221   this.expressionFns = [];
 
 222   this.expressionIndices = [];
 
 223   this.partialText = '';
 
 224   this.concatParts = null;
 
 227 InterpolationParts.prototype.flushPartialText = function flushPartialText() {
 
 228   if (this.partialText) {
 
 229     if (this.concatParts == null) {
 
 230       this.textParts.push(this.partialText);
 
 232       this.textParts.push(this.concatParts.join(''));
 
 233       this.concatParts = null;
 
 235     this.partialText = '';
 
 239 InterpolationParts.prototype.addText = function addText(text) {
 
 241     if (!this.partialText) {
 
 242       this.partialText = text;
 
 243     } else if (this.concatParts) {
 
 244       this.concatParts.push(text);
 
 246       this.concatParts = [this.partialText, text];
 
 251 InterpolationParts.prototype.addExpressionFn = function addExpressionFn(expressionFn) {
 
 252   this.flushPartialText();
 
 253   this.expressionIndices.push(this.textParts.length);
 
 254   this.expressionFns.push(expressionFn);
 
 255   this.textParts.push('');
 
 258 InterpolationParts.prototype.getExpressionValues = function getExpressionValues(context) {
 
 259   var expressionValues = new Array(this.expressionFns.length);
 
 260   for (var i = 0; i < this.expressionFns.length; i++) {
 
 261     expressionValues[i] = this.expressionFns[i](context);
 
 263   return expressionValues;
 
 266 InterpolationParts.prototype.getResult = function getResult(expressionValues) {
 
 267   for (var i = 0; i < this.expressionIndices.length; i++) {
 
 268     var expressionValue = expressionValues[i];
 
 269     if (this.allOrNothing && expressionValue === void 0) return;
 
 270     this.textParts[this.expressionIndices[i]] = expressionValue;
 
 272   return this.textParts.join('');
 
 276 InterpolationParts.prototype.toParsedFn = function toParsedFn(mustHaveExpression, originalText) {
 
 278   this.flushPartialText();
 
 279   if (mustHaveExpression && this.expressionFns.length === 0) {
 
 282   if (this.textParts.length === 0) {
 
 283     return parseTextLiteral('');
 
 285   if (this.trustedContext && this.textParts.length > 1) {
 
 286     $interpolateMinErr['throwNoconcat'](originalText);
 
 288   if (this.expressionFns.length === 0) {
 
 289     if (this.textParts.length != 1) { this.errorInParseLogic(); }
 
 290     return parseTextLiteral(this.textParts[0]);
 
 292   var parsedFn = function(context) {
 
 293     return self.getResult(self.getExpressionValues(context));
 
 295   parsedFn['$$watchDelegate'] = function $$watchDelegate(scope, listener, objectEquality) {
 
 296     return self.watchDelegate(scope, listener, objectEquality);
 
 299   parsedFn['exp'] = originalText; // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
 
 300   parsedFn['expressions'] = new Array(this.expressionFns.length); // Require this to call $compile.$$addBindingInfo() which allows Protractor to find elements by binding.
 
 301   for (var i = 0; i < this.expressionFns.length; i++) {
 
 302     parsedFn['expressions'][i] = this.expressionFns[i]['exp'];
 
 308 InterpolationParts.prototype.watchDelegate = function watchDelegate(scope, listener, objectEquality) {
 
 309   var watcher = new InterpolationPartsWatcher(this, scope, listener, objectEquality);
 
 310   return function() { watcher.cancelWatch(); };
 
 313 function InterpolationPartsWatcher(interpolationParts, scope, listener, objectEquality) {
 
 314   this.interpolationParts = interpolationParts;
 
 316   this.previousResult = (void 0);
 
 317   this.listener = listener;
 
 319   this.expressionFnsWatcher = scope['$watchGroup'](interpolationParts.expressionFns, function(newExpressionValues, oldExpressionValues) {
 
 320     self.watchListener(newExpressionValues, oldExpressionValues);
 
 324 InterpolationPartsWatcher.prototype.watchListener = function watchListener(newExpressionValues, oldExpressionValues) {
 
 325   var result = this.interpolationParts.getResult(newExpressionValues);
 
 326   if (isFunction(this.listener)) {
 
 327     this.listener.call(null, result, newExpressionValues === oldExpressionValues ? result : this.previousResult, this.scope);
 
 329   this.previousResult = result;
 
 332 InterpolationPartsWatcher.prototype.cancelWatch = function cancelWatch() {
 
 333   this.expressionFnsWatcher();
 
 336 // NOTE: ADVANCED_OPTIMIZATIONS mode.
 
 338 // This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
 
 339 // constructs incompatible with that mode.
 
 341 /* global $interpolateMinErr: false */
 
 342 /* global indexToLineAndColumn: false */
 
 343 /* global InterpolationParts: false */
 
 344 /* global PluralMessage: false */
 
 345 /* global SelectMessage: false */
 
 346 /* global subtractOffset: false */
 
 348 // The params src and dst are exactly one of two types: NestedParserState or MessageFormatParser.
 
 349 // This function is fully optimized by V8. (inspect via IRHydra or --trace-deopt.)
 
 350 // The idea behind writing it this way is to avoid repeating oneself.  This is the ONE place where
 
 351 // the parser state that is saved/restored when parsing nested mustaches is specified.
 
 352 function copyNestedParserState(src, dst) {
 
 353   dst.expressionFn = src.expressionFn;
 
 354   dst.expressionMinusOffsetFn = src.expressionMinusOffsetFn;
 
 355   dst.pluralOffset = src.pluralOffset;
 
 356   dst.choices = src.choices;
 
 357   dst.choiceKey = src.choiceKey;
 
 358   dst.interpolationParts = src.interpolationParts;
 
 359   dst.ruleChoiceKeyword = src.ruleChoiceKeyword;
 
 360   dst.msgStartIndex = src.msgStartIndex;
 
 361   dst.expressionStartIndex = src.expressionStartIndex;
 
 364 function NestedParserState(parser) {
 
 365   copyNestedParserState(parser, this);
 
 372 function MessageFormatParser(text, startIndex, $parse, pluralCat, stringifier,
 
 373                              mustHaveExpression, trustedContext, allOrNothing) {
 
 375   this.index = startIndex || 0;
 
 376   this.$parse = $parse;
 
 377   this.pluralCat = pluralCat;
 
 378   this.stringifier = stringifier;
 
 379   this.mustHaveExpression = !!mustHaveExpression;
 
 380   this.trustedContext = trustedContext;
 
 381   this.allOrNothing = !!allOrNothing;
 
 382   this.expressionFn = null;
 
 383   this.expressionMinusOffsetFn = null;
 
 384   this.pluralOffset = null;
 
 386   this.choiceKey = null;
 
 387   this.interpolationParts = null;
 
 388   this.msgStartIndex = null;
 
 389   this.nestedStateStack = [];
 
 390   this.parsedFn = null;
 
 392   this.ruleStack = null;
 
 393   this.ruleChoiceKeyword = null;
 
 394   this.interpNestLevel = null;
 
 395   this.expressionStartIndex = null;
 
 396   this.stringStartIndex = null;
 
 397   this.stringQuote = null;
 
 398   this.stringInterestsRe = null;
 
 399   this.angularOperatorStack = null;
 
 400   this.textPart = null;
 
 403 // preserve v8 optimization.
 
 404 var EMPTY_STATE = new NestedParserState(new MessageFormatParser(
 
 405         /* text= */ '', /* startIndex= */ 0, /* $parse= */ null, /* pluralCat= */ null, /* stringifier= */ null,
 
 406         /* mustHaveExpression= */ false, /* trustedContext= */ null, /* allOrNothing */ false));
 
 408 MessageFormatParser.prototype.pushState = function pushState() {
 
 409   this.nestedStateStack.push(new NestedParserState(this));
 
 410   copyNestedParserState(EMPTY_STATE, this);
 
 413 MessageFormatParser.prototype.popState = function popState() {
 
 414   if (this.nestedStateStack.length === 0) {
 
 415     this.errorInParseLogic();
 
 417   var previousState = this.nestedStateStack.pop();
 
 418   copyNestedParserState(previousState, this);
 
 421 // Oh my JavaScript!  Who knew you couldn't match a regex at a specific
 
 422 // location in a string but will always search forward?!
 
 423 // Apparently you'll be growing this ability via the sticky flag (y) in
 
 424 // ES6.  I'll just to work around you for now.
 
 425 MessageFormatParser.prototype.matchRe = function matchRe(re, search) {
 
 426   re.lastIndex = this.index;
 
 427   var match = re.exec(this.text);
 
 428   if (match != null && (search === true || (match.index == this.index))) {
 
 429     this.index = re.lastIndex;
 
 435 MessageFormatParser.prototype.searchRe = function searchRe(re) {
 
 436   return this.matchRe(re, true);
 
 440 MessageFormatParser.prototype.consumeRe = function consumeRe(re) {
 
 441   // Without the sticky flag, we can't use the .test() method to consume a
 
 442   // match at the current index.  Instead, we'll use the slower .exec() method
 
 443   // and verify match.index.
 
 444   return !!this.matchRe(re);
 
 447 // Run through our grammar avoiding deeply nested function call chains.
 
 448 MessageFormatParser.prototype.run = function run(initialRule) {
 
 449   this.ruleStack = [initialRule];
 
 451     this.rule = this.ruleStack.pop();
 
 455     this.assertRuleOrNull(this.rule);
 
 456   } while (this.ruleStack.length > 0);
 
 459 MessageFormatParser.prototype.errorInParseLogic = function errorInParseLogic() {
 
 460     throw $interpolateMinErr('logicbug',
 
 461         'The messageformat parser has encountered an internal error.  Please file a github issue against the AngularJS project and provide this message text that triggers the bug.  Text: “{0}”',
 
 465 MessageFormatParser.prototype.assertRuleOrNull = function assertRuleOrNull(rule) {
 
 466   if (rule === void 0) {
 
 467     this.errorInParseLogic();
 
 471 var NEXT_WORD_RE = /\s*(\w+)\s*/g;
 
 472 MessageFormatParser.prototype.errorExpecting = function errorExpecting() {
 
 473   // What was wrong with the syntax? Unsupported type, missing comma, or something else?
 
 474   var match = this.matchRe(NEXT_WORD_RE), position;
 
 476     position = indexToLineAndColumn(this.text, this.index);
 
 477     throw $interpolateMinErr('reqarg',
 
 478         'Expected one of “plural” or “select” at line {0}, column {1} of text “{2}”',
 
 479         position.line, position.column, this.text);
 
 482   if (word == "select" || word == "plural") {
 
 483     position = indexToLineAndColumn(this.text, this.index);
 
 484     throw $interpolateMinErr('reqcomma',
 
 485         'Expected a comma after the keyword “{0}” at line {1}, column {2} of text “{3}”',
 
 486         word, position.line, position.column, this.text);
 
 488     position = indexToLineAndColumn(this.text, this.index);
 
 489     throw $interpolateMinErr('unknarg',
 
 490         'Unsupported keyword “{0}” at line {0}, column {1}. Only “plural” and “select” are currently supported.  Text: “{3}”',
 
 491         word, position.line, position.column, this.text);
 
 495 var STRING_START_RE = /['"]/g;
 
 496 MessageFormatParser.prototype.ruleString = function ruleString() {
 
 497   var match = this.matchRe(STRING_START_RE);
 
 499     var position = indexToLineAndColumn(this.text, this.index);
 
 500     throw $interpolateMinErr('wantstring',
 
 501         'Expected the beginning of a string at line {0}, column {1} in text “{2}”',
 
 502         position.line, position.column, this.text);
 
 504   this.startStringAtMatch(match);
 
 507 MessageFormatParser.prototype.startStringAtMatch = function startStringAtMatch(match) {
 
 508   this.stringStartIndex = match.index;
 
 509   this.stringQuote = match[0];
 
 510   this.stringInterestsRe = this.stringQuote == "'" ? SQUOTED_STRING_INTEREST_RE : DQUOTED_STRING_INTEREST_RE;
 
 511   this.rule = this.ruleInsideString;
 
 514 var SQUOTED_STRING_INTEREST_RE = /\\(?:\\|'|u[0-9A-Fa-f]{4}|x[0-9A-Fa-f]{2}|[0-7]{3}|\r\n|\n|[\s\S])|'/g;
 
 515 var DQUOTED_STRING_INTEREST_RE = /\\(?:\\|"|u[0-9A-Fa-f]{4}|x[0-9A-Fa-f]{2}|[0-7]{3}|\r\n|\n|[\s\S])|"/g;
 
 516 MessageFormatParser.prototype.ruleInsideString = function ruleInsideString() {
 
 517   var match = this.searchRe(this.stringInterestsRe);
 
 519     var position = indexToLineAndColumn(this.text, this.stringStartIndex);
 
 520     throw $interpolateMinErr('untermstr',
 
 521         'The string beginning at line {0}, column {1} is unterminated in text “{2}”',
 
 522         position.line, position.column, this.text);
 
 524   var chars = match[0];
 
 525   if (match == this.stringQuote) {
 
 530 var PLURAL_OR_SELECT_ARG_TYPE_RE = /\s*(plural|select)\s*,\s*/g;
 
 531 MessageFormatParser.prototype.rulePluralOrSelect = function rulePluralOrSelect() {
 
 532   var match = this.searchRe(PLURAL_OR_SELECT_ARG_TYPE_RE);
 
 534     this.errorExpecting();
 
 536   var argType = match[1];
 
 538     case "plural": this.rule = this.rulePluralStyle; break;
 
 539     case "select": this.rule = this.ruleSelectStyle; break;
 
 540     default: this.errorInParseLogic();
 
 544 MessageFormatParser.prototype.rulePluralStyle = function rulePluralStyle() {
 
 545   this.choices = Object.create(null);
 
 546   this.ruleChoiceKeyword = this.rulePluralValueOrKeyword;
 
 547   this.rule = this.rulePluralOffset;
 
 550 MessageFormatParser.prototype.ruleSelectStyle = function ruleSelectStyle() {
 
 551   this.choices = Object.create(null);
 
 552   this.ruleChoiceKeyword = this.ruleSelectKeyword;
 
 553   this.rule = this.ruleSelectKeyword;
 
 556 var NUMBER_RE = /[0]|(?:[1-9][0-9]*)/g;
 
 557 var PLURAL_OFFSET_RE = new RegExp("\\s*offset\\s*:\\s*(" + NUMBER_RE.source + ")", "g");
 
 559 MessageFormatParser.prototype.rulePluralOffset = function rulePluralOffset() {
 
 560   var match = this.matchRe(PLURAL_OFFSET_RE);
 
 561   this.pluralOffset = (match == null) ? 0 : parseInt(match[1], 10);
 
 562   this.expressionMinusOffsetFn = subtractOffset(this.expressionFn, this.pluralOffset);
 
 563   this.rule = this.rulePluralValueOrKeyword;
 
 566 MessageFormatParser.prototype.assertChoiceKeyIsNew = function assertChoiceKeyIsNew(choiceKey, index) {
 
 567   if (this.choices[choiceKey] !== void 0) {
 
 568     var position = indexToLineAndColumn(this.text, index);
 
 569     throw $interpolateMinErr('dupvalue',
 
 570         'The choice “{0}” is specified more than once. Duplicate key is at line {1}, column {2} in text “{3}”',
 
 571         choiceKey, position.line, position.column, this.text);
 
 575 var SELECT_KEYWORD = /\s*(\w+)/g;
 
 576 MessageFormatParser.prototype.ruleSelectKeyword = function ruleSelectKeyword() {
 
 577   var match = this.matchRe(SELECT_KEYWORD);
 
 579     this.parsedFn = new SelectMessage(this.expressionFn, this.choices).parsedFn;
 
 583   this.choiceKey = match[1];
 
 584   this.assertChoiceKeyIsNew(this.choiceKey, match.index);
 
 585   this.rule = this.ruleMessageText;
 
 588 var EXPLICIT_VALUE_OR_KEYWORD_RE = new RegExp("\\s*(?:(?:=(" + NUMBER_RE.source + "))|(\\w+))", "g");
 
 589 MessageFormatParser.prototype.rulePluralValueOrKeyword = function rulePluralValueOrKeyword() {
 
 590   var match = this.matchRe(EXPLICIT_VALUE_OR_KEYWORD_RE);
 
 592     this.parsedFn = new PluralMessage(this.expressionFn, this.choices, this.pluralOffset, this.pluralCat).parsedFn;
 
 596   if (match[1] != null) {
 
 597     this.choiceKey = parseInt(match[1], 10);
 
 599     this.choiceKey = match[2];
 
 601   this.assertChoiceKeyIsNew(this.choiceKey, match.index);
 
 602   this.rule = this.ruleMessageText;
 
 605 var BRACE_OPEN_RE = /\s*{/g;
 
 606 var BRACE_CLOSE_RE = /}/g;
 
 607 MessageFormatParser.prototype.ruleMessageText = function ruleMessageText() {
 
 608   if (!this.consumeRe(BRACE_OPEN_RE)) {
 
 609     var position = indexToLineAndColumn(this.text, this.index);
 
 610     throw $interpolateMinErr('reqopenbrace',
 
 611         'The plural choice “{0}” must be followed by a message in braces at line {1}, column {2} in text “{3}”',
 
 612         this.choiceKey, position.line, position.column, this.text);
 
 614   this.msgStartIndex = this.index;
 
 615   this.interpolationParts = new InterpolationParts(this.trustedContext, this.allOrNothing);
 
 616   this.rule = this.ruleInInterpolationOrMessageText;
 
 619 // Note: Since "\" is used as an escape character, don't allow it to be part of the
 
 620 // startSymbol/endSymbol when I add the feature to allow them to be redefined.
 
 621 var INTERP_OR_END_MESSAGE_RE = /\\.|{{|}/g;
 
 622 var INTERP_OR_PLURALVALUE_OR_END_MESSAGE_RE = /\\.|{{|#|}/g;
 
 623 var ESCAPE_OR_MUSTACHE_BEGIN_RE = /\\.|{{/g;
 
 624 MessageFormatParser.prototype.advanceInInterpolationOrMessageText = function advanceInInterpolationOrMessageText() {
 
 625   var currentIndex = this.index, match, re;
 
 626   if (this.ruleChoiceKeyword == null) { // interpolation
 
 627     match = this.searchRe(ESCAPE_OR_MUSTACHE_BEGIN_RE);
 
 628     if (match == null) { // End of interpolation text.  Nothing more to process.
 
 629       this.textPart = this.text.substring(currentIndex);
 
 630       this.index = this.text.length;
 
 634     match = this.searchRe(this.ruleChoiceKeyword == this.rulePluralValueOrKeyword ?
 
 635                           INTERP_OR_PLURALVALUE_OR_END_MESSAGE_RE : INTERP_OR_END_MESSAGE_RE);
 
 637       var position = indexToLineAndColumn(this.text, this.msgStartIndex);
 
 638       throw $interpolateMinErr('reqendbrace',
 
 639           'The plural/select choice “{0}” message starting at line {1}, column {2} does not have an ending closing brace. Text “{3}”',
 
 640           this.choiceKey, position.line, position.column, this.text);
 
 643   // match is non-null.
 
 644   var token = match[0];
 
 645   this.textPart = this.text.substring(currentIndex, match.index);
 
 649 MessageFormatParser.prototype.ruleInInterpolationOrMessageText = function ruleInInterpolationOrMessageText() {
 
 650   var currentIndex = this.index;
 
 651   var token = this.advanceInInterpolationOrMessageText();
 
 653     // End of interpolation text.  Nothing more to process.
 
 654     this.index = this.text.length;
 
 655     this.interpolationParts.addText(this.text.substring(currentIndex));
 
 659   if (token[0] == "\\") {
 
 660     // unescape next character and continue
 
 661     this.interpolationParts.addText(this.textPart + token[1]);
 
 664   this.interpolationParts.addText(this.textPart);
 
 667     this.ruleStack.push(this.ruleEndMustacheInInterpolationOrMessage);
 
 668     this.rule = this.ruleEnteredMustache;
 
 669   } else if (token == "}") {
 
 670     this.choices[this.choiceKey] = this.interpolationParts.toParsedFn(/*mustHaveExpression=*/false, this.text);
 
 671     this.rule = this.ruleChoiceKeyword;
 
 672   } else if (token == "#") {
 
 673     this.interpolationParts.addExpressionFn(this.expressionMinusOffsetFn);
 
 675     this.errorInParseLogic();
 
 679 MessageFormatParser.prototype.ruleInterpolate = function ruleInterpolate() {
 
 680   this.interpolationParts = new InterpolationParts(this.trustedContext, this.allOrNothing);
 
 681   this.rule = this.ruleInInterpolation;
 
 684 MessageFormatParser.prototype.ruleInInterpolation = function ruleInInterpolation() {
 
 685   var currentIndex = this.index;
 
 686   var match = this.searchRe(ESCAPE_OR_MUSTACHE_BEGIN_RE);
 
 688     // End of interpolation text.  Nothing more to process.
 
 689     this.index = this.text.length;
 
 690     this.interpolationParts.addText(this.text.substring(currentIndex));
 
 691     this.parsedFn = this.interpolationParts.toParsedFn(this.mustHaveExpression, this.text);
 
 695   var token = match[0];
 
 696   if (token[0] == "\\") {
 
 697     // unescape next character and continue
 
 698     this.interpolationParts.addText(this.text.substring(currentIndex, match.index) + token[1]);
 
 701   this.interpolationParts.addText(this.text.substring(currentIndex, match.index));
 
 703   this.ruleStack.push(this.ruleInterpolationEndMustache);
 
 704   this.rule = this.ruleEnteredMustache;
 
 707 MessageFormatParser.prototype.ruleInterpolationEndMustache = function ruleInterpolationEndMustache() {
 
 708   var expressionFn = this.parsedFn;
 
 710   this.interpolationParts.addExpressionFn(expressionFn);
 
 711   this.rule = this.ruleInInterpolation;
 
 714 MessageFormatParser.prototype.ruleEnteredMustache = function ruleEnteredMustache() {
 
 715   this.parsedFn = null;
 
 716   this.ruleStack.push(this.ruleEndMustache);
 
 717   this.rule = this.ruleAngularExpression;
 
 720 MessageFormatParser.prototype.ruleEndMustacheInInterpolationOrMessage = function ruleEndMustacheInInterpolationOrMessage() {
 
 721   var expressionFn = this.parsedFn;
 
 723   this.interpolationParts.addExpressionFn(expressionFn);
 
 724   this.rule = this.ruleInInterpolationOrMessageText;
 
 729 var INTERP_END_RE = /\s*}}/g;
 
 730 MessageFormatParser.prototype.ruleEndMustache = function ruleEndMustache() {
 
 731   var match = this.matchRe(INTERP_END_RE);
 
 733     var position = indexToLineAndColumn(this.text, this.index);
 
 734     throw $interpolateMinErr('reqendinterp',
 
 735         'Expecting end of interpolation symbol, “{0}”, at line {1}, column {2} in text “{3}”',
 
 736         '}}', position.line, position.column, this.text);
 
 738   if (this.parsedFn == null) {
 
 739     // If we parsed a MessageFormat extension, (e.g. select/plural today, maybe more some other
 
 740     // day), then the result *has* to be a string and those rules would have already set
 
 741     // this.parsedFn.  If there was no MessageFormat extension, then there is no requirement to
 
 742     // stringify the result and parsedFn isn't set.  We set it here.  While we could have set it
 
 743     // unconditionally when exiting the Angular expression, I intend for us to not just replace
 
 744     // $interpolate, but also to replace $parse in a future version (so ng-bind can work), and in
 
 745     // such a case we do not want to unnecessarily stringify something if it's not going to be used
 
 746     // in a string context.
 
 747     this.parsedFn = this.$parse(this.expressionFn, this.stringifier);
 
 748     this.parsedFn['exp'] = this.expressionFn['exp']; // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
 
 749     this.parsedFn['expressions'] = this.expressionFn['expressions']; // Require this to call $compile.$$addBindingInfo() which allows Protractor to find elements by binding.
 
 754 MessageFormatParser.prototype.ruleAngularExpression = function ruleAngularExpression() {
 
 755   this.angularOperatorStack = [];
 
 756   this.expressionStartIndex = this.index;
 
 757   this.rule = this.ruleInAngularExpression;
 
 760 function getEndOperator(opBegin) {
 
 762     case "{": return "}";
 
 763     case "[": return "]";
 
 764     case "(": return ")";
 
 765     default: return null;
 
 769 function getBeginOperator(opEnd) {
 
 771     case "}": return "{";
 
 772     case "]": return "[";
 
 773     case ")": return "(";
 
 774     default: return null;
 
 778 // TODO(chirayu): The interpolation endSymbol must also be accounted for. It
 
 779 // just so happens that "}" is an operator so it's in the list below.  But we
 
 780 // should support any other type of start/end interpolation symbol.
 
 781 var INTERESTING_OPERATORS_RE = /[[\]{}()'",]/g;
 
 782 MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularExpression() {
 
 783   var startIndex = this.index;
 
 784   var match = this.searchRe(INTERESTING_OPERATORS_RE);
 
 787     if (this.angularOperatorStack.length === 0) {
 
 788       // This is the end of the Angular expression so this is actually a
 
 789       // success.  Note that when inside an interpolation, this means we even
 
 790       // consumed the closing interpolation symbols if they were curlies.  This
 
 791       // is NOT an error at this point but will become an error further up the
 
 792       // stack when the part that saw the opening curlies is unable to find the
 
 794       this.index = this.text.length;
 
 795       this.expressionFn = this.$parse(this.text.substring(this.expressionStartIndex, this.index));
 
 796       // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
 
 797       this.expressionFn['exp'] = this.text.substring(this.expressionStartIndex, this.index);
 
 798       this.expressionFn['expressions'] = this.expressionFn['expressions'];
 
 802     var innermostOperator = this.angularOperatorStack[0];
 
 803     throw $interpolateMinErr('badexpr',
 
 804         'Unexpected end of Angular expression.  Expecting operator “{0}” at the end of the text “{1}”',
 
 805         this.getEndOperator(innermostOperator), this.text);
 
 807   var operator = match[0];
 
 808   if (operator == "'" || operator == '"') {
 
 809     this.ruleStack.push(this.ruleInAngularExpression);
 
 810     this.startStringAtMatch(match);
 
 813   if (operator == ",") {
 
 814     if (this.trustedContext) {
 
 815       position = indexToLineAndColumn(this.text, this.index);
 
 816       throw $interpolateMinErr('unsafe',
 
 817           'Use of select/plural MessageFormat syntax is currently disallowed in a secure context ({0}).  At line {1}, column {2} of text “{3}”',
 
 818           this.trustedContext, position.line, position.column, this.text);
 
 820     // only the top level comma has relevance.
 
 821     if (this.angularOperatorStack.length === 0) {
 
 822       // todo: does this need to be trimmed?
 
 823       this.expressionFn = this.$parse(this.text.substring(this.expressionStartIndex, match.index));
 
 824       // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
 
 825       this.expressionFn['exp'] = this.text.substring(this.expressionStartIndex, match.index);
 
 826       this.expressionFn['expressions'] = this.expressionFn['expressions'];
 
 828       this.rule = this.rulePluralOrSelect;
 
 832   if (getEndOperator(operator) != null) {
 
 833     this.angularOperatorStack.unshift(operator);
 
 836   var beginOperator = getBeginOperator(operator);
 
 837   if (beginOperator == null) {
 
 838     this.errorInParseLogic();
 
 840   if (this.angularOperatorStack.length > 0) {
 
 841     if (beginOperator == this.angularOperatorStack[0]) {
 
 842       this.angularOperatorStack.shift();
 
 845     position = indexToLineAndColumn(this.text, this.index);
 
 846     throw $interpolateMinErr('badexpr',
 
 847         'Unexpected operator “{0}” at line {1}, column {2} in text. Was expecting “{3}”. Text: “{4}”',
 
 848         operator, position.line, position.column, getEndOperator(this.angularOperatorStack[0]), this.text);
 
 850   // We are trying to pop off the operator stack but there really isn't anything to pop off.
 
 851   this.index = match.index;
 
 852   this.expressionFn = this.$parse(this.text.substring(this.expressionStartIndex, this.index));
 
 853   // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
 
 854   this.expressionFn['exp'] = this.text.substring(this.expressionStartIndex, this.index);
 
 855   this.expressionFn['expressions'] = this.expressionFn['expressions'];
 
 859 // NOTE: ADVANCED_OPTIMIZATIONS mode.
 
 861 // This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
 
 862 // constructs incompatible with that mode.
 
 864 /* global $interpolateMinErr: false */
 
 865 /* global MessageFormatParser: false */
 
 866 /* global stringify: false */
 
 870  * @name $$messageFormat
 
 873  * Angular internal service to recognize MessageFormat extensions in interpolation expressions.
 
 874  * For more information, see:
 
 875  * https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit
 
 879  * <example name="ngMessageFormat-example" module="msgFmtExample" deps="angular-message-format.min.js">
 
 880  * <file name="index.html">
 
 881  *   <div ng-controller="AppController">
 
 882  *     <button ng-click="decreaseRecipients()" id="decreaseRecipients">decreaseRecipients</button><br>
 
 883  *     <span>{{recipients.length, plural, offset:1
 
 884  *             =0    {{{sender.name}} gave no gifts (\#=#)}
 
 885  *             =1    {{{sender.name}} gave one gift to {{recipients[0].name}} (\#=#)}
 
 886  *             one   {{{sender.name}} gave {{recipients[0].name}} and one other person a gift (\#=#)}
 
 887  *             other {{{sender.name}} gave {{recipients[0].name}} and # other people a gift (\#=#)}
 
 892  * <file name="script.js">
 
 893  *   function Person(name, gender) {
 
 895  *     this.gender = gender;
 
 898  *   var alice   = new Person("Alice", "female"),
 
 899  *       bob     = new Person("Bob", "male"),
 
 900  *       charlie = new Person("Charlie", "male"),
 
 901  *       harry   = new Person("Harry Potter", "male");
 
 903  *   angular.module('msgFmtExample', ['ngMessageFormat'])
 
 904  *     .controller('AppController', ['$scope', function($scope) {
 
 905  *         $scope.recipients = [alice, bob, charlie];
 
 906  *         $scope.sender = harry;
 
 907  *         $scope.decreaseRecipients = function() {
 
 908  *           --$scope.recipients.length;
 
 913  * <file name="protractor.js" type="protractor">
 
 914  *   describe('MessageFormat plural', function() {
 
 915  *     it('should pluralize initial values', function() {
 
 916  *       var messageElem = element(by.binding('recipients.length')), decreaseRecipientsBtn = element(by.id('decreaseRecipients'));
 
 917  *       expect(messageElem.getText()).toEqual('Harry Potter gave Alice and 2 other people a gift (#=2)');
 
 918  *       decreaseRecipientsBtn.click();
 
 919  *       expect(messageElem.getText()).toEqual('Harry Potter gave Alice and one other person a gift (#=1)');
 
 920  *       decreaseRecipientsBtn.click();
 
 921  *       expect(messageElem.getText()).toEqual('Harry Potter gave one gift to Alice (#=0)');
 
 922  *       decreaseRecipientsBtn.click();
 
 923  *       expect(messageElem.getText()).toEqual('Harry Potter gave no gifts (#=-1)');
 
 929 var $$MessageFormatFactory = ['$parse', '$locale', '$sce', '$exceptionHandler', function $$messageFormat(
 
 930                    $parse,   $locale,   $sce,   $exceptionHandler) {
 
 932   function getStringifier(trustedContext, allOrNothing, text) {
 
 933     return function stringifier(value) {
 
 935         value = trustedContext ? $sce['getTrusted'](trustedContext, value) : $sce['valueOf'](value);
 
 936         return allOrNothing && (value === void 0) ? value : stringify(value);
 
 938         $exceptionHandler($interpolateMinErr['interr'](text, err));
 
 943   function interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
 
 944     var stringifier = getStringifier(trustedContext, allOrNothing, text);
 
 945     var parser = new MessageFormatParser(text, 0, $parse, $locale['pluralCat'], stringifier,
 
 946                                          mustHaveExpression, trustedContext, allOrNothing);
 
 947     parser.run(parser.ruleInterpolate);
 
 948     return parser.parsedFn;
 
 952     'interpolate': interpolate
 
 956 var $$interpolateDecorator = ['$$messageFormat', '$delegate', function $$interpolateDecorator($$messageFormat, $interpolate) {
 
 957   if ($interpolate['startSymbol']() != "{{" || $interpolate['endSymbol']() != "}}") {
 
 958     throw $interpolateMinErr('nochgmustache', 'angular-message-format.js currently does not allow you to use custom start and end symbols for interpolation.');
 
 960   var interpolate = $$messageFormat['interpolate'];
 
 961   interpolate['startSymbol'] = $interpolate['startSymbol'];
 
 962   interpolate['endSymbol'] = $interpolate['endSymbol'];
 
 969  * @name ngMessageFormat
 
 970  * @packageName angular-message-format
 
 973 var module = window['angular']['module']('ngMessageFormat', ['ng']);
 
 974 module['factory']('$$messageFormat', $$MessageFormatFactory);
 
 975 module['config'](['$provide', function($provide) {
 
 976   $provide['decorator']('$interpolate', $$interpolateDecorator);
 
 980 })(window, window.angular);