1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
26: 27: 28: 29:
30: if (!class_exists('Object')) {
31: uses('object');
32: }
33: if (!class_exists('Set')) {
34: require LIBS . 'set.php';
35: }
36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46:
47: class Inflector extends Object {
48: 49: 50: 51: 52: 53:
54: var $pluralized = array();
55: 56: 57: 58: 59: 60: 61:
62: var $pluralRules = array();
63: 64: 65: 66: 67: 68:
69: var $singularized = array();
70: 71: 72: 73: 74: 75: 76:
77: var $singularRules = array();
78: 79: 80: 81: 82: 83:
84: var $__pluralRules = array();
85: 86: 87: 88: 89: 90:
91: var $__uninflectedPlural = array();
92: 93: 94: 95: 96: 97:
98: var $__irregularPlural = array();
99: 100: 101: 102: 103: 104:
105: var $__singularRules = array();
106: 107: 108: 109: 110: 111:
112: var $__uninflectedSingular = array();
113: 114: 115: 116: 117: 118:
119: var $__irregularSingular = array();
120: 121: 122: 123: 124: 125:
126: function &getInstance() {
127: static $instance = array();
128:
129: if (!$instance) {
130: $instance[0] =& new Inflector();
131: if (file_exists(CONFIGS.'inflections.php')) {
132: include(CONFIGS.'inflections.php');
133: $instance[0]->__pluralRules = $pluralRules;
134: $instance[0]->__uninflectedPlural = $uninflectedPlural;
135: $instance[0]->__irregularPlural = $irregularPlural;
136: $instance[0]->__singularRules = $singularRules;
137: $instance[0]->__uninflectedSingular = $uninflectedPlural;
138: $instance[0]->__irregularSingular = array_flip($irregularPlural);
139: }
140: }
141: return $instance[0];
142: }
143: 144: 145: 146: 147: 148:
149: function __initPluralRules() {
150: $corePluralRules = array(
151: '/(s)tatus$/i' => '\1\2tatuses',
152: '/(quiz)$/i' => '\1zes',
153: '/^(ox)$/i' => '\1\2en',
154: '/([m|l])ouse$/i' => '\1ice',
155: '/(matr|vert|ind)(ix|ex)$/i' => '\1ices',
156: '/(x|ch|ss|sh)$/i' => '\1es',
157: '/([^aeiouy]|qu)y$/i' => '\1ies',
158: '/(hive)$/i' => '\1s',
159: '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
160: '/sis$/i' => 'ses',
161: '/([ti])um$/i' => '\1a',
162: '/(p)erson$/i' => '\1eople',
163: '/(m)an$/i' => '\1en',
164: '/(c)hild$/i' => '\1hildren',
165: '/(buffal|tomat)o$/i' => '\1\2oes',
166: '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i',
167: '/us$/' => 'uses',
168: '/(alias)$/i' => '\1es',
169: '/(ax|cris|test)is$/i' => '\1es',
170: '/s$/' => 's',
171: '/^$/' => '',
172: '/$/' => 's');
173:
174: $coreUninflectedPlural = array(
175: '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', 'Amoyese',
176: 'bison', 'Borghese', 'bream', 'breeches', 'britches', 'buffalo', 'cantus', 'carp', 'chassis', 'clippers',
177: 'cod', 'coitus', 'Congoese', 'contretemps', 'corps', 'debris', 'diabetes', 'djinn', 'eland', 'elk',
178: 'equipment', 'Faroese', 'flounder', 'Foochowese', 'gallows', 'Genevese', 'Genoese', 'Gilbertese', 'graffiti',
179: 'headquarters', 'herpes', 'hijinks', 'Hottentotese', 'information', 'innings', 'jackanapes', 'Kiplingese',
180: 'Kongoese', 'Lucchese', 'mackerel', 'Maltese', 'media', 'mews', 'moose', 'mumps', 'Nankingese', 'news',
181: 'nexus', 'Niasese', 'Pekingese', 'People', 'Piedmontese', 'pincers', 'Pistoiese', 'pliers', 'Portuguese', 'proceedings',
182: 'rabies', 'rice', 'rhinoceros', 'salmon', 'Sarawakese', 'scissors', 'sea[- ]bass', 'series', 'Shavese', 'shears',
183: 'siemens', 'species', 'swine', 'testes', 'trousers', 'trout', 'tuna', 'Vermontese', 'Wenchowese',
184: 'whiting', 'wildebeest', 'Yengeese');
185:
186: $coreIrregularPlural = array(
187: 'atlas' => 'atlases',
188: 'beef' => 'beefs',
189: 'brother' => 'brothers',
190: 'child' => 'children',
191: 'corpus' => 'corpuses',
192: 'cow' => 'cows',
193: 'ganglion' => 'ganglions',
194: 'genie' => 'genies',
195: 'genus' => 'genera',
196: 'graffito' => 'graffiti',
197: 'hoof' => 'hoofs',
198: 'loaf' => 'loaves',
199: 'man' => 'men',
200: 'money' => 'monies',
201: 'mongoose' => 'mongooses',
202: 'move' => 'moves',
203: 'mythos' => 'mythoi',
204: 'numen' => 'numina',
205: 'occiput' => 'occiputs',
206: 'octopus' => 'octopuses',
207: 'opus' => 'opuses',
208: 'ox' => 'oxen',
209: 'penis' => 'penises',
210: 'person' => 'people',
211: 'sex' => 'sexes',
212: 'soliloquy' => 'soliloquies',
213: 'testis' => 'testes',
214: 'trilby' => 'trilbys',
215: 'turf' => 'turfs');
216:
217: $pluralRules = Set::pushDiff($this->__pluralRules, $corePluralRules);
218: $uninflected = Set::pushDiff($this->__uninflectedPlural, $coreUninflectedPlural);
219: $irregular = Set::pushDiff($this->__irregularPlural, $coreIrregularPlural);
220:
221: $this->pluralRules = array('pluralRules' => $pluralRules, 'uninflected' => $uninflected, 'irregular' => $irregular);
222: $this->pluralized = array();
223: }
224: 225: 226: 227: 228: 229: 230: 231: 232:
233: function pluralize($word) {
234: $_this =& Inflector::getInstance();
235: if (!isset($_this->pluralRules) || empty($_this->pluralRules)) {
236: $_this->__initPluralRules();
237: }
238:
239: if (isset($_this->pluralized[$word])) {
240: return $_this->pluralized[$word];
241: }
242: extract($_this->pluralRules);
243:
244: if (!isset($regexUninflected) || !isset($regexIrregular)) {
245: $regexUninflected = __enclose(implode( '|', $uninflected));
246: $regexIrregular = __enclose(implode( '|', array_keys($irregular)));
247: $_this->pluralRules['regexUninflected'] = $regexUninflected;
248: $_this->pluralRules['regexIrregular'] = $regexIrregular;
249: }
250:
251: if (preg_match('/^(' . $regexUninflected . ')$/i', $word, $regs)) {
252: $_this->pluralized[$word] = $word;
253: return $word;
254: }
255:
256: if (preg_match('/(.*)\\b(' . $regexIrregular . ')$/i', $word, $regs)) {
257: $_this->pluralized[$word] = $regs[1] . substr($word, 0, 1) . substr($irregular[strtolower($regs[2])], 1);
258: return $_this->pluralized[$word];
259: }
260:
261: foreach ($pluralRules as $rule => $replacement) {
262: if (preg_match($rule, $word)) {
263: $_this->pluralized[$word] = preg_replace($rule, $replacement, $word);
264: return $_this->pluralized[$word];
265: }
266: }
267: }
268: 269: 270: 271: 272: 273:
274: function __initSingularRules() {
275: $coreSingularRules = array(
276: '/(s)tatuses$/i' => '\1\2tatus',
277: '/^(.*)(menu)s$/i' => '\1\2',
278: '/(quiz)zes$/i' => '\\1',
279: '/(matr)ices$/i' => '\1ix',
280: '/(vert|ind)ices$/i' => '\1ex',
281: '/^(ox)en/i' => '\1',
282: '/(alias)(es)*$/i' => '\1',
283: '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us',
284: '/([ftw]ax)es/' => '\1',
285: '/(cris|ax|test)es$/i' => '\1is',
286: '/(shoe)s$/i' => '\1',
287: '/(o)es$/i' => '\1',
288: '/ouses$/' => 'ouse',
289: '/uses$/' => 'us',
290: '/([m|l])ice$/i' => '\1ouse',
291: '/(x|ch|ss|sh)es$/i' => '\1',
292: '/(m)ovies$/i' => '\1\2ovie',
293: '/(s)eries$/i' => '\1\2eries',
294: '/([^aeiouy]|qu)ies$/i' => '\1y',
295: '/([lr])ves$/i' => '\1f',
296: '/(tive)s$/i' => '\1',
297: '/(hive)s$/i' => '\1',
298: '/(drive)s$/i' => '\1',
299: '/([^fo])ves$/i' => '\1fe',
300: '/(^analy)ses$/i' => '\1sis',
301: '/(analy|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
302: '/([ti])a$/i' => '\1um',
303: '/(p)eople$/i' => '\1\2erson',
304: '/(m)en$/i' => '\1an',
305: '/(c)hildren$/i' => '\1\2hild',
306: '/(n)ews$/i' => '\1\2ews',
307: '/eaus$/' => 'eau',
308: '/^(.*us)$/' => '\\1',
309: '/s$/i' => '');
310:
311: $coreUninflectedSingular = array(
312: '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', '.*ss', 'Amoyese',
313: 'bison', 'Borghese', 'bream', 'breeches', 'britches', 'buffalo', 'cantus', 'carp', 'chassis', 'clippers',
314: 'cod', 'coitus', 'Congoese', 'contretemps', 'corps', 'debris', 'diabetes', 'djinn', 'eland', 'elk',
315: 'equipment', 'Faroese', 'flounder', 'Foochowese', 'gallows', 'Genevese', 'Genoese', 'Gilbertese', 'graffiti',
316: 'headquarters', 'herpes', 'hijinks', 'Hottentotese', 'information', 'innings', 'jackanapes', 'Kiplingese',
317: 'Kongoese', 'Lucchese', 'mackerel', 'Maltese', 'media', 'mews', 'moose', 'mumps', 'Nankingese', 'news',
318: 'nexus', 'Niasese', 'Pekingese', 'Piedmontese', 'pincers', 'Pistoiese', 'pliers', 'Portuguese', 'proceedings',
319: 'rabies', 'rice', 'rhinoceros', 'salmon', 'Sarawakese', 'scissors', 'sea[- ]bass', 'series', 'Shavese', 'shears',
320: 'siemens', 'species', 'swine', 'testes', 'trousers', 'trout', 'tuna', 'Vermontese', 'Wenchowese',
321: 'whiting', 'wildebeest', 'Yengeese'
322: );
323:
324: $coreIrregularSingular = array(
325: 'atlases' => 'atlas',
326: 'beefs' => 'beef',
327: 'brothers' => 'brother',
328: 'children' => 'child',
329: 'corpuses' => 'corpus',
330: 'cows' => 'cow',
331: 'ganglions' => 'ganglion',
332: 'genies' => 'genie',
333: 'genera' => 'genus',
334: 'graffiti' => 'graffito',
335: 'hoofs' => 'hoof',
336: 'loaves' => 'loaf',
337: 'men' => 'man',
338: 'monies' => 'money',
339: 'mongooses' => 'mongoose',
340: 'moves' => 'move',
341: 'mythoi' => 'mythos',
342: 'numina' => 'numen',
343: 'occiputs' => 'occiput',
344: 'octopuses' => 'octopus',
345: 'opuses' => 'opus',
346: 'oxen' => 'ox',
347: 'penises' => 'penis',
348: 'people' => 'person',
349: 'sexes' => 'sex',
350: 'soliloquies' => 'soliloquy',
351: 'testes' => 'testis',
352: 'trilbys' => 'trilby',
353: 'turfs' => 'turf',
354: 'waves' => 'wave'
355: );
356:
357: $singularRules = Set::pushDiff($this->__singularRules, $coreSingularRules);
358: $uninflected = Set::pushDiff($this->__uninflectedSingular, $coreUninflectedSingular);
359: $irregular = Set::pushDiff($this->__irregularSingular, $coreIrregularSingular);
360:
361: $this->singularRules = array('singularRules' => $singularRules, 'uninflected' => $uninflected, 'irregular' => $irregular);
362: $this->singularized = array();
363: }
364: 365: 366: 367: 368: 369: 370: 371: 372:
373: function singularize($word) {
374: $_this =& Inflector::getInstance();
375: if (!isset($_this->singularRules) || empty($_this->singularRules)) {
376: $_this->__initSingularRules();
377: }
378:
379: if (isset($_this->singularized[$word])) {
380: return $_this->singularized[$word];
381: }
382: extract($_this->singularRules);
383:
384: if (!isset($regexUninflected) || !isset($regexIrregular)) {
385: $regexUninflected = __enclose(implode( '|', $uninflected));
386: $regexIrregular = __enclose(implode( '|', array_keys($irregular)));
387: $_this->singularRules['regexUninflected'] = $regexUninflected;
388: $_this->singularRules['regexIrregular'] = $regexIrregular;
389: }
390:
391: if (preg_match('/^(' . $regexUninflected . ')$/i', $word, $regs)) {
392: $_this->singularized[$word] = $word;
393: return $word;
394: }
395:
396: if (preg_match('/(.*)\\b(' . $regexIrregular . ')$/i', $word, $regs)) {
397: $_this->singularized[$word] = $regs[1] . substr($word, 0, 1) . substr($irregular[strtolower($regs[2])], 1);
398: return $_this->singularized[$word];
399: }
400:
401: foreach ($singularRules as $rule => $replacement) {
402: if (preg_match($rule, $word)) {
403: $_this->singularized[$word] = preg_replace($rule, $replacement, $word);
404: return $_this->singularized[$word];
405: }
406: }
407: $_this->singularized[$word] = $word;
408: return $word;
409: }
410: 411: 412: 413: 414: 415: 416: 417: 418:
419: function camelize($lowerCaseAndUnderscoredWord) {
420: return str_replace(" ", "", ucwords(str_replace("_", " ", $lowerCaseAndUnderscoredWord)));
421: }
422: 423: 424: 425: 426: 427: 428: 429: 430:
431: function underscore($camelCasedWord) {
432: return strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $camelCasedWord));
433: }
434: 435: 436: 437: 438: 439: 440: 441: 442: 443:
444: function humanize($lowerCaseAndUnderscoredWord) {
445: return ucwords(str_replace("_", " ", $lowerCaseAndUnderscoredWord));
446: }
447: 448: 449: 450: 451: 452: 453: 454: 455:
456: function tableize($className) {
457: return Inflector::pluralize(Inflector::underscore($className));
458: }
459: 460: 461: 462: 463: 464: 465: 466: 467:
468: function classify($tableName) {
469: return Inflector::camelize(Inflector::singularize($tableName));
470: }
471: 472: 473: 474: 475: 476: 477: 478: 479:
480: function variable($string) {
481: $string = Inflector::camelize(Inflector::underscore($string));
482: $replace = strtolower(substr($string, 0, 1));
483: return $replace . substr($string, 1);
484: }
485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495:
496: function slug($string, $replacement = '_') {
497: if (!class_exists('String')) {
498: require LIBS . 'string.php';
499: }
500: $map = array(
501: '/à|á|å|â/' => 'a',
502: '/è|é|ê|ẽ|ë/' => 'e',
503: '/ì|í|î/' => 'i',
504: '/ò|ó|ô|ø/' => 'o',
505: '/ù|ú|ů|û/' => 'u',
506: '/ç/' => 'c',
507: '/ñ/' => 'n',
508: '/ä|æ/' => 'ae',
509: '/ö/' => 'oe',
510: '/ü/' => 'ue',
511: '/Ä/' => 'Ae',
512: '/Ü/' => 'Ue',
513: '/Ö/' => 'Oe',
514: '/ß/' => 'ss',
515: '/[^\w\s]/' => ' ',
516: '/\\s+/' => $replacement,
517: String::insert('/^[:replacement]+|[:replacement]+$/', array('replacement' => preg_quote($replacement, '/'))) => '',
518: );
519: return preg_replace(array_keys($map), array_values($map), $string);
520: }
521: }
522: 523: 524: 525: 526: 527:
528: function __enclose($string) {
529: return '(?:' . $string . ')';
530: }
531: ?>