Initial OpenECOMP policy/engine commit
[policy/engine.git] / ecomp-sdk-app / src / main / webapp / app / fusion / external / angular-1.5 / angular-message-format.js
1 /**
2  * @license AngularJS v1.5.0
3  * (c) 2010-2016 Google, Inc. http://angularjs.org
4  * License: MIT
5  */
6 (function(window, angular, undefined) {'use strict';
7
8 // NOTE: ADVANCED_OPTIMIZATIONS mode.
9 //
10 // This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
11 // constructs incompatible with that mode.
12
13 var $interpolateMinErr = window['angular']['$interpolateMinErr'];
14
15 var noop = window['angular']['noop'],
16     isFunction = window['angular']['isFunction'],
17     toJson = window['angular']['toJson'];
18
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);
25   }
26 }
27
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++) {
33     var line=lines[i];
34     if (index >= line.length) {
35       index -= line.length;
36     } else {
37       return { line: i + 1, column: index + 1 };
38     }
39   }
40 }
41 var PARSE_CACHE_FOR_TEXT_LITERALS = Object.create(null);
42
43 function parseTextLiteral(text) {
44   var cachedFn = PARSE_CACHE_FOR_TEXT_LITERALS[text];
45   if (cachedFn != null) {
46     return cachedFn;
47   }
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); }
53           unwatch();
54         },
55         objectEquality);
56     return unwatch;
57   };
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.
61   return parsedFn;
62 }
63
64 function subtractOffset(expressionFn, offset) {
65   if (offset === 0) {
66     return expressionFn;
67   }
68   function minusOffset(value) {
69     return (value == void 0) ? value : value - offset;
70   }
71   function parsedFn(context) { return minusOffset(expressionFn(context)); }
72   var unwatch;
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); }
77         },
78         objectEquality);
79     return unwatch;
80   };
81   return parsedFn;
82 }
83
84 // NOTE: ADVANCED_OPTIMIZATIONS mode.
85 //
86 // This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
87 // constructs incompatible with that mode.
88
89 /* global $interpolateMinErr: false */
90 /* global isFunction: false */
91 /* global noop: false */
92
93 /**
94  * @constructor
95  * @private
96  */
97 function MessageSelectorBase(expressionFn, choices) {
98   var self = this;
99   this.expressionFn = expressionFn;
100   this.choices = choices;
101   if (choices["other"] === void 0) {
102     throw $interpolateMinErr('reqother', '“other” is a required option.');
103   }
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);
107   };
108   this.parsedFn['exp'] = expressionFn['exp'];
109   this.parsedFn['expressions'] = expressionFn['expressions'];
110 }
111
112 MessageSelectorBase.prototype.getMessageFn = function getMessageFn(value) {
113   return this.choices[this.categorizeValue(value)];
114 };
115
116 MessageSelectorBase.prototype.getResult = function getResult(context) {
117   return this.getMessageFn(this.expressionFn(context))(context);
118 };
119
120 MessageSelectorBase.prototype.watchDelegate = function watchDelegate(scope, listener, objectEquality) {
121   var watchers = new MessageSelectorWatchers(this, scope, listener, objectEquality);
122   return function() { watchers.cancelWatch(); };
123 };
124
125 /**
126  * @constructor
127  * @private
128  */
129 function MessageSelectorWatchers(msgSelector, scope, listener, objectEquality) {
130   var self = this;
131   this.scope = scope;
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);
139 }
140
141 MessageSelectorWatchers.prototype.expressionFnListener = function expressionFnListener(newValue, oldValue) {
142   var self = this;
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);
147 };
148
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);
152   }
153   this.lastMessage = newMessage;
154 };
155
156 MessageSelectorWatchers.prototype.cancelWatch = function cancelWatch() {
157   this.expressionFnWatcher();
158   this.messageFnWatcher();
159 };
160
161 /**
162  * @constructor
163  * @extends MessageSelectorBase
164  * @private
165  */
166 function SelectMessage(expressionFn, choices) {
167   MessageSelectorBase.call(this, expressionFn, choices);
168 }
169
170 function SelectMessageProto() {}
171 SelectMessageProto.prototype = MessageSelectorBase.prototype;
172
173 SelectMessage.prototype = new SelectMessageProto();
174 SelectMessage.prototype.categorizeValue = function categorizeSelectValue(value) {
175   return (this.choices[value] !== void 0) ? value : "other";
176 };
177
178 /**
179  * @constructor
180  * @extends MessageSelectorBase
181  * @private
182  */
183 function PluralMessage(expressionFn, choices, offset, pluralCat) {
184   MessageSelectorBase.call(this, expressionFn, choices);
185   this.offset = offset;
186   this.pluralCat = pluralCat;
187 }
188
189 function PluralMessageProto() {}
190 PluralMessageProto.prototype = MessageSelectorBase.prototype;
191
192 PluralMessage.prototype = new PluralMessageProto();
193 PluralMessage.prototype.categorizeValue = function categorizePluralValue(value) {
194   if (isNaN(value)) {
195     return "other";
196   } else if (this.choices[value] !== void 0) {
197     return value;
198   } else {
199     var category = this.pluralCat(value - this.offset);
200     return (this.choices[category] !== void 0) ? category : "other";
201   }
202 };
203
204 // NOTE: ADVANCED_OPTIMIZATIONS mode.
205 //
206 // This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
207 // constructs incompatible with that mode.
208
209 /* global $interpolateMinErr: false */
210 /* global isFunction: false */
211 /* global parseTextLiteral: false */
212
213 /**
214  * @constructor
215  * @private
216  */
217 function InterpolationParts(trustedContext, allOrNothing) {
218   this.trustedContext = trustedContext;
219   this.allOrNothing = allOrNothing;
220   this.textParts = [];
221   this.expressionFns = [];
222   this.expressionIndices = [];
223   this.partialText = '';
224   this.concatParts = null;
225 }
226
227 InterpolationParts.prototype.flushPartialText = function flushPartialText() {
228   if (this.partialText) {
229     if (this.concatParts == null) {
230       this.textParts.push(this.partialText);
231     } else {
232       this.textParts.push(this.concatParts.join(''));
233       this.concatParts = null;
234     }
235     this.partialText = '';
236   }
237 };
238
239 InterpolationParts.prototype.addText = function addText(text) {
240   if (text.length) {
241     if (!this.partialText) {
242       this.partialText = text;
243     } else if (this.concatParts) {
244       this.concatParts.push(text);
245     } else {
246       this.concatParts = [this.partialText, text];
247     }
248   }
249 };
250
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('');
256 };
257
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);
262   }
263   return expressionValues;
264 };
265
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;
271   }
272   return this.textParts.join('');
273 };
274
275
276 InterpolationParts.prototype.toParsedFn = function toParsedFn(mustHaveExpression, originalText) {
277   var self = this;
278   this.flushPartialText();
279   if (mustHaveExpression && this.expressionFns.length === 0) {
280     return void 0;
281   }
282   if (this.textParts.length === 0) {
283     return parseTextLiteral('');
284   }
285   if (this.trustedContext && this.textParts.length > 1) {
286     $interpolateMinErr['throwNoconcat'](originalText);
287   }
288   if (this.expressionFns.length === 0) {
289     if (this.textParts.length != 1) { this.errorInParseLogic(); }
290     return parseTextLiteral(this.textParts[0]);
291   }
292   var parsedFn = function(context) {
293     return self.getResult(self.getExpressionValues(context));
294   };
295   parsedFn['$$watchDelegate'] = function $$watchDelegate(scope, listener, objectEquality) {
296     return self.watchDelegate(scope, listener, objectEquality);
297   };
298
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'];
303   }
304
305   return parsedFn;
306 };
307
308 InterpolationParts.prototype.watchDelegate = function watchDelegate(scope, listener, objectEquality) {
309   var watcher = new InterpolationPartsWatcher(this, scope, listener, objectEquality);
310   return function() { watcher.cancelWatch(); };
311 };
312
313 function InterpolationPartsWatcher(interpolationParts, scope, listener, objectEquality) {
314   this.interpolationParts = interpolationParts;
315   this.scope = scope;
316   this.previousResult = (void 0);
317   this.listener = listener;
318   var self = this;
319   this.expressionFnsWatcher = scope['$watchGroup'](interpolationParts.expressionFns, function(newExpressionValues, oldExpressionValues) {
320     self.watchListener(newExpressionValues, oldExpressionValues);
321   });
322 }
323
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);
328   }
329   this.previousResult = result;
330 };
331
332 InterpolationPartsWatcher.prototype.cancelWatch = function cancelWatch() {
333   this.expressionFnsWatcher();
334 };
335
336 // NOTE: ADVANCED_OPTIMIZATIONS mode.
337 //
338 // This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
339 // constructs incompatible with that mode.
340
341 /* global $interpolateMinErr: false */
342 /* global indexToLineAndColumn: false */
343 /* global InterpolationParts: false */
344 /* global PluralMessage: false */
345 /* global SelectMessage: false */
346 /* global subtractOffset: false */
347
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;
362 }
363
364 function NestedParserState(parser) {
365   copyNestedParserState(parser, this);
366 }
367
368 /**
369  * @constructor
370  * @private
371  */
372 function MessageFormatParser(text, startIndex, $parse, pluralCat, stringifier,
373                              mustHaveExpression, trustedContext, allOrNothing) {
374   this.text = text;
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;
385   this.choices = null;
386   this.choiceKey = null;
387   this.interpolationParts = null;
388   this.msgStartIndex = null;
389   this.nestedStateStack = [];
390   this.parsedFn = null;
391   this.rule = 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;
401 }
402
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));
407
408 MessageFormatParser.prototype.pushState = function pushState() {
409   this.nestedStateStack.push(new NestedParserState(this));
410   copyNestedParserState(EMPTY_STATE, this);
411 };
412
413 MessageFormatParser.prototype.popState = function popState() {
414   if (this.nestedStateStack.length === 0) {
415     this.errorInParseLogic();
416   }
417   var previousState = this.nestedStateStack.pop();
418   copyNestedParserState(previousState, this);
419 };
420
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;
430     return match;
431   }
432   return null;
433 };
434
435 MessageFormatParser.prototype.searchRe = function searchRe(re) {
436   return this.matchRe(re, true);
437 };
438
439
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);
445 };
446
447 // Run through our grammar avoiding deeply nested function call chains.
448 MessageFormatParser.prototype.run = function run(initialRule) {
449   this.ruleStack = [initialRule];
450   do {
451     this.rule = this.ruleStack.pop();
452     while (this.rule) {
453       this.rule();
454     }
455     this.assertRuleOrNull(this.rule);
456   } while (this.ruleStack.length > 0);
457 };
458
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}”',
462         this.text);
463 };
464
465 MessageFormatParser.prototype.assertRuleOrNull = function assertRuleOrNull(rule) {
466   if (rule === void 0) {
467     this.errorInParseLogic();
468   }
469 };
470
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;
475   if (match == null) {
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);
480   }
481   var word = match[1];
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);
487   } else {
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);
492   }
493 };
494
495 var STRING_START_RE = /['"]/g;
496 MessageFormatParser.prototype.ruleString = function ruleString() {
497   var match = this.matchRe(STRING_START_RE);
498   if (match == null) {
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);
503   }
504   this.startStringAtMatch(match);
505 };
506
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;
512 };
513
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);
518   if (match == null) {
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);
523   }
524   var chars = match[0];
525   if (match == this.stringQuote) {
526     this.rule = null;
527   }
528 };
529
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);
533   if (match == null) {
534     this.errorExpecting();
535   }
536   var argType = match[1];
537   switch (argType) {
538     case "plural": this.rule = this.rulePluralStyle; break;
539     case "select": this.rule = this.ruleSelectStyle; break;
540     default: this.errorInParseLogic();
541   }
542 };
543
544 MessageFormatParser.prototype.rulePluralStyle = function rulePluralStyle() {
545   this.choices = Object.create(null);
546   this.ruleChoiceKeyword = this.rulePluralValueOrKeyword;
547   this.rule = this.rulePluralOffset;
548 };
549
550 MessageFormatParser.prototype.ruleSelectStyle = function ruleSelectStyle() {
551   this.choices = Object.create(null);
552   this.ruleChoiceKeyword = this.ruleSelectKeyword;
553   this.rule = this.ruleSelectKeyword;
554 };
555
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");
558
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;
564 };
565
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);
572   }
573 };
574
575 var SELECT_KEYWORD = /\s*(\w+)/g;
576 MessageFormatParser.prototype.ruleSelectKeyword = function ruleSelectKeyword() {
577   var match = this.matchRe(SELECT_KEYWORD);
578   if (match == null) {
579     this.parsedFn = new SelectMessage(this.expressionFn, this.choices).parsedFn;
580     this.rule = null;
581     return;
582   }
583   this.choiceKey = match[1];
584   this.assertChoiceKeyIsNew(this.choiceKey, match.index);
585   this.rule = this.ruleMessageText;
586 };
587
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);
591   if (match == null) {
592     this.parsedFn = new PluralMessage(this.expressionFn, this.choices, this.pluralOffset, this.pluralCat).parsedFn;
593     this.rule = null;
594     return;
595   }
596   if (match[1] != null) {
597     this.choiceKey = parseInt(match[1], 10);
598   } else {
599     this.choiceKey = match[2];
600   }
601   this.assertChoiceKeyIsNew(this.choiceKey, match.index);
602   this.rule = this.ruleMessageText;
603 };
604
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);
613   }
614   this.msgStartIndex = this.index;
615   this.interpolationParts = new InterpolationParts(this.trustedContext, this.allOrNothing);
616   this.rule = this.ruleInInterpolationOrMessageText;
617 };
618
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;
631       return null;
632     }
633   } else {
634     match = this.searchRe(this.ruleChoiceKeyword == this.rulePluralValueOrKeyword ?
635                           INTERP_OR_PLURALVALUE_OR_END_MESSAGE_RE : INTERP_OR_END_MESSAGE_RE);
636     if (match == null) {
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);
641     }
642   }
643   // match is non-null.
644   var token = match[0];
645   this.textPart = this.text.substring(currentIndex, match.index);
646   return token;
647 };
648
649 MessageFormatParser.prototype.ruleInInterpolationOrMessageText = function ruleInInterpolationOrMessageText() {
650   var currentIndex = this.index;
651   var token = this.advanceInInterpolationOrMessageText();
652   if (token == null) {
653     // End of interpolation text.  Nothing more to process.
654     this.index = this.text.length;
655     this.interpolationParts.addText(this.text.substring(currentIndex));
656     this.rule = null;
657     return;
658   }
659   if (token[0] == "\\") {
660     // unescape next character and continue
661     this.interpolationParts.addText(this.textPart + token[1]);
662     return;
663   }
664   this.interpolationParts.addText(this.textPart);
665   if (token == "{{") {
666     this.pushState();
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);
674   } else {
675     this.errorInParseLogic();
676   }
677 };
678
679 MessageFormatParser.prototype.ruleInterpolate = function ruleInterpolate() {
680   this.interpolationParts = new InterpolationParts(this.trustedContext, this.allOrNothing);
681   this.rule = this.ruleInInterpolation;
682 };
683
684 MessageFormatParser.prototype.ruleInInterpolation = function ruleInInterpolation() {
685   var currentIndex = this.index;
686   var match = this.searchRe(ESCAPE_OR_MUSTACHE_BEGIN_RE);
687   if (match == null) {
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);
692     this.rule = null;
693     return;
694   }
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]);
699     return;
700   }
701   this.interpolationParts.addText(this.text.substring(currentIndex, match.index));
702   this.pushState();
703   this.ruleStack.push(this.ruleInterpolationEndMustache);
704   this.rule = this.ruleEnteredMustache;
705 };
706
707 MessageFormatParser.prototype.ruleInterpolationEndMustache = function ruleInterpolationEndMustache() {
708   var expressionFn = this.parsedFn;
709   this.popState();
710   this.interpolationParts.addExpressionFn(expressionFn);
711   this.rule = this.ruleInInterpolation;
712 };
713
714 MessageFormatParser.prototype.ruleEnteredMustache = function ruleEnteredMustache() {
715   this.parsedFn = null;
716   this.ruleStack.push(this.ruleEndMustache);
717   this.rule = this.ruleAngularExpression;
718 };
719
720 MessageFormatParser.prototype.ruleEndMustacheInInterpolationOrMessage = function ruleEndMustacheInInterpolationOrMessage() {
721   var expressionFn = this.parsedFn;
722   this.popState();
723   this.interpolationParts.addExpressionFn(expressionFn);
724   this.rule = this.ruleInInterpolationOrMessageText;
725 };
726
727
728
729 var INTERP_END_RE = /\s*}}/g;
730 MessageFormatParser.prototype.ruleEndMustache = function ruleEndMustache() {
731   var match = this.matchRe(INTERP_END_RE);
732   if (match == null) {
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);
737   }
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.
750   }
751   this.rule = null;
752 };
753
754 MessageFormatParser.prototype.ruleAngularExpression = function ruleAngularExpression() {
755   this.angularOperatorStack = [];
756   this.expressionStartIndex = this.index;
757   this.rule = this.ruleInAngularExpression;
758 };
759
760 function getEndOperator(opBegin) {
761   switch (opBegin) {
762     case "{": return "}";
763     case "[": return "]";
764     case "(": return ")";
765     default: return null;
766   }
767 }
768
769 function getBeginOperator(opEnd) {
770   switch (opEnd) {
771     case "}": return "{";
772     case "]": return "[";
773     case ")": return "(";
774     default: return null;
775   }
776 }
777
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);
785   var position;
786   if (match == null) {
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
793       // closing ones.
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'];
799       this.rule = null;
800       return;
801     }
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);
806   }
807   var operator = match[0];
808   if (operator == "'" || operator == '"') {
809     this.ruleStack.push(this.ruleInAngularExpression);
810     this.startStringAtMatch(match);
811     return;
812   }
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);
819     }
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'];
827       this.rule = null;
828       this.rule = this.rulePluralOrSelect;
829     }
830     return;
831   }
832   if (getEndOperator(operator) != null) {
833     this.angularOperatorStack.unshift(operator);
834     return;
835   }
836   var beginOperator = getBeginOperator(operator);
837   if (beginOperator == null) {
838     this.errorInParseLogic();
839   }
840   if (this.angularOperatorStack.length > 0) {
841     if (beginOperator == this.angularOperatorStack[0]) {
842       this.angularOperatorStack.shift();
843       return;
844     }
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);
849   }
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'];
856   this.rule = null;
857 };
858
859 // NOTE: ADVANCED_OPTIMIZATIONS mode.
860 //
861 // This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
862 // constructs incompatible with that mode.
863
864 /* global $interpolateMinErr: false */
865 /* global MessageFormatParser: false */
866 /* global stringify: false */
867
868 /**
869  * @ngdoc service
870  * @name $$messageFormat
871  *
872  * @description
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
876  *
877  * ## Example
878  *
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 (\#=#)}
888  *           }}</span>
889  *   </div>
890  * </file>
891  *
892  * <file name="script.js">
893  *   function Person(name, gender) {
894  *     this.name = name;
895  *     this.gender = gender;
896  *   }
897  *
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");
902  *
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;
909  *         };
910  *       }]);
911  * </file>
912  *
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)');
924  *     });
925  *   });
926  * </file>
927  * </example>
928  */
929 var $$MessageFormatFactory = ['$parse', '$locale', '$sce', '$exceptionHandler', function $$messageFormat(
930                    $parse,   $locale,   $sce,   $exceptionHandler) {
931
932   function getStringifier(trustedContext, allOrNothing, text) {
933     return function stringifier(value) {
934       try {
935         value = trustedContext ? $sce['getTrusted'](trustedContext, value) : $sce['valueOf'](value);
936         return allOrNothing && (value === void 0) ? value : stringify(value);
937       } catch (err) {
938         $exceptionHandler($interpolateMinErr['interr'](text, err));
939       }
940     };
941   }
942
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;
949   }
950
951   return {
952     'interpolate': interpolate
953   };
954 }];
955
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.');
959   }
960   var interpolate = $$messageFormat['interpolate'];
961   interpolate['startSymbol'] = $interpolate['startSymbol'];
962   interpolate['endSymbol'] = $interpolate['endSymbol'];
963   return interpolate;
964 }];
965
966
967 /**
968  * @ngdoc module
969  * @name ngMessageFormat
970  * @packageName angular-message-format
971  * @description
972  */
973 var module = window['angular']['module']('ngMessageFormat', ['ng']);
974 module['factory']('$$messageFormat', $$MessageFormatFactory);
975 module['config'](['$provide', function($provide) {
976   $provide['decorator']('$interpolate', $$interpolateDecorator);
977 }]);
978
979
980 })(window, window.angular);