angular-message-format.js 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121
  1. /**
  2. * @license AngularJS v1.6.5
  3. * (c) 2010-2017 Google, Inc. http://angularjs.org
  4. * License: MIT
  5. */
  6. (function (window, angular) {
  7. 'use strict';
  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. /* global isFunction: false */
  13. /* global noop: false */
  14. /* global toJson: false */
  15. /* global $$stringify: false */
  16. // Convert an index into the string into line/column for use in error messages
  17. // As such, this doesn't have to be efficient.
  18. function indexToLineAndColumn(text, index) {
  19. var lines = text.split(/\n/g);
  20. for (var i = 0; i < lines.length; i++) {
  21. var line = lines[i];
  22. if (index >= line.length) {
  23. index -= line.length;
  24. } else {
  25. return {line: i + 1, column: index + 1};
  26. }
  27. }
  28. }
  29. var PARSE_CACHE_FOR_TEXT_LITERALS = Object.create(null);
  30. function parseTextLiteral(text) {
  31. var cachedFn = PARSE_CACHE_FOR_TEXT_LITERALS[text];
  32. if (cachedFn != null) {
  33. return cachedFn;
  34. }
  35. function parsedFn(context) {
  36. return text;
  37. }
  38. parsedFn['$$watchDelegate'] = function watchDelegate(scope, listener, objectEquality) {
  39. var unwatch = scope['$watch'](noop,
  40. function textLiteralWatcher() {
  41. if (isFunction(listener)) {
  42. listener(text, text, scope);
  43. }
  44. unwatch();
  45. },
  46. objectEquality);
  47. return unwatch;
  48. };
  49. PARSE_CACHE_FOR_TEXT_LITERALS[text] = parsedFn;
  50. parsedFn['exp'] = text; // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
  51. parsedFn['expressions'] = []; // Require this to call $compile.$$addBindingInfo() which allows Protractor to find elements by binding.
  52. return parsedFn;
  53. }
  54. function subtractOffset(expressionFn, offset) {
  55. if (offset === 0) {
  56. return expressionFn;
  57. }
  58. function minusOffset(value) {
  59. return (value == null) ? value : value - offset;
  60. }
  61. function parsedFn(context) {
  62. return minusOffset(expressionFn(context));
  63. }
  64. var unwatch;
  65. parsedFn['$$watchDelegate'] = function watchDelegate(scope, listener, objectEquality) {
  66. unwatch = scope['$watch'](expressionFn,
  67. function pluralExpressionWatchListener(newValue, oldValue) {
  68. if (isFunction(listener)) {
  69. listener(minusOffset(newValue), minusOffset(oldValue), scope);
  70. }
  71. },
  72. objectEquality);
  73. return unwatch;
  74. };
  75. return parsedFn;
  76. }
  77. // NOTE: ADVANCED_OPTIMIZATIONS mode.
  78. //
  79. // This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
  80. // constructs incompatible with that mode.
  81. /* global $interpolateMinErr: false */
  82. /* global isFunction: false */
  83. /* global noop: false */
  84. /**
  85. * @constructor
  86. * @private
  87. */
  88. function MessageSelectorBase(expressionFn, choices) {
  89. var self = this;
  90. this.expressionFn = expressionFn;
  91. this.choices = choices;
  92. if (choices['other'] === undefined) {
  93. throw $interpolateMinErr('reqother', '“other” is a required option.');
  94. }
  95. this.parsedFn = function (context) {
  96. return self.getResult(context);
  97. };
  98. this.parsedFn['$$watchDelegate'] = function $$watchDelegate(scope, listener, objectEquality) {
  99. return self.watchDelegate(scope, listener, objectEquality);
  100. };
  101. this.parsedFn['exp'] = expressionFn['exp'];
  102. this.parsedFn['expressions'] = expressionFn['expressions'];
  103. }
  104. MessageSelectorBase.prototype.getMessageFn = function getMessageFn(value) {
  105. return this.choices[this.categorizeValue(value)];
  106. };
  107. MessageSelectorBase.prototype.getResult = function getResult(context) {
  108. return this.getMessageFn(this.expressionFn(context))(context);
  109. };
  110. MessageSelectorBase.prototype.watchDelegate = function watchDelegate(scope, listener, objectEquality) {
  111. var watchers = new MessageSelectorWatchers(this, scope, listener, objectEquality);
  112. return function () {
  113. watchers.cancelWatch();
  114. };
  115. };
  116. /**
  117. * @constructor
  118. * @private
  119. */
  120. function MessageSelectorWatchers(msgSelector, scope, listener, objectEquality) {
  121. var self = this;
  122. this.scope = scope;
  123. this.msgSelector = msgSelector;
  124. this.listener = listener;
  125. this.objectEquality = objectEquality;
  126. this.lastMessage = undefined;
  127. this.messageFnWatcher = noop;
  128. var expressionFnListener = function (newValue, oldValue) {
  129. return self.expressionFnListener(newValue, oldValue);
  130. };
  131. this.expressionFnWatcher = scope['$watch'](msgSelector.expressionFn, expressionFnListener, objectEquality);
  132. }
  133. MessageSelectorWatchers.prototype.expressionFnListener = function expressionFnListener(newValue, oldValue) {
  134. var self = this;
  135. this.messageFnWatcher();
  136. var messageFnListener = function (newMessage, oldMessage) {
  137. return self.messageFnListener(newMessage, oldMessage);
  138. };
  139. var messageFn = this.msgSelector.getMessageFn(newValue);
  140. this.messageFnWatcher = this.scope['$watch'](messageFn, messageFnListener, this.objectEquality);
  141. };
  142. MessageSelectorWatchers.prototype.messageFnListener = function messageFnListener(newMessage, oldMessage) {
  143. if (isFunction(this.listener)) {
  144. this.listener.call(null, newMessage, newMessage === oldMessage ? newMessage : this.lastMessage, this.scope);
  145. }
  146. this.lastMessage = newMessage;
  147. };
  148. MessageSelectorWatchers.prototype.cancelWatch = function cancelWatch() {
  149. this.expressionFnWatcher();
  150. this.messageFnWatcher();
  151. };
  152. /**
  153. * @constructor
  154. * @extends MessageSelectorBase
  155. * @private
  156. */
  157. function SelectMessage(expressionFn, choices) {
  158. MessageSelectorBase.call(this, expressionFn, choices);
  159. }
  160. function SelectMessageProto() {
  161. }
  162. SelectMessageProto.prototype = MessageSelectorBase.prototype;
  163. SelectMessage.prototype = new SelectMessageProto();
  164. SelectMessage.prototype.categorizeValue = function categorizeSelectValue(value) {
  165. return (this.choices[value] !== undefined) ? value : 'other';
  166. };
  167. /**
  168. * @constructor
  169. * @extends MessageSelectorBase
  170. * @private
  171. */
  172. function PluralMessage(expressionFn, choices, offset, pluralCat) {
  173. MessageSelectorBase.call(this, expressionFn, choices);
  174. this.offset = offset;
  175. this.pluralCat = pluralCat;
  176. }
  177. function PluralMessageProto() {
  178. }
  179. PluralMessageProto.prototype = MessageSelectorBase.prototype;
  180. PluralMessage.prototype = new PluralMessageProto();
  181. PluralMessage.prototype.categorizeValue = function categorizePluralValue(value) {
  182. if (isNaN(value)) {
  183. return 'other';
  184. } else if (this.choices[value] !== undefined) {
  185. return value;
  186. } else {
  187. var category = this.pluralCat(value - this.offset);
  188. return (this.choices[category] !== undefined) ? category : 'other';
  189. }
  190. };
  191. // NOTE: ADVANCED_OPTIMIZATIONS mode.
  192. //
  193. // This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
  194. // constructs incompatible with that mode.
  195. /* global $interpolateMinErr: false */
  196. /* global isFunction: false */
  197. /* global parseTextLiteral: false */
  198. /**
  199. * @constructor
  200. * @private
  201. */
  202. function InterpolationParts(trustedContext, allOrNothing) {
  203. this.trustedContext = trustedContext;
  204. this.allOrNothing = allOrNothing;
  205. this.textParts = [];
  206. this.expressionFns = [];
  207. this.expressionIndices = [];
  208. this.partialText = '';
  209. this.concatParts = null;
  210. }
  211. InterpolationParts.prototype.flushPartialText = function flushPartialText() {
  212. if (this.partialText) {
  213. if (this.concatParts == null) {
  214. this.textParts.push(this.partialText);
  215. } else {
  216. this.textParts.push(this.concatParts.join(''));
  217. this.concatParts = null;
  218. }
  219. this.partialText = '';
  220. }
  221. };
  222. InterpolationParts.prototype.addText = function addText(text) {
  223. if (text.length) {
  224. if (!this.partialText) {
  225. this.partialText = text;
  226. } else if (this.concatParts) {
  227. this.concatParts.push(text);
  228. } else {
  229. this.concatParts = [this.partialText, text];
  230. }
  231. }
  232. };
  233. InterpolationParts.prototype.addExpressionFn = function addExpressionFn(expressionFn) {
  234. this.flushPartialText();
  235. this.expressionIndices.push(this.textParts.length);
  236. this.expressionFns.push(expressionFn);
  237. this.textParts.push('');
  238. };
  239. InterpolationParts.prototype.getExpressionValues = function getExpressionValues(context) {
  240. var expressionValues = new Array(this.expressionFns.length);
  241. for (var i = 0; i < this.expressionFns.length; i++) {
  242. expressionValues[i] = this.expressionFns[i](context);
  243. }
  244. return expressionValues;
  245. };
  246. InterpolationParts.prototype.getResult = function getResult(expressionValues) {
  247. for (var i = 0; i < this.expressionIndices.length; i++) {
  248. var expressionValue = expressionValues[i];
  249. if (this.allOrNothing && expressionValue === undefined) return;
  250. this.textParts[this.expressionIndices[i]] = expressionValue;
  251. }
  252. return this.textParts.join('');
  253. };
  254. InterpolationParts.prototype.toParsedFn = function toParsedFn(mustHaveExpression, originalText) {
  255. var self = this;
  256. this.flushPartialText();
  257. if (mustHaveExpression && this.expressionFns.length === 0) {
  258. return undefined;
  259. }
  260. if (this.textParts.length === 0) {
  261. return parseTextLiteral('');
  262. }
  263. if (this.trustedContext && this.textParts.length > 1) {
  264. $interpolateMinErr['throwNoconcat'](originalText);
  265. }
  266. if (this.expressionFns.length === 0) {
  267. if (this.textParts.length !== 1) {
  268. this.errorInParseLogic();
  269. }
  270. return parseTextLiteral(this.textParts[0]);
  271. }
  272. var parsedFn = function (context) {
  273. return self.getResult(self.getExpressionValues(context));
  274. };
  275. parsedFn['$$watchDelegate'] = function $$watchDelegate(scope, listener, objectEquality) {
  276. return self.watchDelegate(scope, listener, objectEquality);
  277. };
  278. parsedFn['exp'] = originalText; // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
  279. parsedFn['expressions'] = new Array(this.expressionFns.length); // Require this to call $compile.$$addBindingInfo() which allows Protractor to find elements by binding.
  280. for (var i = 0; i < this.expressionFns.length; i++) {
  281. parsedFn['expressions'][i] = this.expressionFns[i]['exp'];
  282. }
  283. return parsedFn;
  284. };
  285. InterpolationParts.prototype.watchDelegate = function watchDelegate(scope, listener, objectEquality) {
  286. var watcher = new InterpolationPartsWatcher(this, scope, listener, objectEquality);
  287. return function () {
  288. watcher.cancelWatch();
  289. };
  290. };
  291. function InterpolationPartsWatcher(interpolationParts, scope, listener, objectEquality) {
  292. this.interpolationParts = interpolationParts;
  293. this.scope = scope;
  294. this.previousResult = (undefined);
  295. this.listener = listener;
  296. var self = this;
  297. this.expressionFnsWatcher = scope['$watchGroup'](interpolationParts.expressionFns, function (newExpressionValues, oldExpressionValues) {
  298. self.watchListener(newExpressionValues, oldExpressionValues);
  299. });
  300. }
  301. InterpolationPartsWatcher.prototype.watchListener = function watchListener(newExpressionValues, oldExpressionValues) {
  302. var result = this.interpolationParts.getResult(newExpressionValues);
  303. if (isFunction(this.listener)) {
  304. this.listener.call(null, result, newExpressionValues === oldExpressionValues ? result : this.previousResult, this.scope);
  305. }
  306. this.previousResult = result;
  307. };
  308. InterpolationPartsWatcher.prototype.cancelWatch = function cancelWatch() {
  309. this.expressionFnsWatcher();
  310. };
  311. // NOTE: ADVANCED_OPTIMIZATIONS mode.
  312. //
  313. // This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
  314. // constructs incompatible with that mode.
  315. /* global $interpolateMinErr: false */
  316. /* global indexToLineAndColumn: false */
  317. /* global InterpolationParts: false */
  318. /* global PluralMessage: false */
  319. /* global SelectMessage: false */
  320. /* global subtractOffset: false */
  321. // The params src and dst are exactly one of two types: NestedParserState or MessageFormatParser.
  322. // This function is fully optimized by V8. (inspect via IRHydra or --trace-deopt.)
  323. // The idea behind writing it this way is to avoid repeating oneself. This is the ONE place where
  324. // the parser state that is saved/restored when parsing nested mustaches is specified.
  325. function copyNestedParserState(src, dst) {
  326. dst.expressionFn = src.expressionFn;
  327. dst.expressionMinusOffsetFn = src.expressionMinusOffsetFn;
  328. dst.pluralOffset = src.pluralOffset;
  329. dst.choices = src.choices;
  330. dst.choiceKey = src.choiceKey;
  331. dst.interpolationParts = src.interpolationParts;
  332. dst.ruleChoiceKeyword = src.ruleChoiceKeyword;
  333. dst.msgStartIndex = src.msgStartIndex;
  334. dst.expressionStartIndex = src.expressionStartIndex;
  335. }
  336. function NestedParserState(parser) {
  337. copyNestedParserState(parser, this);
  338. }
  339. /**
  340. * @constructor
  341. * @private
  342. */
  343. function MessageFormatParser(text, startIndex, $parse, pluralCat, stringifier,
  344. mustHaveExpression, trustedContext, allOrNothing) {
  345. this.text = text;
  346. this.index = startIndex || 0;
  347. this.$parse = $parse;
  348. this.pluralCat = pluralCat;
  349. this.stringifier = stringifier;
  350. this.mustHaveExpression = !!mustHaveExpression;
  351. this.trustedContext = trustedContext;
  352. this.allOrNothing = !!allOrNothing;
  353. this.expressionFn = null;
  354. this.expressionMinusOffsetFn = null;
  355. this.pluralOffset = null;
  356. this.choices = null;
  357. this.choiceKey = null;
  358. this.interpolationParts = null;
  359. this.msgStartIndex = null;
  360. this.nestedStateStack = [];
  361. this.parsedFn = null;
  362. this.rule = null;
  363. this.ruleStack = null;
  364. this.ruleChoiceKeyword = null;
  365. this.interpNestLevel = null;
  366. this.expressionStartIndex = null;
  367. this.stringStartIndex = null;
  368. this.stringQuote = null;
  369. this.stringInterestsRe = null;
  370. this.angularOperatorStack = null;
  371. this.textPart = null;
  372. }
  373. // preserve v8 optimization.
  374. var EMPTY_STATE = new NestedParserState(new MessageFormatParser(
  375. /* text= */ '', /* startIndex= */ 0, /* $parse= */ null, /* pluralCat= */ null, /* stringifier= */ null,
  376. /* mustHaveExpression= */ false, /* trustedContext= */ null, /* allOrNothing */ false));
  377. MessageFormatParser.prototype.pushState = function pushState() {
  378. this.nestedStateStack.push(new NestedParserState(this));
  379. copyNestedParserState(EMPTY_STATE, this);
  380. };
  381. MessageFormatParser.prototype.popState = function popState() {
  382. if (this.nestedStateStack.length === 0) {
  383. this.errorInParseLogic();
  384. }
  385. var previousState = this.nestedStateStack.pop();
  386. copyNestedParserState(previousState, this);
  387. };
  388. // Oh my JavaScript! Who knew you couldn't match a regex at a specific
  389. // location in a string but will always search forward?!
  390. // Apparently you'll be growing this ability via the sticky flag (y) in
  391. // ES6. I'll just to work around you for now.
  392. MessageFormatParser.prototype.matchRe = function matchRe(re, search) {
  393. re.lastIndex = this.index;
  394. var match = re.exec(this.text);
  395. if (match != null && (search === true || (match.index === this.index))) {
  396. this.index = re.lastIndex;
  397. return match;
  398. }
  399. return null;
  400. };
  401. MessageFormatParser.prototype.searchRe = function searchRe(re) {
  402. return this.matchRe(re, true);
  403. };
  404. MessageFormatParser.prototype.consumeRe = function consumeRe(re) {
  405. // Without the sticky flag, we can't use the .test() method to consume a
  406. // match at the current index. Instead, we'll use the slower .exec() method
  407. // and verify match.index.
  408. return !!this.matchRe(re);
  409. };
  410. // Run through our grammar avoiding deeply nested function call chains.
  411. MessageFormatParser.prototype.run = function run(initialRule) {
  412. this.ruleStack = [initialRule];
  413. do {
  414. this.rule = this.ruleStack.pop();
  415. while (this.rule) {
  416. this.rule();
  417. }
  418. this.assertRuleOrNull(this.rule);
  419. } while (this.ruleStack.length > 0);
  420. };
  421. MessageFormatParser.prototype.errorInParseLogic = function errorInParseLogic() {
  422. throw $interpolateMinErr('logicbug',
  423. '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}”',
  424. this.text);
  425. };
  426. MessageFormatParser.prototype.assertRuleOrNull = function assertRuleOrNull(rule) {
  427. if (rule === undefined) {
  428. this.errorInParseLogic();
  429. }
  430. };
  431. var NEXT_WORD_RE = /\s*(\w+)\s*/g;
  432. MessageFormatParser.prototype.errorExpecting = function errorExpecting() {
  433. // What was wrong with the syntax? Unsupported type, missing comma, or something else?
  434. var match = this.matchRe(NEXT_WORD_RE), position;
  435. if (match == null) {
  436. position = indexToLineAndColumn(this.text, this.index);
  437. throw $interpolateMinErr('reqarg',
  438. 'Expected one of “plural” or “select” at line {0}, column {1} of text “{2}”',
  439. position.line, position.column, this.text);
  440. }
  441. var word = match[1];
  442. if (word === 'select' || word === 'plural') {
  443. position = indexToLineAndColumn(this.text, this.index);
  444. throw $interpolateMinErr('reqcomma',
  445. 'Expected a comma after the keyword “{0}” at line {1}, column {2} of text “{3}”',
  446. word, position.line, position.column, this.text);
  447. } else {
  448. position = indexToLineAndColumn(this.text, this.index);
  449. throw $interpolateMinErr('unknarg',
  450. 'Unsupported keyword “{0}” at line {0}, column {1}. Only “plural” and “select” are currently supported. Text: “{3}”',
  451. word, position.line, position.column, this.text);
  452. }
  453. };
  454. var STRING_START_RE = /['"]/g;
  455. MessageFormatParser.prototype.ruleString = function ruleString() {
  456. var match = this.matchRe(STRING_START_RE);
  457. if (match == null) {
  458. var position = indexToLineAndColumn(this.text, this.index);
  459. throw $interpolateMinErr('wantstring',
  460. 'Expected the beginning of a string at line {0}, column {1} in text “{2}”',
  461. position.line, position.column, this.text);
  462. }
  463. this.startStringAtMatch(match);
  464. };
  465. MessageFormatParser.prototype.startStringAtMatch = function startStringAtMatch(match) {
  466. this.stringStartIndex = match.index;
  467. this.stringQuote = match[0];
  468. this.stringInterestsRe = this.stringQuote === '\'' ? SQUOTED_STRING_INTEREST_RE : DQUOTED_STRING_INTEREST_RE;
  469. this.rule = this.ruleInsideString;
  470. };
  471. 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;
  472. 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;
  473. MessageFormatParser.prototype.ruleInsideString = function ruleInsideString() {
  474. var match = this.searchRe(this.stringInterestsRe);
  475. if (match == null) {
  476. var position = indexToLineAndColumn(this.text, this.stringStartIndex);
  477. throw $interpolateMinErr('untermstr',
  478. 'The string beginning at line {0}, column {1} is unterminated in text “{2}”',
  479. position.line, position.column, this.text);
  480. }
  481. if (match[0] === this.stringQuote) {
  482. this.rule = null;
  483. }
  484. };
  485. var PLURAL_OR_SELECT_ARG_TYPE_RE = /\s*(plural|select)\s*,\s*/g;
  486. MessageFormatParser.prototype.rulePluralOrSelect = function rulePluralOrSelect() {
  487. var match = this.searchRe(PLURAL_OR_SELECT_ARG_TYPE_RE);
  488. if (match == null) {
  489. this.errorExpecting();
  490. }
  491. var argType = match[1];
  492. switch (argType) {
  493. case 'plural':
  494. this.rule = this.rulePluralStyle;
  495. break;
  496. case 'select':
  497. this.rule = this.ruleSelectStyle;
  498. break;
  499. default:
  500. this.errorInParseLogic();
  501. }
  502. };
  503. MessageFormatParser.prototype.rulePluralStyle = function rulePluralStyle() {
  504. this.choices = Object.create(null);
  505. this.ruleChoiceKeyword = this.rulePluralValueOrKeyword;
  506. this.rule = this.rulePluralOffset;
  507. };
  508. MessageFormatParser.prototype.ruleSelectStyle = function ruleSelectStyle() {
  509. this.choices = Object.create(null);
  510. this.ruleChoiceKeyword = this.ruleSelectKeyword;
  511. this.rule = this.ruleSelectKeyword;
  512. };
  513. var NUMBER_RE = /[0]|(?:[1-9][0-9]*)/g;
  514. var PLURAL_OFFSET_RE = new RegExp('\\s*offset\\s*:\\s*(' + NUMBER_RE.source + ')', 'g');
  515. MessageFormatParser.prototype.rulePluralOffset = function rulePluralOffset() {
  516. var match = this.matchRe(PLURAL_OFFSET_RE);
  517. this.pluralOffset = (match == null) ? 0 : parseInt(match[1], 10);
  518. this.expressionMinusOffsetFn = subtractOffset(this.expressionFn, this.pluralOffset);
  519. this.rule = this.rulePluralValueOrKeyword;
  520. };
  521. MessageFormatParser.prototype.assertChoiceKeyIsNew = function assertChoiceKeyIsNew(choiceKey, index) {
  522. if (this.choices[choiceKey] !== undefined) {
  523. var position = indexToLineAndColumn(this.text, index);
  524. throw $interpolateMinErr('dupvalue',
  525. 'The choice “{0}” is specified more than once. Duplicate key is at line {1}, column {2} in text “{3}”',
  526. choiceKey, position.line, position.column, this.text);
  527. }
  528. };
  529. var SELECT_KEYWORD = /\s*(\w+)/g;
  530. MessageFormatParser.prototype.ruleSelectKeyword = function ruleSelectKeyword() {
  531. var match = this.matchRe(SELECT_KEYWORD);
  532. if (match == null) {
  533. this.parsedFn = new SelectMessage(this.expressionFn, this.choices).parsedFn;
  534. this.rule = null;
  535. return;
  536. }
  537. this.choiceKey = match[1];
  538. this.assertChoiceKeyIsNew(this.choiceKey, match.index);
  539. this.rule = this.ruleMessageText;
  540. };
  541. var EXPLICIT_VALUE_OR_KEYWORD_RE = new RegExp('\\s*(?:(?:=(' + NUMBER_RE.source + '))|(\\w+))', 'g');
  542. MessageFormatParser.prototype.rulePluralValueOrKeyword = function rulePluralValueOrKeyword() {
  543. var match = this.matchRe(EXPLICIT_VALUE_OR_KEYWORD_RE);
  544. if (match == null) {
  545. this.parsedFn = new PluralMessage(this.expressionFn, this.choices, this.pluralOffset, this.pluralCat).parsedFn;
  546. this.rule = null;
  547. return;
  548. }
  549. if (match[1] != null) {
  550. this.choiceKey = parseInt(match[1], 10);
  551. } else {
  552. this.choiceKey = match[2];
  553. }
  554. this.assertChoiceKeyIsNew(this.choiceKey, match.index);
  555. this.rule = this.ruleMessageText;
  556. };
  557. var BRACE_OPEN_RE = /\s*\{/g;
  558. var BRACE_CLOSE_RE = /}/g;
  559. MessageFormatParser.prototype.ruleMessageText = function ruleMessageText() {
  560. if (!this.consumeRe(BRACE_OPEN_RE)) {
  561. var position = indexToLineAndColumn(this.text, this.index);
  562. throw $interpolateMinErr('reqopenbrace',
  563. 'The plural choice “{0}” must be followed by a message in braces at line {1}, column {2} in text “{3}”',
  564. this.choiceKey, position.line, position.column, this.text);
  565. }
  566. this.msgStartIndex = this.index;
  567. this.interpolationParts = new InterpolationParts(this.trustedContext, this.allOrNothing);
  568. this.rule = this.ruleInInterpolationOrMessageText;
  569. };
  570. // Note: Since "\" is used as an escape character, don't allow it to be part of the
  571. // startSymbol/endSymbol when I add the feature to allow them to be redefined.
  572. var INTERP_OR_END_MESSAGE_RE = /\\.|{{|}/g;
  573. var INTERP_OR_PLURALVALUE_OR_END_MESSAGE_RE = /\\.|{{|#|}/g;
  574. var ESCAPE_OR_MUSTACHE_BEGIN_RE = /\\.|{{/g;
  575. MessageFormatParser.prototype.advanceInInterpolationOrMessageText = function advanceInInterpolationOrMessageText() {
  576. var currentIndex = this.index, match;
  577. if (this.ruleChoiceKeyword == null) { // interpolation
  578. match = this.searchRe(ESCAPE_OR_MUSTACHE_BEGIN_RE);
  579. if (match == null) { // End of interpolation text. Nothing more to process.
  580. this.textPart = this.text.substring(currentIndex);
  581. this.index = this.text.length;
  582. return null;
  583. }
  584. } else {
  585. match = this.searchRe(this.ruleChoiceKeyword === this.rulePluralValueOrKeyword ?
  586. INTERP_OR_PLURALVALUE_OR_END_MESSAGE_RE : INTERP_OR_END_MESSAGE_RE);
  587. if (match == null) {
  588. var position = indexToLineAndColumn(this.text, this.msgStartIndex);
  589. throw $interpolateMinErr('reqendbrace',
  590. 'The plural/select choice “{0}” message starting at line {1}, column {2} does not have an ending closing brace. Text “{3}”',
  591. this.choiceKey, position.line, position.column, this.text);
  592. }
  593. }
  594. // match is non-null.
  595. var token = match[0];
  596. this.textPart = this.text.substring(currentIndex, match.index);
  597. return token;
  598. };
  599. MessageFormatParser.prototype.ruleInInterpolationOrMessageText = function ruleInInterpolationOrMessageText() {
  600. var currentIndex = this.index;
  601. var token = this.advanceInInterpolationOrMessageText();
  602. if (token == null) {
  603. // End of interpolation text. Nothing more to process.
  604. this.index = this.text.length;
  605. this.interpolationParts.addText(this.text.substring(currentIndex));
  606. this.rule = null;
  607. return;
  608. }
  609. if (token[0] === '\\') {
  610. // unescape next character and continue
  611. this.interpolationParts.addText(this.textPart + token[1]);
  612. return;
  613. }
  614. this.interpolationParts.addText(this.textPart);
  615. if (token === '{{') {
  616. this.pushState();
  617. this.ruleStack.push(this.ruleEndMustacheInInterpolationOrMessage);
  618. this.rule = this.ruleEnteredMustache;
  619. } else if (token === '}') {
  620. this.choices[this.choiceKey] = this.interpolationParts.toParsedFn(/*mustHaveExpression=*/false, this.text);
  621. this.rule = this.ruleChoiceKeyword;
  622. } else if (token === '#') {
  623. this.interpolationParts.addExpressionFn(this.expressionMinusOffsetFn);
  624. } else {
  625. this.errorInParseLogic();
  626. }
  627. };
  628. MessageFormatParser.prototype.ruleInterpolate = function ruleInterpolate() {
  629. this.interpolationParts = new InterpolationParts(this.trustedContext, this.allOrNothing);
  630. this.rule = this.ruleInInterpolation;
  631. };
  632. MessageFormatParser.prototype.ruleInInterpolation = function ruleInInterpolation() {
  633. var currentIndex = this.index;
  634. var match = this.searchRe(ESCAPE_OR_MUSTACHE_BEGIN_RE);
  635. if (match == null) {
  636. // End of interpolation text. Nothing more to process.
  637. this.index = this.text.length;
  638. this.interpolationParts.addText(this.text.substring(currentIndex));
  639. this.parsedFn = this.interpolationParts.toParsedFn(this.mustHaveExpression, this.text);
  640. this.rule = null;
  641. return;
  642. }
  643. var token = match[0];
  644. if (token[0] === '\\') {
  645. // unescape next character and continue
  646. this.interpolationParts.addText(this.text.substring(currentIndex, match.index) + token[1]);
  647. return;
  648. }
  649. this.interpolationParts.addText(this.text.substring(currentIndex, match.index));
  650. this.pushState();
  651. this.ruleStack.push(this.ruleInterpolationEndMustache);
  652. this.rule = this.ruleEnteredMustache;
  653. };
  654. MessageFormatParser.prototype.ruleInterpolationEndMustache = function ruleInterpolationEndMustache() {
  655. var expressionFn = this.parsedFn;
  656. this.popState();
  657. this.interpolationParts.addExpressionFn(expressionFn);
  658. this.rule = this.ruleInInterpolation;
  659. };
  660. MessageFormatParser.prototype.ruleEnteredMustache = function ruleEnteredMustache() {
  661. this.parsedFn = null;
  662. this.ruleStack.push(this.ruleEndMustache);
  663. this.rule = this.ruleAngularExpression;
  664. };
  665. MessageFormatParser.prototype.ruleEndMustacheInInterpolationOrMessage = function ruleEndMustacheInInterpolationOrMessage() {
  666. var expressionFn = this.parsedFn;
  667. this.popState();
  668. this.interpolationParts.addExpressionFn(expressionFn);
  669. this.rule = this.ruleInInterpolationOrMessageText;
  670. };
  671. var INTERP_END_RE = /\s*}}/g;
  672. MessageFormatParser.prototype.ruleEndMustache = function ruleEndMustache() {
  673. var match = this.matchRe(INTERP_END_RE);
  674. if (match == null) {
  675. var position = indexToLineAndColumn(this.text, this.index);
  676. throw $interpolateMinErr('reqendinterp',
  677. 'Expecting end of interpolation symbol, “{0}”, at line {1}, column {2} in text “{3}”',
  678. '}}', position.line, position.column, this.text);
  679. }
  680. if (this.parsedFn == null) {
  681. // If we parsed a MessageFormat extension, (e.g. select/plural today, maybe more some other
  682. // day), then the result *has* to be a string and those rules would have already set
  683. // this.parsedFn. If there was no MessageFormat extension, then there is no requirement to
  684. // stringify the result and parsedFn isn't set. We set it here. While we could have set it
  685. // unconditionally when exiting the Angular expression, I intend for us to not just replace
  686. // $interpolate, but also to replace $parse in a future version (so ng-bind can work), and in
  687. // such a case we do not want to unnecessarily stringify something if it's not going to be used
  688. // in a string context.
  689. this.parsedFn = this.$parse(this.expressionFn, this.stringifier);
  690. this.parsedFn['exp'] = this.expressionFn['exp']; // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
  691. this.parsedFn['expressions'] = this.expressionFn['expressions']; // Require this to call $compile.$$addBindingInfo() which allows Protractor to find elements by binding.
  692. }
  693. this.rule = null;
  694. };
  695. MessageFormatParser.prototype.ruleAngularExpression = function ruleAngularExpression() {
  696. this.angularOperatorStack = [];
  697. this.expressionStartIndex = this.index;
  698. this.rule = this.ruleInAngularExpression;
  699. };
  700. function getEndOperator(opBegin) {
  701. switch (opBegin) {
  702. case '{':
  703. return '}';
  704. case '[':
  705. return ']';
  706. case '(':
  707. return ')';
  708. default:
  709. return null;
  710. }
  711. }
  712. function getBeginOperator(opEnd) {
  713. switch (opEnd) {
  714. case '}':
  715. return '{';
  716. case ']':
  717. return '[';
  718. case ')':
  719. return '(';
  720. default:
  721. return null;
  722. }
  723. }
  724. // TODO(chirayu): The interpolation endSymbol must also be accounted for. It
  725. // just so happens that "}" is an operator so it's in the list below. But we
  726. // should support any other type of start/end interpolation symbol.
  727. var INTERESTING_OPERATORS_RE = /[[\]{}()'",]/g;
  728. MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularExpression() {
  729. var match = this.searchRe(INTERESTING_OPERATORS_RE);
  730. var position;
  731. if (match == null) {
  732. if (this.angularOperatorStack.length === 0) {
  733. // This is the end of the Angular expression so this is actually a
  734. // success. Note that when inside an interpolation, this means we even
  735. // consumed the closing interpolation symbols if they were curlies. This
  736. // is NOT an error at this point but will become an error further up the
  737. // stack when the part that saw the opening curlies is unable to find the
  738. // closing ones.
  739. this.index = this.text.length;
  740. this.expressionFn = this.$parse(this.text.substring(this.expressionStartIndex, this.index));
  741. // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
  742. this.expressionFn['exp'] = this.text.substring(this.expressionStartIndex, this.index);
  743. this.expressionFn['expressions'] = this.expressionFn['expressions'];
  744. this.rule = null;
  745. return;
  746. }
  747. var innermostOperator = this.angularOperatorStack[0];
  748. throw $interpolateMinErr('badexpr',
  749. 'Unexpected end of Angular expression. Expecting operator “{0}” at the end of the text “{1}”',
  750. this.getEndOperator(innermostOperator), this.text);
  751. }
  752. var operator = match[0];
  753. if (operator === '\'' || operator === '"') {
  754. this.ruleStack.push(this.ruleInAngularExpression);
  755. this.startStringAtMatch(match);
  756. return;
  757. }
  758. if (operator === ',') {
  759. if (this.trustedContext) {
  760. position = indexToLineAndColumn(this.text, this.index);
  761. throw $interpolateMinErr('unsafe',
  762. 'Use of select/plural MessageFormat syntax is currently disallowed in a secure context ({0}). At line {1}, column {2} of text “{3}”',
  763. this.trustedContext, position.line, position.column, this.text);
  764. }
  765. // only the top level comma has relevance.
  766. if (this.angularOperatorStack.length === 0) {
  767. // todo: does this need to be trimmed?
  768. this.expressionFn = this.$parse(this.text.substring(this.expressionStartIndex, match.index));
  769. // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
  770. this.expressionFn['exp'] = this.text.substring(this.expressionStartIndex, match.index);
  771. this.expressionFn['expressions'] = this.expressionFn['expressions'];
  772. this.rule = null;
  773. this.rule = this.rulePluralOrSelect;
  774. }
  775. return;
  776. }
  777. if (getEndOperator(operator) != null) {
  778. this.angularOperatorStack.unshift(operator);
  779. return;
  780. }
  781. var beginOperator = getBeginOperator(operator);
  782. if (beginOperator == null) {
  783. this.errorInParseLogic();
  784. }
  785. if (this.angularOperatorStack.length > 0) {
  786. if (beginOperator === this.angularOperatorStack[0]) {
  787. this.angularOperatorStack.shift();
  788. return;
  789. }
  790. position = indexToLineAndColumn(this.text, this.index);
  791. throw $interpolateMinErr('badexpr',
  792. 'Unexpected operator “{0}” at line {1}, column {2} in text. Was expecting “{3}”. Text: “{4}”',
  793. operator, position.line, position.column, getEndOperator(this.angularOperatorStack[0]), this.text);
  794. }
  795. // We are trying to pop off the operator stack but there really isn't anything to pop off.
  796. this.index = match.index;
  797. this.expressionFn = this.$parse(this.text.substring(this.expressionStartIndex, this.index));
  798. // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
  799. this.expressionFn['exp'] = this.text.substring(this.expressionStartIndex, this.index);
  800. this.expressionFn['expressions'] = this.expressionFn['expressions'];
  801. this.rule = null;
  802. };
  803. // NOTE: ADVANCED_OPTIMIZATIONS mode.
  804. //
  805. // This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
  806. // constructs incompatible with that mode.
  807. /* global $interpolateMinErr: true */
  808. /* global isFunction: true */
  809. /* global noop: true */
  810. /* global toJson: true */
  811. /* global MessageFormatParser: false */
  812. /**
  813. * @ngdoc module
  814. * @name ngMessageFormat
  815. * @packageName angular-message-format
  816. *
  817. * @description
  818. *
  819. * ## What is ngMessageFormat?
  820. *
  821. * The ngMessageFormat module extends the Angular {@link ng.$interpolate `$interpolate`} service
  822. * with a syntax for handling pluralization and gender specific messages, which is based on the
  823. * [ICU MessageFormat syntax][ICU].
  824. *
  825. * See [the design doc][ngMessageFormat doc] for more information.
  826. *
  827. * [ICU]: http://userguide.icu-project.org/formatparse/messages#TOC-MessageFormat
  828. * [ngMessageFormat doc]: https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit
  829. *
  830. * ## Examples
  831. *
  832. * ### Gender
  833. *
  834. * This example uses the "select" keyword to specify the message based on gender.
  835. *
  836. * <example name="ngMessageFormat-example-gender" module="msgFmtExample" deps="angular-message-format.js">
  837. * <file name="index.html">
  838. * <div ng-controller="AppController">
  839. * Select Recipient:<br>
  840. <select ng-model="recipient" ng-options="person as person.name for person in recipients">
  841. </select>
  842. <p>{{recipient.gender, select,
  843. male {{{recipient.name}} unwrapped his gift. }
  844. female {{{recipient.name}} unwrapped her gift. }
  845. other {{{recipient.name}} unwrapped their gift. }
  846. }}</p>
  847. * </div>
  848. * </file>
  849. * <file name="script.js">
  850. * function Person(name, gender) {
  851. * this.name = name;
  852. * this.gender = gender;
  853. * }
  854. *
  855. * var alice = new Person('Alice', 'female'),
  856. * bob = new Person('Bob', 'male'),
  857. * ashley = new Person('Ashley', '');
  858. *
  859. * angular.module('msgFmtExample', ['ngMessageFormat'])
  860. * .controller('AppController', ['$scope', function($scope) {
  861. * $scope.recipients = [alice, bob, ashley];
  862. * $scope.recipient = $scope.recipients[0];
  863. * }]);
  864. * </file>
  865. * </example>
  866. *
  867. * ### Plural
  868. *
  869. * This example shows how the "plural" keyword is used to account for a variable number of entities.
  870. * The "#" variable holds the current number and can be embedded in the message.
  871. *
  872. * Note that "=1" takes precedence over "one".
  873. *
  874. * The example also shows the "offset" keyword, which allows you to offset the value of the "#" variable.
  875. *
  876. * <example name="ngMessageFormat-example-plural" module="msgFmtExample" deps="angular-message-format.js">
  877. * <file name="index.html">
  878. * <div ng-controller="AppController">
  879. * <button ng-click="recipients.pop()" id="decreaseRecipients">decreaseRecipients</button><br>
  880. * Select recipients:<br>
  881. * <select multiple size=5 ng-model="recipients" ng-options="person as person.name for person in people">
  882. * </select><br>
  883. * <p>{{recipients.length, plural, offset:1
  884. * =0 {{{sender.name}} gave no gifts (\#=#)}
  885. * =1 {{{sender.name}} gave a 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. * }}</p>
  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. * sarah = new Person('Sarah', 'female'),
  901. * harry = new Person('Harry Potter', 'male'),
  902. * ashley = new Person('Ashley', '');
  903. *
  904. * angular.module('msgFmtExample', ['ngMessageFormat'])
  905. * .controller('AppController', ['$scope', function($scope) {
  906. * $scope.people = [alice, bob, sarah, ashley];
  907. * $scope.recipients = [alice, bob, sarah];
  908. * $scope.sender = harry;
  909. * }]);
  910. * </file>
  911. *
  912. * <file name="protractor.js" type="protractor">
  913. * describe('MessageFormat plural', function() {
  914. *
  915. * it('should pluralize initial values', function() {
  916. * var messageElem = element(by.binding('recipients.length')),
  917. * decreaseRecipientsBtn = element(by.id('decreaseRecipients'));
  918. *
  919. * expect(messageElem.getText()).toEqual('Harry Potter gave Alice and 2 other people a gift (#=2)');
  920. * decreaseRecipientsBtn.click();
  921. * expect(messageElem.getText()).toEqual('Harry Potter gave Alice and one other person a gift (#=1)');
  922. * decreaseRecipientsBtn.click();
  923. * expect(messageElem.getText()).toEqual('Harry Potter gave a gift to Alice (#=0)');
  924. * decreaseRecipientsBtn.click();
  925. * expect(messageElem.getText()).toEqual('Harry Potter gave no gifts (#=-1)');
  926. * });
  927. * });
  928. * </file>
  929. * </example>
  930. *
  931. * ### Plural and Gender together
  932. *
  933. * This example shows how you can specify gender rules for specific plural matches - in this case,
  934. * =1 is special cased for gender.
  935. * <example name="ngMessageFormat-example-plural-gender" module="msgFmtExample" deps="angular-message-format.js">
  936. * <file name="index.html">
  937. * <div ng-controller="AppController">
  938. Select recipients:<br>
  939. <select multiple size=5 ng-model="recipients" ng-options="person as person.name for person in people">
  940. </select><br>
  941. <p>{{recipients.length, plural,
  942. =0 {{{sender.name}} has not given any gifts to anyone.}
  943. =1 { {{recipients[0].gender, select,
  944. female { {{sender.name}} gave {{recipients[0].name}} her gift.}
  945. male { {{sender.name}} gave {{recipients[0].name}} his gift.}
  946. other { {{sender.name}} gave {{recipients[0].name}} their gift.}
  947. }}
  948. }
  949. other {{{sender.name}} gave {{recipients.length}} people gifts.}
  950. }}</p>
  951. </file>
  952. * <file name="script.js">
  953. * function Person(name, gender) {
  954. * this.name = name;
  955. * this.gender = gender;
  956. * }
  957. *
  958. * var alice = new Person('Alice', 'female'),
  959. * bob = new Person('Bob', 'male'),
  960. * harry = new Person('Harry Potter', 'male'),
  961. * ashley = new Person('Ashley', '');
  962. *
  963. * angular.module('msgFmtExample', ['ngMessageFormat'])
  964. * .controller('AppController', ['$scope', function($scope) {
  965. * $scope.people = [alice, bob, ashley];
  966. * $scope.recipients = [alice];
  967. * $scope.sender = harry;
  968. * }]);
  969. * </file>
  970. </example>
  971. */
  972. var $$MessageFormatFactory = ['$parse', '$locale', '$sce', '$exceptionHandler', function $$messageFormat(
  973. $parse, $locale, $sce, $exceptionHandler) {
  974. function getStringifier(trustedContext, allOrNothing, text) {
  975. return function stringifier(value) {
  976. try {
  977. value = trustedContext ? $sce['getTrusted'](trustedContext, value) : $sce['valueOf'](value);
  978. return allOrNothing && (value === undefined) ? value : $$stringify(value);
  979. } catch (err) {
  980. $exceptionHandler($interpolateMinErr['interr'](text, err));
  981. }
  982. };
  983. }
  984. function interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
  985. var stringifier = getStringifier(trustedContext, allOrNothing, text);
  986. var parser = new MessageFormatParser(text, 0, $parse, $locale['pluralCat'], stringifier,
  987. mustHaveExpression, trustedContext, allOrNothing);
  988. parser.run(parser.ruleInterpolate);
  989. return parser.parsedFn;
  990. }
  991. return {
  992. 'interpolate': interpolate
  993. };
  994. }];
  995. var $$interpolateDecorator = ['$$messageFormat', '$delegate', function $$interpolateDecorator($$messageFormat, $interpolate) {
  996. if ($interpolate['startSymbol']() !== '{{' || $interpolate['endSymbol']() !== '}}') {
  997. throw $interpolateMinErr('nochgmustache', 'angular-message-format.js currently does not allow you to use custom start and end symbols for interpolation.');
  998. }
  999. var interpolate = $$messageFormat['interpolate'];
  1000. interpolate['startSymbol'] = $interpolate['startSymbol'];
  1001. interpolate['endSymbol'] = $interpolate['endSymbol'];
  1002. return interpolate;
  1003. }];
  1004. var $interpolateMinErr;
  1005. var isFunction;
  1006. var noop;
  1007. var toJson;
  1008. var $$stringify;
  1009. var module = window['angular']['module']('ngMessageFormat', ['ng']);
  1010. module['info']({'angularVersion': '1.6.5'});
  1011. module['factory']('$$messageFormat', $$MessageFormatFactory);
  1012. module['config'](['$provide', function ($provide) {
  1013. $interpolateMinErr = window['angular']['$interpolateMinErr'];
  1014. isFunction = window['angular']['isFunction'];
  1015. noop = window['angular']['noop'];
  1016. toJson = window['angular']['toJson'];
  1017. $$stringify = window['angular']['$$stringify'];
  1018. $provide['decorator']('$interpolate', $$interpolateDecorator);
  1019. }]);
  1020. })(window, window.angular);