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