Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / util / Inflector.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and
17  * {@link #ordinalize ordinalizes} words. Sample usage:
18  *
19  *     //turning singular words into plurals
20  *     Ext.util.Inflector.pluralize('word'); //'words'
21  *     Ext.util.Inflector.pluralize('person'); //'people'
22  *     Ext.util.Inflector.pluralize('sheep'); //'sheep'
23  *
24  *     //turning plurals into singulars
25  *     Ext.util.Inflector.singularize('words'); //'word'
26  *     Ext.util.Inflector.singularize('people'); //'person'
27  *     Ext.util.Inflector.singularize('sheep'); //'sheep'
28  *
29  *     //ordinalizing numbers
30  *     Ext.util.Inflector.ordinalize(11); //"11th"
31  *     Ext.util.Inflector.ordinalize(21); //"21th"
32  *     Ext.util.Inflector.ordinalize(1043); //"1043rd"
33  *
34  * # Customization
35  *
36  * The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
37  * rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages.
38  * Here is how we might add a rule that pluralizes "ox" to "oxen":
39  *
40  *     Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
41  *
42  * Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string. In
43  * this case, the regular expression will only match the string "ox", and will replace that match with "oxen". Here's
44  * how we could add the inverse rule:
45  *
46  *     Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
47  *
48  * Note that the ox/oxen rules are present by default.
49  */
50 Ext.define('Ext.util.Inflector', {
51
52     /* Begin Definitions */
53
54     singleton: true,
55
56     /* End Definitions */
57
58     /**
59      * @private
60      * The registered plural tuples. Each item in the array should contain two items - the first must be a regular
61      * expression that matchers the singular form of a word, the second must be a String that replaces the matched
62      * part of the regular expression. This is managed by the {@link #plural} method.
63      * @property {Array} plurals
64      */
65     plurals: [
66         [(/(quiz)$/i),                "$1zes"  ],
67         [(/^(ox)$/i),                 "$1en"   ],
68         [(/([m|l])ouse$/i),           "$1ice"  ],
69         [(/(matr|vert|ind)ix|ex$/i),  "$1ices" ],
70         [(/(x|ch|ss|sh)$/i),          "$1es"   ],
71         [(/([^aeiouy]|qu)y$/i),       "$1ies"  ],
72         [(/(hive)$/i),                "$1s"    ],
73         [(/(?:([^f])fe|([lr])f)$/i),  "$1$2ves"],
74         [(/sis$/i),                   "ses"    ],
75         [(/([ti])um$/i),              "$1a"    ],
76         [(/(buffal|tomat|potat)o$/i), "$1oes"  ],
77         [(/(bu)s$/i),                 "$1ses"  ],
78         [(/(alias|status|sex)$/i),    "$1es"   ],
79         [(/(octop|vir)us$/i),         "$1i"    ],
80         [(/(ax|test)is$/i),           "$1es"   ],
81         [(/^person$/),                "people" ],
82         [(/^man$/),                   "men"    ],
83         [(/^(child)$/),               "$1ren"  ],
84         [(/s$/i),                     "s"      ],
85         [(/$/),                       "s"      ]
86     ],
87
88     /**
89      * @private
90      * The set of registered singular matchers. Each item in the array should contain two items - the first must be a
91      * regular expression that matches the plural form of a word, the second must be a String that replaces the
92      * matched part of the regular expression. This is managed by the {@link #singular} method.
93      * @property {Array} singulars
94      */
95     singulars: [
96       [(/(quiz)zes$/i),                                                    "$1"     ],
97       [(/(matr)ices$/i),                                                   "$1ix"   ],
98       [(/(vert|ind)ices$/i),                                               "$1ex"   ],
99       [(/^(ox)en/i),                                                       "$1"     ],
100       [(/(alias|status)es$/i),                                             "$1"     ],
101       [(/(octop|vir)i$/i),                                                 "$1us"   ],
102       [(/(cris|ax|test)es$/i),                                             "$1is"   ],
103       [(/(shoe)s$/i),                                                      "$1"     ],
104       [(/(o)es$/i),                                                        "$1"     ],
105       [(/(bus)es$/i),                                                      "$1"     ],
106       [(/([m|l])ice$/i),                                                   "$1ouse" ],
107       [(/(x|ch|ss|sh)es$/i),                                               "$1"     ],
108       [(/(m)ovies$/i),                                                     "$1ovie" ],
109       [(/(s)eries$/i),                                                     "$1eries"],
110       [(/([^aeiouy]|qu)ies$/i),                                            "$1y"    ],
111       [(/([lr])ves$/i),                                                    "$1f"    ],
112       [(/(tive)s$/i),                                                      "$1"     ],
113       [(/(hive)s$/i),                                                      "$1"     ],
114       [(/([^f])ves$/i),                                                    "$1fe"   ],
115       [(/(^analy)ses$/i),                                                  "$1sis"  ],
116       [(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i), "$1$2sis"],
117       [(/([ti])a$/i),                                                      "$1um"   ],
118       [(/(n)ews$/i),                                                       "$1ews"  ],
119       [(/people$/i),                                                       "person" ],
120       [(/s$/i),                                                            ""       ]
121     ],
122
123     /**
124      * @private
125      * The registered uncountable words
126      * @property {String[]} uncountable
127      */
128      uncountable: [
129         "sheep",
130         "fish",
131         "series",
132         "species",
133         "money",
134         "rice",
135         "information",
136         "equipment",
137         "grass",
138         "mud",
139         "offspring",
140         "deer",
141         "means"
142     ],
143
144     /**
145      * Adds a new singularization rule to the Inflector. See the intro docs for more information
146      * @param {RegExp} matcher The matcher regex
147      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
148      */
149     singular: function(matcher, replacer) {
150         this.singulars.unshift([matcher, replacer]);
151     },
152
153     /**
154      * Adds a new pluralization rule to the Inflector. See the intro docs for more information
155      * @param {RegExp} matcher The matcher regex
156      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
157      */
158     plural: function(matcher, replacer) {
159         this.plurals.unshift([matcher, replacer]);
160     },
161
162     /**
163      * Removes all registered singularization rules
164      */
165     clearSingulars: function() {
166         this.singulars = [];
167     },
168
169     /**
170      * Removes all registered pluralization rules
171      */
172     clearPlurals: function() {
173         this.plurals = [];
174     },
175
176     /**
177      * Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish)
178      * @param {String} word The word to test
179      * @return {Boolean} True if the word is transnumeral
180      */
181     isTransnumeral: function(word) {
182         return Ext.Array.indexOf(this.uncountable, word) != -1;
183     },
184
185     /**
186      * Returns the pluralized form of a word (e.g. Ext.util.Inflector.pluralize('word') returns 'words')
187      * @param {String} word The word to pluralize
188      * @return {String} The pluralized form of the word
189      */
190     pluralize: function(word) {
191         if (this.isTransnumeral(word)) {
192             return word;
193         }
194
195         var plurals = this.plurals,
196             length  = plurals.length,
197             tuple, regex, i;
198
199         for (i = 0; i < length; i++) {
200             tuple = plurals[i];
201             regex = tuple[0];
202
203             if (regex == word || (regex.test && regex.test(word))) {
204                 return word.replace(regex, tuple[1]);
205             }
206         }
207
208         return word;
209     },
210
211     /**
212      * Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word')
213      * @param {String} word The word to singularize
214      * @return {String} The singularized form of the word
215      */
216     singularize: function(word) {
217         if (this.isTransnumeral(word)) {
218             return word;
219         }
220
221         var singulars = this.singulars,
222             length    = singulars.length,
223             tuple, regex, i;
224
225         for (i = 0; i < length; i++) {
226             tuple = singulars[i];
227             regex = tuple[0];
228
229             if (regex == word || (regex.test && regex.test(word))) {
230                 return word.replace(regex, tuple[1]);
231             }
232         }
233
234         return word;
235     },
236
237     /**
238      * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data
239      * package
240      * @param {String} word The word to classify
241      * @return {String} The classified version of the word
242      */
243     classify: function(word) {
244         return Ext.String.capitalize(this.singularize(word));
245     },
246
247     /**
248      * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the
249      * number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc
250      * @param {Number} number The number to ordinalize
251      * @return {String} The ordinalized number
252      */
253     ordinalize: function(number) {
254         var parsed = parseInt(number, 10),
255             mod10  = parsed % 10,
256             mod100 = parsed % 100;
257
258         //11 through 13 are a special case
259         if (11 <= mod100 && mod100 <= 13) {
260             return number + "th";
261         } else {
262             switch(mod10) {
263                 case 1 : return number + "st";
264                 case 2 : return number + "nd";
265                 case 3 : return number + "rd";
266                 default: return number + "th";
267             }
268         }
269     }
270 }, function() {
271     //aside from the rules above, there are a number of words that have irregular pluralization so we add them here
272     var irregulars = {
273             alumnus: 'alumni',
274             cactus : 'cacti',
275             focus  : 'foci',
276             nucleus: 'nuclei',
277             radius: 'radii',
278             stimulus: 'stimuli',
279             ellipsis: 'ellipses',
280             paralysis: 'paralyses',
281             oasis: 'oases',
282             appendix: 'appendices',
283             index: 'indexes',
284             beau: 'beaux',
285             bureau: 'bureaux',
286             tableau: 'tableaux',
287             woman: 'women',
288             child: 'children',
289             man: 'men',
290             corpus:     'corpora',
291             criterion: 'criteria',
292             curriculum: 'curricula',
293             genus: 'genera',
294             memorandum: 'memoranda',
295             phenomenon: 'phenomena',
296             foot: 'feet',
297             goose: 'geese',
298             tooth: 'teeth',
299             antenna: 'antennae',
300             formula: 'formulae',
301             nebula: 'nebulae',
302             vertebra: 'vertebrae',
303             vita: 'vitae'
304         },
305         singular;
306
307     for (singular in irregulars) {
308         this.plural(singular, irregulars[singular]);
309         this.singular(irregulars[singular], singular);
310     }
311 });