1 <?php
2 /**
3 * @package FrameworkOnFramework
4 * @subpackage inflector
5 * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
6 * @license GNU General Public License version 2 or later; see LICENSE.txt
7 */
8 defined('FOF_INCLUDED') or die;
9
10 /**
11 * The FOFInflector is an adaptation of the Akelos PHP Inflector which is a PHP
12 * port from a Ruby on Rails project.
13 */
14
15 /**
16 * FOFInflector to pluralize and singularize English nouns.
17 *
18 * @package FrameworkOnFramework
19 * @since 1.0
20 */
21 class FOFInflector
22 {
23 /**
24 * Rules for pluralizing and singularizing of nouns.
25 *
26 * @var array
27 */
28 protected static $_rules = array
29 (
30 'pluralization' => array(
31 '/move$/i' => 'moves',
32 '/sex$/i' => 'sexes',
33 '/child$/i' => 'children',
34 '/children$/i' => 'children',
35 '/man$/i' => 'men',
36 '/men$/i' => 'men',
37 '/foot$/i' => 'feet',
38 '/feet$/i' => 'feet',
39 '/person$/i' => 'people',
40 '/people$/i' => 'people',
41 '/taxon$/i' => 'taxa',
42 '/taxa$/i' => 'taxa',
43 '/(quiz)$/i' => '$1zes',
44 '/^(ox)$/i' => '$1en',
45 '/oxen$/i' => 'oxen',
46 '/(m|l)ouse$/i' => '$1ice',
47 '/(m|l)ice$/i' => '$1ice',
48 '/(matr|vert|ind|suff)ix|ex$/i' => '$1ices',
49 '/(x|ch|ss|sh)$/i' => '$1es',
50 '/([^aeiouy]|qu)y$/i' => '$1ies',
51 '/(?:([^f])fe|([lr])f)$/i' => '$1$2ves',
52 '/sis$/i' => 'ses',
53 '/([ti]|addend)um$/i' => '$1a',
54 '/([ti]|addend)a$/i' => '$1a',
55 '/(alumn|formul)a$/i' => '$1ae',
56 '/(alumn|formul)ae$/i' => '$1ae',
57 '/(buffal|tomat|her)o$/i' => '$1oes',
58 '/(bu)s$/i' => '$1ses',
59 '/(alias|status)$/i' => '$1es',
60 '/(octop|vir)us$/i' => '$1i',
61 '/(octop|vir)i$/i' => '$1i',
62 '/(gen)us$/i' => '$1era',
63 '/(gen)era$/i' => '$1era',
64 '/(ax|test)is$/i' => '$1es',
65 '/s$/i' => 's',
66 '/$/' => 's',
67 ),
68 'singularization' => array(
69 '/cookies$/i' => 'cookie',
70 '/moves$/i' => 'move',
71 '/sexes$/i' => 'sex',
72 '/children$/i' => 'child',
73 '/men$/i' => 'man',
74 '/feet$/i' => 'foot',
75 '/people$/i' => 'person',
76 '/taxa$/i' => 'taxon',
77 '/databases$/i' => 'database',
78 '/menus$/i' => 'menu',
79 '/(quiz)zes$/i' => '\1',
80 '/(matr|suff)ices$/i' => '\1ix',
81 '/(vert|ind|cod)ices$/i' => '\1ex',
82 '/^(ox)en/i' => '\1',
83 '/(alias|status)es$/i' => '\1',
84 '/(tomato|hero|buffalo)es$/i' => '\1',
85 '/([octop|vir])i$/i' => '\1us',
86 '/(gen)era$/i' => '\1us',
87 '/(cris|^ax|test)es$/i' => '\1is',
88 '/is$/i' => 'is',
89 '/us$/i' => 'us',
90 '/ias$/i' => 'ias',
91 '/(shoe)s$/i' => '\1',
92 '/(o)es$/i' => '\1e',
93 '/(bus)es$/i' => '\1',
94 '/([m|l])ice$/i' => '\1ouse',
95 '/(x|ch|ss|sh)es$/i' => '\1',
96 '/(m)ovies$/i' => '\1ovie',
97 '/(s)eries$/i' => '\1eries',
98 '/(v)ies$/i' => '\1ie',
99 '/([^aeiouy]|qu)ies$/i' => '\1y',
100 '/([lr])ves$/i' => '\1f',
101 '/(tive)s$/i' => '\1',
102 '/(hive)s$/i' => '\1',
103 '/([^f])ves$/i' => '\1fe',
104 '/(^analy)ses$/i' => '\1sis',
105 '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
106 '/([ti]|addend)a$/i' => '\1um',
107 '/(alumn|formul)ae$/i' => '$1a',
108 '/(n)ews$/i' => '\1ews',
109 '/(.*)ss$/i' => '\1ss',
110 '/(.*)s$/i' => '\1',
111 ),
112 'countable' => array(
113 'aircraft',
114 'cannon',
115 'deer',
116 'equipment',
117 'fish',
118 'information',
119 'money',
120 'moose',
121 'rice',
122 'series',
123 'sheep',
124 'species',
125 'swine',
126 )
127 );
128
129 /**
130 * Cache of pluralized and singularized nouns.
131 *
132 * @var array
133 */
134 protected static $_cache = array(
135 'singularized' => array(),
136 'pluralized' => array()
137 );
138
139 /**
140 * Constructor
141 *
142 * Prevent creating instances of this class by making the constructor private
143 */
144 private function __construct()
145 {
146 }
147
148 public static function deleteCache()
149 {
150 static::$_cache['pluralized'] = array();
151 static::$_cache['singularized'] = array();
152 }
153
154 /**
155 * Add a word to the cache, useful to make exceptions or to add words in other languages.
156 *
157 * @param string $singular word.
158 * @param string $plural word.
159 *
160 * @return void
161 */
162 public static function addWord($singular, $plural)
163 {
164 static::$_cache['pluralized'][$singular] = $plural;
165 static::$_cache['singularized'][$plural] = $singular;
166 }
167
168 /**
169 * Singular English word to plural.
170 *
171 * @param string $word word to pluralize.
172 *
173 * @return string Plural noun.
174 */
175 public static function pluralize($word)
176 {
177 // Get the cached noun of it exists
178 if (isset(static::$_cache['pluralized'][$word]))
179 {
180 return static::$_cache['pluralized'][$word];
181 }
182
183 // Create the plural noun
184 if (in_array($word, self::$_rules['countable']))
185 {
186 static::$_cache['pluralized'][$word] = $word;
187
188 return $word;
189 }
190
191 foreach (self::$_rules['pluralization'] as $regexp => $replacement)
192 {
193 $matches = null;
194 $plural = preg_replace($regexp, $replacement, $word, -1, $matches);
195
196 if ($matches > 0)
197 {
198 static::$_cache['pluralized'][$word] = $plural;
199
200 return $plural;
201 }
202 }
203
204 static::$_cache['pluralized'][$word] = $word;
205
206 return static::$_cache['pluralized'][$word];
207 }
208
209 /**
210 * Plural English word to singular.
211 *
212 * @param string $word Word to singularize.
213 *
214 * @return string Singular noun.
215 */
216 public static function singularize($word)
217 {
218 // Get the cached noun of it exists
219 if (isset(static::$_cache['singularized'][$word]))
220 {
221 return static::$_cache['singularized'][$word];
222 }
223
224 // Create the singular noun
225 if (in_array($word, self::$_rules['countable']))
226 {
227 static::$_cache['singularized'][$word] = $word;
228
229 return $word;
230 }
231
232 foreach (self::$_rules['singularization'] as $regexp => $replacement)
233 {
234 $matches = null;
235 $singular = preg_replace($regexp, $replacement, $word, -1, $matches);
236
237 if ($matches > 0)
238 {
239 static::$_cache['singularized'][$word] = $singular;
240
241 return $singular;
242 }
243 }
244
245 static::$_cache['singularized'][$word] = $word;
246
247 return static::$_cache['singularized'][$word];
248 }
249
250 /**
251 * Returns given word as CamelCased.
252 *
253 * Converts a word like "foo_bar" or "foo bar" to "FooBar". It
254 * will remove non alphanumeric characters from the word, so
255 * "who's online" will be converted to "WhoSOnline"
256 *
257 * @param string $word Word to convert to camel case.
258 *
259 * @return string UpperCamelCasedWord
260 */
261 public static function camelize($word)
262 {
263 $word = preg_replace('/[^a-zA-Z0-9\s]/', ' ', $word);
264 $word = str_replace(' ', '', ucwords(strtolower(str_replace('_', ' ', $word))));
265
266 return $word;
267 }
268
269 /**
270 * Converts a word "into_it_s_underscored_version"
271 *
272 * Convert any "CamelCased" or "ordinary Word" into an "underscored_word".
273 *
274 * @param string $word Word to underscore
275 *
276 * @return string Underscored word
277 */
278 public static function underscore($word)
279 {
280 $word = preg_replace('/(\s)+/', '_', $word);
281 $word = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $word));
282
283 return $word;
284 }
285
286 /**
287 * Convert any "CamelCased" word into an array of strings
288 *
289 * Returns an array of strings each of which is a substring of string formed
290 * by splitting it at the camelcased letters.
291 *
292 * @param string $word Word to explode
293 *
294 * @return array Array of strings
295 */
296 public static function explode($word)
297 {
298 $result = explode('_', self::underscore($word));
299
300 return $result;
301 }
302
303 /**
304 * Convert an array of strings into a "CamelCased" word.
305 *
306 * @param array $words Array to implode
307 *
308 * @return string UpperCamelCasedWord
309 */
310 public static function implode($words)
311 {
312 $result = self::camelize(implode('_', $words));
313
314 return $result;
315 }
316
317 /**
318 * Returns a human-readable string from $word.
319 *
320 * Returns a human-readable string from $word, by replacing
321 * underscores with a space, and by upper-casing the initial
322 * character by default.
323 *
324 * @param string $word String to "humanize"
325 *
326 * @return string Human-readable word
327 */
328 public static function humanize($word)
329 {
330 $result = ucwords(strtolower(str_replace("_", " ", $word)));
331
332 return $result;
333 }
334
335 /**
336 * Converts a class name to its table name according to Koowa
337 * naming conventions.
338 *
339 * Converts "Person" to "people"
340 *
341 * @param string $className Class name for getting related table_name.
342 *
343 * @return string plural_table_name
344 *
345 * @see classify
346 */
347 public static function tableize($className)
348 {
349 $result = self::underscore($className);
350
351 if (!self::isPlural($className))
352 {
353 $result = self::pluralize($result);
354 }
355
356 return $result;
357 }
358
359 /**
360 * Converts a table name to its class name according to Koowa naming conventions.
361 *
362 * @param string $tableName Table name for getting related ClassName.
363 *
364 * @return string SingularClassName
365 *
366 * @example Converts "people" to "Person"
367 * @see tableize
368 */
369 public static function classify($tableName)
370 {
371 $result = self::camelize(self::singularize($tableName));
372
373 return $result;
374 }
375
376 /**
377 * Returns camelBacked version of a string. Same as camelize but first char is lowercased.
378 *
379 * @param string $string String to be camelBacked.
380 *
381 * @return string
382 *
383 * @see camelize
384 */
385 public static function variablize($string)
386 {
387 $string = self::camelize(self::underscore($string));
388 $result = strtolower(substr($string, 0, 1));
389 $variable = preg_replace('/\\w/', $result, $string, 1);
390
391 return $variable;
392 }
393
394 /**
395 * Check to see if an English word is singular
396 *
397 * @param string $string The word to check
398 *
399 * @return boolean
400 */
401 public static function isSingular($string)
402 {
403 // Check cache assuming the string is plural.
404 $singular = isset(static::$_cache['singularized'][$string]) ? static::$_cache['singularized'][$string] : null;
405 $plural = $singular && isset(static::$_cache['pluralized'][$singular]) ? static::$_cache['pluralized'][$singular] : null;
406
407 if ($singular && $plural)
408 {
409 return $plural != $string;
410 }
411
412 // If string is not in the cache, try to pluralize and singularize it.
413 return self::singularize(self::pluralize($string)) == $string;
414 }
415
416 /**
417 * Check to see if an Enlish word is plural.
418 *
419 * @param string $string String to be checked.
420 *
421 * @return boolean
422 */
423 public static function isPlural($string)
424 {
425 // Check cache assuming the string is singular.
426 $plural = isset(static::$_cache['pluralized'][$string]) ? static::$_cache['pluralized'][$string] : null;
427 $singular = $plural && isset(static::$_cache['singularized'][$plural]) ? static::$_cache['singularized'][$plural] : null;
428
429 if ($plural && $singular)
430 {
431 return $singular != $string;
432 }
433
434 // If string is not in the cache, try to singularize and pluralize it.
435 return self::pluralize(self::singularize($string)) == $string;
436 }
437
438 /**
439 * Gets a part of a CamelCased word by index.
440 *
441 * Use a negative index to start at the last part of the word (-1 is the
442 * last part)
443 *
444 * @param string $string Word
445 * @param integer $index Index of the part
446 * @param string $default Default value
447 *
448 * @return string
449 */
450 public static function getPart($string, $index, $default = null)
451 {
452 $parts = self::explode($string);
453
454 if ($index < 0)
455 {
456 $index = count($parts) + $index;
457 }
458
459 return isset($parts[$index]) ? $parts[$index] : $default;
460 }
461 }
462