1cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Copyright 2010 The Closure Library Authors. All Rights Reserved
2cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)//
3cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Licensed under the Apache License, Version 2.0 (the "License");
4cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// you may not use this file except in compliance with the License.
5cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// You may obtain a copy of the License at
6cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)//
7cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)//      http://www.apache.org/licenses/LICENSE-2.0
8cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)//
9cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Unless required by applicable law or agreed to in writing, software
10cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// distributed under the License is distributed on an "AS-IS" BASIS,
11cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// See the License for the specific language governing permissions and
13cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// limitations under the License.
14cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
15cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
16cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @fileoverview Message/plural format library with locale support.
17cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
18cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Message format grammar:
19cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
20cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * messageFormatPattern := string ( "{" messageFormatElement "}" string )*
21cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * messageFormatElement := argumentIndex [ "," elementFormat ]
22cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * elementFormat := "plural" "," pluralStyle
23cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *                  | "selectordinal" "," ordinalStyle
24cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *                  | "select" "," selectStyle
25cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * pluralStyle :=  pluralFormatPattern
26cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * ordinalStyle :=  selectFormatPattern
27cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * selectStyle :=  selectFormatPattern
28cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * pluralFormatPattern := [ "offset" ":" offsetIndex ] pluralForms*
29cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * selectFormatPattern := pluralForms*
30cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * pluralForms := stringKey "{" ( "{" messageFormatElement "}"|string )* "}"
31cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
32cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * This is a subset of the ICU MessageFormatSyntax:
33cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *   http://userguide.icu-project.org/formatparse/messages
34cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * See also http://go/plurals and http://go/ordinals for internal details.
35cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
36cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
37cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Message example:
38cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
39cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * I see {NUM_PEOPLE, plural, offset:1
40cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *         =0 {no one at all}
41cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *         =1 {{WHO}}
42cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *         one {{WHO} and one other person}
43cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *         other {{WHO} and # other people}}
44cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * in {PLACE}.
45cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
46cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Calling format({'NUM_PEOPLE': 2, 'WHO': 'Mark', 'PLACE': 'Athens'}) would
47cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * produce "I see Mark and one other person in Athens." as output.
48cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
49cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * OR:
50cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
51cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * {NUM_FLOOR, selectordinal,
52cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *   one {Take the elevator to the #st floor.}
53cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *   two {Take the elevator to the #nd floor.}
54cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *   few {Take the elevator to the #rd floor.}
55cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *   other {Take the elevator to the #th floor.}}
56cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
57cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Calling format({'NUM_FLOOR': 22}) would produce
58cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * "Take the elevator to the 22nd floor".
59cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
60cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * See messageformat_test.html for more examples.
61cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
62cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
63cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.provide('goog.i18n.MessageFormat');
64cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
65cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.require('goog.asserts');
66cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.require('goog.i18n.ordinalRules');
67cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.require('goog.i18n.pluralRules');
68cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
69cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
70cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
71cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
72cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Constructor of MessageFormat.
73cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string} pattern The pattern we parse and apply positional parameters
74cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     to.
75cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @constructor
76cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @final
77cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
78cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat = function(pattern) {
79cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
80cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * All encountered literals during parse stage. Indices tell us the order of
81cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * replacement.
82cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {!Array.<string>}
83cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
84cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
85cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.literals_ = [];
86cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
87cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
88cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * Input pattern gets parsed into objects for faster formatting.
89cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {!Array.<!Object>}
90cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
91cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
92cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.parsedPattern_ = [];
93cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
94cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.parsePattern_(pattern);
95cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
96cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
97cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
98cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
99cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Literal strings, including '', are replaced with \uFDDF_x_ for
100cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * parsing purposes, and recovered during format phase.
101cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * \uFDDF is a Unicode nonprinting character, not expected to be found in the
102cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * typical message.
103cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {string}
104cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
105cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
106cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.LITERAL_PLACEHOLDER_ = '\uFDDF_';
107cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
108cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
109cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
110cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Marks a string and block during parsing.
111cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @enum {number}
112cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
113cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
114cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.Element_ = {
115cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  STRING: 0,
116cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  BLOCK: 1
117cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
118cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
119cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
120cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
121cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Block type.
122cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @enum {number}
123cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
124cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
125cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.BlockType_ = {
126cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  PLURAL: 0,
127cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  ORDINAL: 1,
128cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  SELECT: 2,
129cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  SIMPLE: 3,
130cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  STRING: 4,
131cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  UNKNOWN: 5
132cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
133cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
134cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
135cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
136cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Mandatory option in both select and plural form.
137cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {string}
138cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
139cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
140cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.OTHER_ = 'other';
141cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
142cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
143cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
144cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Regular expression for looking for string literals.
145cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {RegExp}
146cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
147cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
148cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.REGEX_LITERAL_ = new RegExp("'([{}#].*?)'", 'g');
149cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
150cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
151cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
152cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Regular expression for looking for '' in the message.
153cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {RegExp}
154cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
155cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
156cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.REGEX_DOUBLE_APOSTROPHE_ = new RegExp("''", 'g');
157cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
158cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
159cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
160cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Formats a message, treating '#' with special meaning representing
161cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * the number (plural_variable - offset).
162cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Object} namedParameters Parameters that either
163cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     influence the formatting or are used as actual data.
164cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     I.e. in call to fmt.format({'NUM_PEOPLE': 5, 'NAME': 'Angela'}),
165cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     object {'NUM_PEOPLE': 5, 'NAME': 'Angela'} holds positional parameters.
166cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     1st parameter could mean 5 people, which could influence plural format,
167cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     and 2nd parameter is just a data to be printed out in proper position.
168cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {string} Formatted message.
169cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
170cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.format = function(namedParameters) {
171cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return this.format_(namedParameters, false);
172cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
173cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
174cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
175cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
176cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Formats a message, treating '#' as literary character.
177cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Object} namedParameters Parameters that either
178cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     influence the formatting or are used as actual data.
179cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     I.e. in call to fmt.format({'NUM_PEOPLE': 5, 'NAME': 'Angela'}),
180cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     object {'NUM_PEOPLE': 5, 'NAME': 'Angela'} holds positional parameters.
181cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     1st parameter could mean 5 people, which could influence plural format,
182cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     and 2nd parameter is just a data to be printed out in proper position.
183cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {string} Formatted message.
184cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
185cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.formatIgnoringPound =
186cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    function(namedParameters) {
187cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return this.format_(namedParameters, true);
188cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
189cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
190cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
191cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
192cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Formats a message.
193cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Object} namedParameters Parameters that either
194cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     influence the formatting or are used as actual data.
195cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     I.e. in call to fmt.format({'NUM_PEOPLE': 5, 'NAME': 'Angela'}),
196cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     object {'NUM_PEOPLE': 5, 'NAME': 'Angela'} holds positional parameters.
197cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     1st parameter could mean 5 people, which could influence plural format,
198cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     and 2nd parameter is just a data to be printed out in proper position.
199cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {boolean} ignorePound If true, treat '#' in plural messages as a
200cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     literary character, else treat it as an ICU syntax character, resolving
201cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     to the number (plural_variable - offset).
202cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {string} Formatted message.
203cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
204cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
205cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.format_ =
206cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    function(namedParameters, ignorePound) {
207cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (this.parsedPattern_.length == 0) {
208cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return '';
209cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
210cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
211cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var result = [];
212cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.formatBlock_(this.parsedPattern_, namedParameters, ignorePound, result);
213cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var message = result.join('');
214cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
215cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!ignorePound) {
216cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    goog.asserts.assert(message.search('#') == -1, 'Not all # were replaced.');
217cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
218cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
219cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  while (this.literals_.length > 0) {
220cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    message = message.replace(this.buildPlaceholder_(this.literals_),
221cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                              this.literals_.pop());
222cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
223cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
224cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return message;
225cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
226cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
227cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
228cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
229cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Parses generic block and returns a formatted string.
230cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Array.<!Object>} parsedPattern Holds parsed tree.
231cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Object} namedParameters Parameters that either influence
232cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     the formatting or are used as actual data.
233cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {boolean} ignorePound If true, treat '#' in plural messages as a
234cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     literary character, else treat it as an ICU syntax character, resolving
235cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     to the number (plural_variable - offset).
236cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Array.<!string>} result Each formatting stage appends its product
237cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     to the result.
238cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
239cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
240cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.formatBlock_ = function(
241cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    parsedPattern, namedParameters, ignorePound, result) {
242cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (var i = 0; i < parsedPattern.length; i++) {
243cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    switch (parsedPattern[i].type) {
244cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      case goog.i18n.MessageFormat.BlockType_.STRING:
245cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        result.push(parsedPattern[i].value);
246cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        break;
247cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      case goog.i18n.MessageFormat.BlockType_.SIMPLE:
248cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var pattern = parsedPattern[i].value;
249cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.formatSimplePlaceholder_(pattern, namedParameters, result);
250cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        break;
251cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      case goog.i18n.MessageFormat.BlockType_.SELECT:
252cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var pattern = parsedPattern[i].value;
253cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.formatSelectBlock_(pattern, namedParameters, ignorePound, result);
254cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        break;
255cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      case goog.i18n.MessageFormat.BlockType_.PLURAL:
256cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var pattern = parsedPattern[i].value;
257cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.formatPluralOrdinalBlock_(pattern,
258cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                       namedParameters,
259cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                       goog.i18n.pluralRules.select,
260cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                       ignorePound,
261cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                       result);
262cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        break;
263cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      case goog.i18n.MessageFormat.BlockType_.ORDINAL:
264cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var pattern = parsedPattern[i].value;
265cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.formatPluralOrdinalBlock_(pattern,
266cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                       namedParameters,
267cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                       goog.i18n.ordinalRules.select,
268cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                       ignorePound,
269cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                       result);
270cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        break;
271cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      default:
272cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        goog.asserts.fail('Unrecognized block type.');
273cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
274cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
275cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
276cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
277cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
278cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
279cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Formats simple placeholder.
280cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Object} parsedPattern JSON object containing placeholder info.
281cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Object} namedParameters Parameters that are used as actual data.
282cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Array.<!string>} result Each formatting stage appends its product
283cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     to the result.
284cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
285cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
286cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.formatSimplePlaceholder_ = function(
287cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    parsedPattern, namedParameters, result) {
288cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var value = namedParameters[parsedPattern];
289cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!goog.isDef(value)) {
290cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    result.push('Undefined parameter - ' + parsedPattern);
291cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
292cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
293cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
294cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Don't push the value yet, it may contain any of # { } in it which
295cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // will break formatter. Insert a placeholder and replace at the end.
296cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.literals_.push(value);
297cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  result.push(this.buildPlaceholder_(this.literals_));
298cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
299cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
300cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
301cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
302cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Formats select block. Only one option is selected.
303cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Object} parsedPattern JSON object containing select block info.
304cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Object} namedParameters Parameters that either influence
305cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     the formatting or are used as actual data.
306cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {boolean} ignorePound If true, treat '#' in plural messages as a
307cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     literary character, else treat it as an ICU syntax character, resolving
308cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     to the number (plural_variable - offset).
309cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Array.<!string>} result Each formatting stage appends its product
310cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     to the result.
311cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
312cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
313cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.formatSelectBlock_ = function(
314cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    parsedPattern, namedParameters, ignorePound, result) {
315cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var argumentIndex = parsedPattern.argumentIndex;
316cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!goog.isDef(namedParameters[argumentIndex])) {
317cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    result.push('Undefined parameter - ' + argumentIndex);
318cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
319cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
320cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
321cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var option = parsedPattern[namedParameters[argumentIndex]];
322cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!goog.isDef(option)) {
323cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    option = parsedPattern[goog.i18n.MessageFormat.OTHER_];
324cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    goog.asserts.assertArray(
325cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        option, 'Invalid option or missing other option for select block.');
326cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
327cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
328cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.formatBlock_(option, namedParameters, ignorePound, result);
329cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
330cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
331cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
332cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
333cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Formats plural or selectordinal block. Only one option is selected and all #
334cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * are replaced.
335cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Object} parsedPattern JSON object containing plural block info.
336cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Object} namedParameters Parameters that either influence
337cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     the formatting or are used as actual data.
338cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!function(number, number=):string} pluralSelector  A select function
339cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     from goog.i18n.pluralRules or goog.i18n.ordinalRules which determines
340cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     which plural/ordinal form to use based on the input number's cardinality.
341cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {boolean} ignorePound If true, treat '#' in plural messages as a
342cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     literary character, else treat it as an ICU syntax character, resolving
343cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     to the number (plural_variable - offset).
344cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Array.<!string>} result Each formatting stage appends its product
345cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     to the result.
346cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
347cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
348cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.formatPluralOrdinalBlock_ = function(
349cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    parsedPattern, namedParameters, pluralSelector, ignorePound, result) {
350cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var argumentIndex = parsedPattern.argumentIndex;
351cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var argumentOffset = parsedPattern.argumentOffset;
352cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var pluralValue = +namedParameters[argumentIndex];
353cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (isNaN(pluralValue)) {
354cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // TODO(user): Distinguish between undefined and invalid parameters.
355cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    result.push('Undefined or invalid parameter - ' + argumentIndex);
356cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
357cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
358cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var diff = pluralValue - argumentOffset;
359cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
360cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Check if there is an exact match.
361cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var option = parsedPattern[namedParameters[argumentIndex]];
362cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!goog.isDef(option)) {
363cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    goog.asserts.assert(diff >= 0, 'Argument index smaller than offset.');
364cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var item;
365cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    item = pluralSelector(diff);
366cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    goog.asserts.assertString(item, 'Invalid plural key.');
367cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
368cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    option = parsedPattern[item];
369cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
370cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // If option is not provided fall back to "other".
371cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (!goog.isDef(option)) {
372cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      option = parsedPattern[goog.i18n.MessageFormat.OTHER_];
373cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
374cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
375cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    goog.asserts.assertArray(
376cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        option, 'Invalid option or missing other option for plural block.');
377cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
378cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
379cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var pluralResult = [];
380cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.formatBlock_(option, namedParameters, ignorePound, pluralResult);
381cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var plural = pluralResult.join('');
382cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  goog.asserts.assertString(plural, 'Empty block in plural.');
383cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (ignorePound) {
384cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    result.push(plural);
385cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
386cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // TODO(plundblad): Might want to use a more specific number formatter.
387cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var localeAwareDiff = diff.toLocaleString();
388cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    result.push(plural.replace(/#/g, localeAwareDiff));
389cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
390cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
391cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
392cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
393cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
394cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Parses input pattern into an array, for faster reformatting with
395cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * different input parameters.
396cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Parsing is locale independent.
397cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string} pattern MessageFormat pattern to parse.
398cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
399cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
400cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.parsePattern_ = function(pattern) {
401cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (pattern) {
402cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    pattern = this.insertPlaceholders_(pattern);
403cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
404cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.parsedPattern_ = this.parseBlock_(pattern);
405cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
406cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
407cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
408cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
409cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
410cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Replaces string literals with literal placeholders.
411cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Literals are string of the form '}...', '{...' and '#...' where ... is
412cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * set of characters not containing '
413cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Builds a dictionary so we can recover literals during format phase.
414cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string} pattern Pattern to clean up.
415cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {string} Pattern with literals replaced with placeholders.
416cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
417cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
418cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.insertPlaceholders_ = function(pattern) {
419cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var literals = this.literals_;
420cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var buildPlaceholder = goog.bind(this.buildPlaceholder_, this);
421cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
422cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // First replace '' with single quote placeholder since they can be found
423cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // inside other literals.
424cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  pattern = pattern.replace(
425cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      goog.i18n.MessageFormat.REGEX_DOUBLE_APOSTROPHE_,
426cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      function() {
427cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        literals.push("'");
428cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        return buildPlaceholder(literals);
429cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      });
430cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
431cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  pattern = pattern.replace(
432cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      goog.i18n.MessageFormat.REGEX_LITERAL_,
433cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      function(match, text) {
434cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        literals.push(text);
435cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        return buildPlaceholder(literals);
436cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      });
437cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
438cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return pattern;
439cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
440cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
441cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
442cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
443cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Breaks pattern into strings and top level {...} blocks.
444cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string} pattern (sub)Pattern to be broken.
445cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {!Array.<Object>} Each item is {type, value}.
446cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
447cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
448cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.extractParts_ = function(pattern) {
449cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var prevPos = 0;
450cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var inBlock = false;
451cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var braceStack = [];
452cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var results = [];
453cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
454cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var braces = /[{}]/g;
455cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  braces.lastIndex = 0;  // lastIndex doesn't get set to 0 so we have to.
456cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var match;
457cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
458cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  while (match = braces.exec(pattern)) {
459cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var pos = match.index;
460cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (match[0] == '}') {
461cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var brace = braceStack.pop();
462cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      goog.asserts.assert(goog.isDef(brace) && brace == '{',
463cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                          'No matching { for }.');
464cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
465cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (braceStack.length == 0) {
466cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // End of the block.
467cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var part = {};
468cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        part.type = goog.i18n.MessageFormat.Element_.BLOCK;
469cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        part.value = pattern.substring(prevPos, pos);
470cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        results.push(part);
471cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        prevPos = pos + 1;
472cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        inBlock = false;
473cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
474cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else {
475cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (braceStack.length == 0) {
476cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        inBlock = true;
477cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        var substring = pattern.substring(prevPos, pos);
478cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (substring != '') {
479cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          results.push({
480cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            type: goog.i18n.MessageFormat.Element_.STRING,
481cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            value: substring
482cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          });
483cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
484cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        prevPos = pos + 1;
485cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
486cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      braceStack.push('{');
487cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
488cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
489cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
490cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Take care of the final string, and check if the braceStack is empty.
491cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  goog.asserts.assert(braceStack.length == 0,
492cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                      'There are mismatched { or } in the pattern.');
493cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
494cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var substring = pattern.substring(prevPos);
495cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (substring != '') {
496cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    results.push({
497cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      type: goog.i18n.MessageFormat.Element_.STRING,
498cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      value: substring
499cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    });
500cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
501cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
502cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return results;
503cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
504cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
505cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
506cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
507cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * A regular expression to parse the plural block, extracting the argument
508cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * index and offset (if any).
509cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {RegExp}
510cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
511cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
512cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.PLURAL_BLOCK_RE_ =
513cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    /^\s*(\w+)\s*,\s*plural\s*,(?:\s*offset:(\d+))?/;
514cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
515cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
516cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
517cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * A regular expression to parse the ordinal block, extracting the argument
518cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * index.
519cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {RegExp}
520cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
521cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
522cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.ORDINAL_BLOCK_RE_ = /^\s*(\w+)\s*,\s*selectordinal\s*,/;
523cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
524cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
525cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
526cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * A regular expression to parse the select block, extracting the argument
527cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * index.
528cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {RegExp}
529cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
530cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
531cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.SELECT_BLOCK_RE_ = /^\s*(\w+)\s*,\s*select\s*,/;
532cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
533cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
534cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
535cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Detects which type of a block is the pattern.
536cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string} pattern Content of the block.
537cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {goog.i18n.MessageFormat.BlockType_} One of the block types.
538cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
539cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
540cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.parseBlockType_ = function(pattern) {
541cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (goog.i18n.MessageFormat.PLURAL_BLOCK_RE_.test(pattern)) {
542cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return goog.i18n.MessageFormat.BlockType_.PLURAL;
543cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
544cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
545cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (goog.i18n.MessageFormat.ORDINAL_BLOCK_RE_.test(pattern)) {
546cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return goog.i18n.MessageFormat.BlockType_.ORDINAL;
547cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
548cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
549cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (goog.i18n.MessageFormat.SELECT_BLOCK_RE_.test(pattern)) {
550cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return goog.i18n.MessageFormat.BlockType_.SELECT;
551cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
552cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
553cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (/^\s*\w+\s*/.test(pattern)) {
554cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return goog.i18n.MessageFormat.BlockType_.SIMPLE;
555cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
556cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
557cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return goog.i18n.MessageFormat.BlockType_.UNKNOWN;
558cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
559cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
560cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
561cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
562cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Parses generic block.
563cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string} pattern Content of the block to parse.
564cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {!Array.<!Object>} Subblocks marked as strings, select...
565cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
566cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
567cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.parseBlock_ = function(pattern) {
568cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var result = [];
569cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var parts = this.extractParts_(pattern);
570cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (var i = 0; i < parts.length; i++) {
571cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var block = {};
572cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (goog.i18n.MessageFormat.Element_.STRING == parts[i].type) {
573cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      block.type = goog.i18n.MessageFormat.BlockType_.STRING;
574cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      block.value = parts[i].value;
575cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else if (goog.i18n.MessageFormat.Element_.BLOCK == parts[i].type) {
576cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var blockType = this.parseBlockType_(parts[i].value);
577cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
578cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      switch (blockType) {
579cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        case goog.i18n.MessageFormat.BlockType_.SELECT:
580cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          block.type = goog.i18n.MessageFormat.BlockType_.SELECT;
581cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          block.value = this.parseSelectBlock_(parts[i].value);
582cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          break;
583cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        case goog.i18n.MessageFormat.BlockType_.PLURAL:
584cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          block.type = goog.i18n.MessageFormat.BlockType_.PLURAL;
585cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          block.value = this.parsePluralBlock_(parts[i].value);
586cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          break;
587cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        case goog.i18n.MessageFormat.BlockType_.ORDINAL:
588cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          block.type = goog.i18n.MessageFormat.BlockType_.ORDINAL;
589cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          block.value = this.parseOrdinalBlock_(parts[i].value);
590cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          break;
591cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        case goog.i18n.MessageFormat.BlockType_.SIMPLE:
592cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          block.type = goog.i18n.MessageFormat.BlockType_.SIMPLE;
593cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          block.value = parts[i].value;
594cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          break;
595cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        default:
596cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          goog.asserts.fail('Unknown block type.');
597cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
598cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else {
599cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      goog.asserts.fail('Unknown part of the pattern.');
600cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
601cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    result.push(block);
602cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
603cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
604cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return result;
605cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
606cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
607cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
608cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
609cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Parses a select type of a block and produces JSON object for it.
610cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string} pattern Subpattern that needs to be parsed as select pattern.
611cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {!Object} Object with select block info.
612cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
613cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
614cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.parseSelectBlock_ = function(pattern) {
615cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var argumentIndex = '';
616cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var replaceRegex = goog.i18n.MessageFormat.SELECT_BLOCK_RE_;
617cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  pattern = pattern.replace(replaceRegex, function(string, name) {
618cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    argumentIndex = name;
619cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return '';
620cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  });
621cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var result = {};
622cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  result.argumentIndex = argumentIndex;
623cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
624cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var parts = this.extractParts_(pattern);
625cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Looking for (key block)+ sequence. One of the keys has to be "other".
626cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var pos = 0;
627cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  while (pos < parts.length) {
628cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var key = parts[pos].value;
629cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    goog.asserts.assertString(key, 'Missing select key element.');
630cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
631cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    pos++;
632cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    goog.asserts.assert(pos < parts.length,
633cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                        'Missing or invalid select value element.');
634cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
635cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (goog.i18n.MessageFormat.Element_.BLOCK == parts[pos].type) {
636cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var value = this.parseBlock_(parts[pos].value);
637cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else {
638cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      goog.asserts.fail('Expected block type.');
639cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
640cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    result[key.replace(/\s/g, '')] = value;
641cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    pos++;
642cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
643cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
644cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  goog.asserts.assertArray(result[goog.i18n.MessageFormat.OTHER_],
645cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                           'Missing other key in select statement.');
646cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return result;
647cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
648cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
649cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
650cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
651cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Parses a plural type of a block and produces JSON object for it.
652cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string} pattern Subpattern that needs to be parsed as plural pattern.
653cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {!Object} Object with select block info.
654cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
655cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
656cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.parsePluralBlock_ = function(pattern) {
657cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var argumentIndex = '';
658cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var argumentOffset = 0;
659cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var replaceRegex = goog.i18n.MessageFormat.PLURAL_BLOCK_RE_;
660cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  pattern = pattern.replace(replaceRegex, function(string, name, offset) {
661cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    argumentIndex = name;
662cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (offset) {
663cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      argumentOffset = parseInt(offset, 10);
664cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
665cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return '';
666cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  });
667cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
668cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var result = {};
669cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  result.argumentIndex = argumentIndex;
670cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  result.argumentOffset = argumentOffset;
671cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
672cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var parts = this.extractParts_(pattern);
673cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Looking for (key block)+ sequence.
674cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var pos = 0;
675cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  while (pos < parts.length) {
676cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var key = parts[pos].value;
677cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    goog.asserts.assertString(key, 'Missing plural key element.');
678cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
679cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    pos++;
680cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    goog.asserts.assert(pos < parts.length,
681cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                        'Missing or invalid plural value element.');
682cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
683cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (goog.i18n.MessageFormat.Element_.BLOCK == parts[pos].type) {
684cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var value = this.parseBlock_(parts[pos].value);
685cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else {
686cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      goog.asserts.fail('Expected block type.');
687cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
688cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    result[key.replace(/\s*(?:=)?(\w+)\s*/, '$1')] = value;
689cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    pos++;
690cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
691cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
692cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  goog.asserts.assertArray(result[goog.i18n.MessageFormat.OTHER_],
693cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                           'Missing other key in plural statement.');
694cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
695cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return result;
696cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
697cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
698cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
699cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
700cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Parses an ordinal type of a block and produces JSON object for it.
701cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * For example the input string:
702cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  '{FOO, selectordinal, one {Message A}other {Message B}}'
703cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Should result in the output object:
704cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * {
705cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *   argumentIndex: 'FOO',
706cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *   argumentOffest: 0,
707cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *   one: [ { type: 4, value: 'Message A' } ],
708cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *   other: [ { type: 4, value: 'Message B' } ]
709cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * }
710cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string} pattern Subpattern that needs to be parsed as plural pattern.
711cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {!Object} Object with select block info.
712cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
713cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
714cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.parseOrdinalBlock_ = function(pattern) {
715cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var argumentIndex = '';
716cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var replaceRegex = goog.i18n.MessageFormat.ORDINAL_BLOCK_RE_;
717cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  pattern = pattern.replace(replaceRegex, function(string, name) {
718cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    argumentIndex = name;
719cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return '';
720cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  });
721cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
722cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var result = {};
723cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  result.argumentIndex = argumentIndex;
724cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  result.argumentOffset = 0;
725cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
726cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var parts = this.extractParts_(pattern);
727cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Looking for (key block)+ sequence.
728cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var pos = 0;
729cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  while (pos < parts.length) {
730cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var key = parts[pos].value;
731cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    goog.asserts.assertString(key, 'Missing ordinal key element.');
732cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
733cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    pos++;
734cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    goog.asserts.assert(pos < parts.length,
735cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                        'Missing or invalid ordinal value element.');
736cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
737cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (goog.i18n.MessageFormat.Element_.BLOCK == parts[pos].type) {
738cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var value = this.parseBlock_(parts[pos].value);
739cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else {
740cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      goog.asserts.fail('Expected block type.');
741cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
742cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    result[key.replace(/\s*(?:=)?(\w+)\s*/, '$1')] = value;
743cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    pos++;
744cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
745cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
746cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  goog.asserts.assertArray(result[goog.i18n.MessageFormat.OTHER_],
747cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                           'Missing other key in selectordinal statement.');
748cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
749cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return result;
750cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
751cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
752cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
753cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
754cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Builds a placeholder from the last index of the array.
755cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!Array} literals All literals encountered during parse.
756cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {string} \uFDDF_ + last index + _.
757cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
758cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
759cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.i18n.MessageFormat.prototype.buildPlaceholder_ = function(literals) {
760cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  goog.asserts.assert(literals.length > 0, 'Literal array is empty.');
761cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
762cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var index = (literals.length - 1).toString(10);
763cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return goog.i18n.MessageFormat.LITERAL_PLACEHOLDER_ + index + '_';
764cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
765