1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 
  9 /** @private */
 10 SC.STRING_TITLEIZE_REGEXP = (/([\s|\-|\_|\n])([^\s|\-|\_|\n]?)/g);
 11 SC.STRING_HUMANIZE_REGEXP = (/[\-_]/g);
 12 SC.STRING_REGEXP_ESCAPED_REGEXP = (/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g);
 13 
 14 /** @private
 15   Since there are many strings that are commonly dasherized(), we'll maintain
 16   // a cache.  Moreover, we'll pre-add some common ones.
 17 */
 18 SC.STRING_DASHERIZE_CACHE = {
 19   top:      'top',
 20   left:     'left',
 21   right:    'right',
 22   bottom:   'bottom',
 23   width:    'width',
 24   height:   'height',
 25   minWidth: 'min-width',
 26   maxWidth: 'max-width'
 27 };
 28 
 29 /**
 30   @namespace
 31   @lends SC.String
 32 */
 33 SC.mixin(SC.String, {
 34 
 35   /**
 36     Capitalizes every word in a string.  Unlike titleize, spaces or dashes
 37     will remain in-tact.
 38 
 39     ## Examples
 40 
 41       - **Input String** -> **Output String**
 42       - my favorite items -> My Favorite Items
 43       - css-class-name -> Css-Class-Name
 44       - action_name -> Action_Name
 45       - innerHTML -> InnerHTML
 46 
 47     @param {String} str String to capitalize each letter2
 48     @returns {String} capitalized string
 49   */
 50   capitalizeEach: function(str) {
 51     return str.replace(SC.STRING_TITLEIZE_REGEXP,
 52       function(subStr, sep, character) {
 53         return (character) ? (sep + character.toUpperCase()) : sep;
 54       }).capitalize();
 55   },
 56 
 57   /**
 58     Converts a string to a title.  This will decamelize the string, convert
 59     separators to spaces and capitalize every word.
 60 
 61     ## Examples
 62 
 63       - **Input String** -> **Output String**
 64       - my favorite items -> My Favorite Items
 65       - css-class-name -> Css Class Name
 66       - action_name -> Action Name
 67       - innerHTML -> Inner HTML
 68 
 69     @param {String} str String to titleize
 70     @return {String} titleized string.
 71   */
 72   titleize: function(str) {
 73     var ret = str.replace(SC.STRING_DECAMELIZE_REGEXP,'$1_$2'); // decamelize
 74     return ret.replace(SC.STRING_TITLEIZE_REGEXP,
 75       function(subStr, separater, character) {
 76         return character ? ' ' + character.toUpperCase() : ' ';
 77       }).capitalize();
 78   },
 79 
 80   /**
 81     Converts the string into a class name.  This method will camelize your
 82     string and then capitalize the first letter.
 83 
 84     ## Examples
 85 
 86       - **Input String** -> **Output String**
 87       - my favorite items -> MyFavoriteItems
 88       - css-class-name -> CssClassName
 89       - action_name -> ActionName
 90       - innerHTML -> InnerHtml
 91 
 92     @param {String} str String to classify
 93     @returns {String}
 94   */
 95   classify: function(str) {
 96     var ret = str.replace(SC.STRING_TITLEIZE_REGEXP,
 97       function(subStr, separater, character) {
 98         return character ? character.toUpperCase() : '';
 99       });
100     var first = ret.charAt(0), upper = first.toUpperCase();
101     return first !== upper ? upper + ret.slice(1) : ret;
102   },
103 
104   /**
105     Converts a camelized string or a string with dashes or underscores into
106     a string with components separated by spaces.
107 
108     ## Examples
109 
110       - **Input String** -> **Output String**
111       - my favorite items -> my favorite items
112       - css-class-name -> css class name
113       - action_name -> action name
114       - innerHTML -> inner html
115 
116     @param {String} str String to humanize
117     @returns {String} the humanized string.
118   */
119   humanize: function(str) {
120     return SC.String.decamelize(str).replace(SC.STRING_HUMANIZE_REGEXP,' ');
121   },
122 
123   /**
124     Will escape a string so it can be securely used in a regular expression.
125 
126     Useful when you need to use user input in a regular expression without
127     having to worry about it breaking code if any reserved regular expression
128     characters are used.
129 
130     @param {String} str String to escape for regex
131     @returns {String} the string properly escaped for use in a regexp.
132   */
133   escapeForRegExp: function(str) {
134     return str.replace(SC.STRING_REGEXP_ESCAPED_REGEXP, "\\$1");
135   },
136 
137   /**
138     Removes any standard diacritic characters from the string. So, for
139     example, all instances of 'Á' will become 'A'.
140 
141     @param {String} str String to remove diacritics from
142     @returns {String} the modified string
143   */
144   removeDiacritics: function(str) {
145     // Lazily create the SC.diacriticMappingTable object.
146     var diacriticMappingTable = SC.diacriticMappingTable;
147     if (!diacriticMappingTable) {
148       SC.diacriticMappingTable = {
149        'À':'A', 'Á':'A', 'Â':'A', 'Ã':'A', 'Ä':'A', 'Å':'A', 'Ā':'A', 'Ă':'A',
150        'Ą':'A', 'Ǎ':'A', 'Ǟ':'A', 'Ǡ':'A', 'Ǻ':'A', 'Ȁ':'A', 'Ȃ':'A', 'Ȧ':'A',
151        'Ḁ':'A', 'Ạ':'A', 'Ả':'A', 'Ấ':'A', 'Ầ':'A', 'Ẩ':'A', 'Ẫ':'A', 'Ậ':'A',
152        'Ắ':'A', 'Ằ':'A', 'Ẳ':'A', 'Ẵ':'A', 'Ặ':'A', 'Å':'A', 'Ḃ':'B', 'Ḅ':'B',
153        'Ḇ':'B', 'Ç':'C', 'Ć':'C', 'Ĉ':'C', 'Ċ':'C', 'Č':'C', 'Ḉ':'C', 'Ď':'D',
154        'Ḋ':'D', 'Ḍ':'D', 'Ḏ':'D', 'Ḑ':'D', 'Ḓ':'D', 'È':'E', 'É':'E', 'Ê':'E',
155        'Ë':'E', 'Ē':'E', 'Ĕ':'E', 'Ė':'E', 'Ę':'E', 'Ě':'E', 'Ȅ':'E', 'Ȇ':'E',
156        'Ȩ':'E', 'Ḕ':'E', 'Ḗ':'E', 'Ḙ':'E', 'Ḛ':'E', 'Ḝ':'E', 'Ẹ':'E', 'Ẻ':'E',
157        'Ẽ':'E', 'Ế':'E', 'Ề':'E', 'Ể':'E', 'Ễ':'E', 'Ệ':'E', 'Ḟ':'F', 'Ĝ':'G',
158        'Ğ':'G', 'Ġ':'G', 'Ģ':'G', 'Ǧ':'G', 'Ǵ':'G', 'Ḡ':'G', 'Ĥ':'H', 'Ȟ':'H',
159        'Ḣ':'H', 'Ḥ':'H', 'Ḧ':'H', 'Ḩ':'H', 'Ḫ':'H', 'Ì':'I', 'Í':'I', 'Î':'I',
160        'Ï':'I', 'Ĩ':'I', 'Ī':'I', 'Ĭ':'I', 'Į':'I', 'İ':'I', 'Ǐ':'I', 'Ȉ':'I',
161        'Ȋ':'I', 'Ḭ':'I', 'Ḯ':'I', 'Ỉ':'I', 'Ị':'I', 'Ĵ':'J', 'Ķ':'K', 'Ǩ':'K',
162        'Ḱ':'K', 'Ḳ':'K', 'Ḵ':'K', 'Ĺ':'L', 'Ļ':'L', 'Ľ':'L', 'Ḷ':'L', 'Ḹ':'L',
163        'Ḻ':'L', 'Ḽ':'L', 'Ḿ':'M', 'Ṁ':'M', 'Ṃ':'M', 'Ñ':'N', 'Ń':'N', 'Ņ':'N',
164        'Ň':'N', 'Ǹ':'N', 'Ṅ':'N', 'Ṇ':'N', 'Ṉ':'N', 'Ṋ':'N', 'Ò':'O', 'Ó':'O',
165        'Ô':'O', 'Õ':'O', 'Ö':'O', 'Ō':'O', 'Ŏ':'O', 'Ő':'O', 'Ơ':'O', 'Ǒ':'O',
166        'Ǫ':'O', 'Ǭ':'O', 'Ȍ':'O', 'Ȏ':'O', 'Ȫ':'O', 'Ȭ':'O', 'Ȯ':'O', 'Ȱ':'O',
167        'Ṍ':'O', 'Ṏ':'O', 'Ṑ':'O', 'Ṓ':'O', 'Ọ':'O', 'Ỏ':'O', 'Ố':'O', 'Ồ':'O',
168        'Ổ':'O', 'Ỗ':'O', 'Ộ':'O', 'Ớ':'O', 'Ờ':'O', 'Ở':'O', 'Ỡ':'O', 'Ợ':'O',
169        'Ṕ':'P', 'Ṗ':'P', 'Ŕ':'R', 'Ŗ':'R', 'Ř':'R', 'Ȑ':'R', 'Ȓ':'R', 'Ṙ':'R',
170        'Ṛ':'R', 'Ṝ':'R', 'Ṟ':'R', 'Ś':'S', 'Ŝ':'S', 'Ş':'S', 'Š':'S', 'Ș':'S',
171        'Ṡ':'S', 'Ṣ':'S', 'Ṥ':'S', 'Ṧ':'S', 'Ṩ':'S', 'Ţ':'T', 'Ť':'T', 'Ț':'T',
172        'Ṫ':'T', 'Ṭ':'T', 'Ṯ':'T', 'Ṱ':'T', 'Ù':'U', 'Ú':'U', 'Û':'U', 'Ü':'U',
173        'Ũ':'U', 'Ū':'U', 'Ŭ':'U', 'Ů':'U', 'Ű':'U', 'Ų':'U', 'Ư':'U', 'Ǔ':'U',
174        'Ǖ':'U', 'Ǘ':'U', 'Ǚ':'U', 'Ǜ':'U', 'Ȕ':'U', 'Ȗ':'U', 'Ṳ':'U', 'Ṵ':'U',
175        'Ṷ':'U', 'Ṹ':'U', 'Ṻ':'U', 'Ụ':'U', 'Ủ':'U', 'Ứ':'U', 'Ừ':'U', 'Ử':'U',
176        'Ữ':'U', 'Ự':'U', 'Ṽ':'V', 'Ṿ':'V', 'Ŵ':'W', 'Ẁ':'W', 'Ẃ':'W', 'Ẅ':'W',
177        'Ẇ':'W', 'Ẉ':'W', 'Ẋ':'X', 'Ẍ':'X', 'Ý':'Y', 'Ŷ':'Y', 'Ÿ':'Y', 'Ȳ':'Y',
178        'Ẏ':'Y', 'Ỳ':'Y', 'Ỵ':'Y', 'Ỷ':'Y', 'Ỹ':'Y', 'Ź':'Z', 'Ż':'Z', 'Ž':'Z',
179        'Ẑ':'Z', 'Ẓ':'Z', 'Ẕ':'Z',
180        '`': '`',
181        'à':'a', 'á':'a', 'â':'a', 'ã':'a', 'ä':'a', 'å':'a', 'ā':'a', 'ă':'a',
182        'ą':'a', 'ǎ':'a', 'ǟ':'a', 'ǡ':'a', 'ǻ':'a', 'ȁ':'a', 'ȃ':'a', 'ȧ':'a',
183        'ḁ':'a', 'ạ':'a', 'ả':'a', 'ấ':'a', 'ầ':'a', 'ẩ':'a', 'ẫ':'a', 'ậ':'a',
184        'ắ':'a', 'ằ':'a', 'ẳ':'a', 'ẵ':'a', 'ặ':'a', 'ḃ':'b', 'ḅ':'b', 'ḇ':'b',
185        'ç':'c', 'ć':'c', 'ĉ':'c', 'ċ':'c', 'č':'c', 'ḉ':'c', 'ď':'d', 'ḋ':'d',
186        'ḍ':'d', 'ḏ':'d', 'ḑ':'d', 'ḓ':'d', 'è':'e', 'é':'e', 'ê':'e', 'ë':'e',
187        'ē':'e', 'ĕ':'e', 'ė':'e', 'ę':'e', 'ě':'e', 'ȅ':'e', 'ȇ':'e', 'ȩ':'e',
188        'ḕ':'e', 'ḗ':'e', 'ḙ':'e', 'ḛ':'e', 'ḝ':'e', 'ẹ':'e', 'ẻ':'e', 'ẽ':'e',
189        'ế':'e', 'ề':'e', 'ể':'e', 'ễ':'e', 'ệ':'e', 'ḟ':'f', 'ĝ':'g', 'ğ':'g',
190        'ġ':'g', 'ģ':'g', 'ǧ':'g', 'ǵ':'g', 'ḡ':'g', 'ĥ':'h', 'ȟ':'h', 'ḣ':'h',
191        'ḥ':'h', 'ḧ':'h', 'ḩ':'h', 'ḫ':'h', 'ẖ':'h', 'ì':'i', 'í':'i', 'î':'i',
192        'ï':'i', 'ĩ':'i', 'ī':'i', 'ĭ':'i', 'į':'i', 'ǐ':'i', 'ȉ':'i', 'ȋ':'i',
193        'ḭ':'i', 'ḯ':'i', 'ỉ':'i', 'ị':'i', 'ĵ':'j', 'ǰ':'j', 'ķ':'k', 'ǩ':'k',
194        'ḱ':'k', 'ḳ':'k', 'ḵ':'k', 'ĺ':'l', 'ļ':'l', 'ľ':'l', 'ḷ':'l', 'ḹ':'l',
195        'ḻ':'l', 'ḽ':'l', 'ḿ':'m', 'ṁ':'m', 'ṃ':'m', 'ñ':'n', 'ń':'n', 'ņ':'n',
196        'ň':'n', 'ǹ':'n', 'ṅ':'n', 'ṇ':'n', 'ṉ':'n', 'ṋ':'n', 'ò':'o', 'ó':'o',
197        'ô':'o', 'õ':'o', 'ö':'o', 'ō':'o', 'ŏ':'o', 'ő':'o', 'ơ':'o', 'ǒ':'o',
198        'ǫ':'o', 'ǭ':'o', 'ȍ':'o', 'ȏ':'o', 'ȫ':'o', 'ȭ':'o', 'ȯ':'o', 'ȱ':'o',
199        'ṍ':'o', 'ṏ':'o', 'ṑ':'o', 'ṓ':'o', 'ọ':'o', 'ỏ':'o', 'ố':'o', 'ồ':'o',
200        'ổ':'o', 'ỗ':'o', 'ộ':'o', 'ớ':'o', 'ờ':'o', 'ở':'o', 'ỡ':'o', 'ợ':'o',
201        'ṕ':'p', 'ṗ':'p', 'ŕ':'r', 'ŗ':'r', 'ř':'r', 'ȑ':'r', 'ȓ':'r', 'ṙ':'r',
202        'ṛ':'r', 'ṝ':'r', 'ṟ':'r', 'ś':'s', 'ŝ':'s', 'ş':'s', 'š':'s', 'ș':'s',
203        'ṡ':'s', 'ṣ':'s', 'ṥ':'s', 'ṧ':'s', 'ṩ':'s', 'ţ':'t', 'ť':'t', 'ț':'t',
204        'ṫ':'t', 'ṭ':'t', 'ṯ':'t', 'ṱ':'t', 'ẗ':'t', 'ù':'u', 'ú':'u', 'û':'u',
205        'ü':'u', 'ũ':'u', 'ū':'u', 'ŭ':'u', 'ů':'u', 'ű':'u', 'ų':'u', 'ư':'u',
206        'ǔ':'u', 'ǖ':'u', 'ǘ':'u', 'ǚ':'u', 'ǜ':'u', 'ȕ':'u', 'ȗ':'u', 'ṳ':'u',
207        'ṵ':'u', 'ṷ':'u', 'ṹ':'u', 'ṻ':'u', 'ụ':'u', 'ủ':'u', 'ứ':'u', 'ừ':'u',
208        'ử':'u', 'ữ':'u', 'ự':'u', 'ṽ':'v', 'ṿ':'v', 'ŵ':'w', 'ẁ':'w', 'ẃ':'w',
209        'ẅ':'w', 'ẇ':'w', 'ẉ':'w', 'ẘ':'w', 'ẋ':'x', 'ẍ':'x', 'ý':'y', 'ÿ':'y',
210        'ŷ':'y', 'ȳ':'y', 'ẏ':'y', 'ẙ':'y', 'ỳ':'y', 'ỵ':'y', 'ỷ':'y', 'ỹ':'y',
211        'ź':'z', 'ż':'z', 'ž':'z', 'ẑ':'z', 'ẓ':'z', 'ẕ':'z'
212       };
213       diacriticMappingTable = SC.diacriticMappingTable;
214     }
215 
216     var original, replacement, ret = "",
217         length = str.length;
218 
219     for (var i = 0; i <= length; ++i) {
220       original = str.charAt(i);
221       replacement = diacriticMappingTable[original];
222       ret += replacement || original;
223     }
224     return ret;
225   },
226 
227 
228   /**
229     Converts a word into its plural form.
230 
231     @param {String} str String to pluralize
232     @returns {String} the plural form of the string
233   */
234   pluralize: function(str) {
235     var inflectionConstants = SC.Locale.currentLocale.inflectionConstants;
236 
237     // Fast path if there is no inflection constants for a locale
238     if (!inflectionConstants) return str.toString();
239 
240     var idx, len,
241       compare = str.split(/\s/).pop(), //check only the last word of a string
242       restOfString = str.replace(compare,''),
243       isCapitalized = compare.charAt(0).match(/[A-Z]/) ? true : false;
244 
245     compare = compare.toLowerCase();
246     for (idx=0, len=inflectionConstants.UNCOUNTABLE.length; idx < len; idx++) {
247       var uncountable = inflectionConstants.UNCOUNTABLE[idx];
248       if (compare == uncountable) {
249         return str.toString();
250       }
251     }
252     for (idx=0, len=inflectionConstants.IRREGULAR.length; idx < len; idx++) {
253       var singular = inflectionConstants.IRREGULAR[idx][0],
254           plural   = inflectionConstants.IRREGULAR[idx][1];
255       if ((compare == singular) || (compare == plural)) {
256         if(isCapitalized) plural = plural.capitalize();
257         return restOfString + plural;
258       }
259     }
260     for (idx=0, len=inflectionConstants.PLURAL.length; idx < len; idx++) {
261       var regex          = inflectionConstants.PLURAL[idx][0],
262         replace_string = inflectionConstants.PLURAL[idx][1];
263       if (regex.test(compare)) {
264         return str.replace(regex, replace_string);
265       }
266     }
267   },
268 
269   /**
270     Converts a word into its singular form.
271 
272     @param {String} str String to singularize
273     @returns {String} the singular form of the string
274   */
275   singularize: function(str) {
276     var inflectionConstants = SC.Locale.currentLocale.inflectionConstants;
277 
278     // Fast path if there is no inflection constants for a locale
279     if (!inflectionConstants) return str.toString();
280 
281     var idx, len,
282       compare = str.split(/\s/).pop(), //check only the last word of a string
283       restOfString = str.replace(compare,''),
284       isCapitalized = compare.charAt(0).match(/[A-Z]/) ? true : false;
285 
286     compare = compare.toLowerCase();
287     for (idx=0, len=inflectionConstants.UNCOUNTABLE.length; idx < len; idx++) {
288       var uncountable = inflectionConstants.UNCOUNTABLE[idx];
289       if (compare == uncountable) {
290         return str.toString();
291       }
292     }
293     for (idx=0, len=inflectionConstants.IRREGULAR.length; idx < len; idx++) {
294       var singular = inflectionConstants.IRREGULAR[idx][0],
295         plural   = inflectionConstants.IRREGULAR[idx][1];
296       if ((compare == singular) || (compare == plural)) {
297         if(isCapitalized) singular = singular.capitalize();
298         return restOfString + singular;
299       }
300     }
301     for (idx=0, len=inflectionConstants.SINGULAR.length; idx < len; idx++) {
302       var regex          = inflectionConstants.SINGULAR[idx][0],
303           replace_string = inflectionConstants.SINGULAR[idx][1];
304       if (regex.test(compare)) {
305         return str.replace(regex, replace_string);
306       }
307     }
308   }
309 
310 });
311 
312 /** @private */
313 SC.String.strip = SC.String.trim;
314