1 <?php 2 /** 3 * Copyright 2007-2017 Horde LLC (http://www.horde.org/) 4 * 5 * @category Horde 6 * @package Support 7 * @license http://www.horde.org/licenses/bsd 8 */ 9 10 /** 11 * Horde Inflector class. 12 * 13 * @todo Add the locale-bubbling pattern from 14 * Horde_Date_Parser/Horde_Support_Numerizer 15 * 16 * @category Horde 17 * @package Support 18 * @license http://www.horde.org/licenses/bsd 19 */ 20 class Horde_Support_Inflector 21 { 22 /** 23 * Inflection cache 24 * 25 * @var array 26 */ 27 protected $_cache = array(); 28 29 /** 30 * Rules for pluralizing English nouns. 31 * 32 * @var array 33 */ 34 protected $_pluralizationRules = array( 35 '/move$/i' => 'moves', 36 '/sex$/i' => 'sexes', 37 '/child$/i' => 'children', 38 '/man$/i' => 'men', 39 '/foot$/i' => 'feet', 40 '/person$/i' => 'people', 41 '/(quiz)$/i' => '$1zes', 42 '/^(ox)$/i' => '$1en', 43 '/(m|l)ouse$/i' => '$1ice', 44 '/(matr|vert|ind)ix|ex$/i' => '$1ices', 45 '/(x|ch|ss|sh)$/i' => '$1es', 46 '/([^aeiouy]|qu)ies$/i' => '$1y', 47 '/([^aeiouy]|qu)y$/i' => '$1ies', 48 '/(?:([^f])fe|([lr])f)$/i' => '$1$2ves', 49 '/sis$/i' => 'ses', 50 '/([ti])um$/i' => '$1a', 51 '/(buffal|tomat)o$/i' => '$1oes', 52 '/(bu)s$/i' => '$1ses', 53 '/(alias|status)$/i' => '$1es', 54 '/(octop|vir)us$/i' => '$1i', 55 '/(ax|test)is$/i' => '$1es', 56 '/s$/i' => 's', 57 '/$/' => 's', 58 ); 59 60 /** 61 * Rules for singularizing English nouns. 62 * 63 * @var array 64 */ 65 protected $_singularizationRules = array( 66 '/cookies$/i' => 'cookie', 67 '/moves$/i' => 'move', 68 '/sexes$/i' => 'sex', 69 '/children$/i' => 'child', 70 '/men$/i' => 'man', 71 '/feet$/i' => 'foot', 72 '/people$/i' => 'person', 73 '/databases$/i'=> 'database', 74 '/(quiz)zes$/i' => '\1', 75 '/(matr)ices$/i' => '\1ix', 76 '/(vert|ind)ices$/i' => '\1ex', 77 '/^(ox)en/i' => '\1', 78 '/(alias|status)es$/i' => '\1', 79 '/([octop|vir])i$/i' => '\1us', 80 '/(cris|ax|test)es$/i' => '\1is', 81 '/(shoe)s$/i' => '\1', 82 '/(o)es$/i' => '\1', 83 '/(bus)es$/i' => '\1', 84 '/([m|l])ice$/i' => '\1ouse', 85 '/(x|ch|ss|sh)es$/i' => '\1', 86 '/(m)ovies$/i' => '\1ovie', 87 '/(s)eries$/i' => '\1eries', 88 '/([^aeiouy]|qu)ies$/i' => '\1y', 89 '/([lr])ves$/i' => '\1f', 90 '/(tive)s$/i' => '\1', 91 '/(hive)s$/i' => '\1', 92 '/([^f])ves$/i' => '\1fe', 93 '/(^analy)ses$/i' => '\1sis', 94 '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', 95 '/([ti])a$/i' => '\1um', 96 '/(n)ews$/i' => '\1ews', 97 '/(.*)s$/i' => '\1', 98 ); 99 100 /** 101 * An array of words with the same singular and plural spellings. 102 * 103 * @var array 104 */ 105 protected $_uncountables = array( 106 'aircraft', 107 'cannon', 108 'deer', 109 'equipment', 110 'fish', 111 'information', 112 'money', 113 'moose', 114 'rice', 115 'series', 116 'sheep', 117 'species', 118 'swine', 119 ); 120 121 /** 122 * Constructor. 123 * 124 * Stores a map of the uncountable words for quicker checks. 125 */ 126 public function __construct() 127 { 128 $this->_uncountables_keys = array_flip($this->_uncountables); 129 } 130 131 /** 132 * Adds an uncountable word. 133 * 134 * @param string $word The uncountable word. 135 */ 136 public function uncountable($word) 137 { 138 $this->_uncountables[] = $word; 139 $this->_uncountables_keys[$word] = true; 140 } 141 142 /** 143 * Singular English word to pluralize. 144 * 145 * @param string $word Word to pluralize. 146 * 147 * @return string Plural form of $word. 148 */ 149 public function pluralize($word) 150 { 151 if ($plural = $this->getCache($word, 'pluralize')) { 152 return $plural; 153 } 154 155 if (isset($this->_uncountables_keys[$word])) { 156 return $word; 157 } 158 159 foreach ($this->_pluralizationRules as $regexp => $replacement) { 160 $plural = preg_replace($regexp, $replacement, $word, -1, $matches); 161 if ($matches > 0) { 162 return $this->setCache($word, 'pluralize', $plural); 163 } 164 } 165 166 return $this->setCache($word, 'pluralize', $word); 167 } 168 169 /** 170 * Plural English word to singularize. 171 * 172 * @param string $word Word to singularize. 173 * 174 * @return string Singular form of $word. 175 */ 176 public function singularize($word) 177 { 178 if ($singular = $this->getCache($word, 'singularize')) { 179 return $singular; 180 } 181 182 if (isset($this->_uncountables_keys[$word])) { 183 return $word; 184 } 185 186 foreach ($this->_singularizationRules as $regexp => $replacement) { 187 $singular = preg_replace($regexp, $replacement, $word, -1, $matches); 188 if ($matches > 0) { 189 return $this->setCache($word, 'singularize', $singular); 190 } 191 } 192 193 return $this->setCache($word, 'singularize', $word); 194 } 195 196 /** 197 * Camel-cases a word. 198 * 199 * @todo Do we want locale-specific or locale-independent camel casing? 200 * 201 * @param string $word The word to camel-case. 202 * @param string $firstLetter Whether to upper or lower case the first. 203 * letter of each slash-separated section. 204 * 205 * @return string Camelized $word 206 */ 207 public function camelize($word, $firstLetter = 'upper') 208 { 209 if ($camelized = $this->getCache($word, 'camelize' . $firstLetter)) { 210 return $camelized; 211 } 212 213 $camelized = $word; 214 if (Horde_String::lower($camelized) != $camelized && 215 strpos($camelized, '_') !== false) { 216 $camelized = str_replace('_', '/', $camelized); 217 } 218 if (strpos($camelized, '/') !== false) { 219 $camelized = str_replace('/', '/ ', $camelized); 220 } 221 if (strpos($camelized, '_') !== false) { 222 $camelized = strtr($camelized, '_', ' '); 223 } 224 225 $camelized = str_replace(' ', '', Horde_String::ucwords($camelized)); 226 227 if ($firstLetter == 'lower') { 228 $parts = array(); 229 foreach (explode('/', $camelized) as $part) { 230 $part[0] = Horde_String::lower($part[0]); 231 $parts[] = $part; 232 } 233 $camelized = implode('/', $parts); 234 } 235 236 return $this->setCache($word, 'camelize' . $firstLetter, $camelized); 237 } 238 239 /** 240 * Capitalizes all the words and replaces some characters in the string to 241 * create a nicer looking title. 242 * 243 * Titleize is meant for creating pretty output. 244 * 245 * See: 246 * - http://daringfireball.net/2008/05/title_case 247 * - http://daringfireball.net/2008/08/title_case_update 248 * 249 * Examples: 250 * 1. titleize("man from the boondocks") => "Man From The Boondocks" 251 * 2. titleize("x-men: the last stand") => "X Men: The Last Stand" 252 */ 253 public function titleize($word) 254 { 255 throw new Exception('not implemented yet'); 256 } 257 258 /** 259 * The reverse of camelize(). 260 * 261 * Makes an underscored form from the expression in the string. 262 * 263 * Examples: 264 * 1. underscore("ActiveRecord") => "active_record" 265 * 2. underscore("ActiveRecord_Errors") => "active_record_errors" 266 * 267 * @todo Do we want locale-specific or locale-independent lowercasing? 268 */ 269 public function underscore($camelCasedWord) 270 { 271 $word = $camelCasedWord; 272 if ($result = $this->getCache($word, 'underscore')) { 273 return $result; 274 } 275 $result = Horde_String::lower(preg_replace('/([a-z])([A-Z])/', "\$1}_\$2}", $word)); 276 return $this->setCache($word, 'underscore', $result); 277 } 278 279 /** 280 * Replaces underscores with dashes in the string. 281 * 282 * Example: 283 * 1. dasherize("puni_puni") => "puni-puni" 284 */ 285 public function dasherize($underscoredWord) 286 { 287 if ($result = $this->getCache($underscoredWord, 'dasherize')) { 288 return $result; 289 } 290 291 $result = str_replace('_', '-', $this->underscore($underscoredWord)); 292 return $this->setCache($underscoredWord, 'dasherize', $result); 293 } 294 295 /** 296 * Capitalizes the first word and turns underscores into spaces and strips 297 * _id. 298 * 299 * Like titleize(), this is meant for creating pretty output. 300 * 301 * Examples: 302 * 1. humanize("employee_salary") => "Employee salary" 303 * 2. humanize("author_id") => "Author" 304 */ 305 public function humanize($lowerCaseAndUnderscoredWord) 306 { 307 $word = $lowerCaseAndUnderscoredWord; 308 if ($result = $this->getCache($word, 'humanize')) { 309 return $result; 310 } 311 312 $result = ucfirst(str_replace('_', ' ', $this->underscore($word))); 313 if (substr($result, -3, 3) == ' id') { 314 $result = str_replace(' id', '', $result); 315 } 316 return $this->setCache($word, 'humanize', $result); 317 } 318 319 /** 320 * Removes the module part from the expression in the string. 321 * 322 * Examples: 323 * 1. demodulize("Fax_Job") => "Job" 324 * 1. demodulize("User") => "User" 325 */ 326 public function demodulize($classNameInModule) 327 { 328 $result = explode('_', $classNameInModule); 329 return array_pop($result); 330 } 331 332 /** 333 * Creates the name of a table like Rails does for models to table names. 334 * 335 * This method uses the pluralize() method on the last word in the string. 336 * 337 * Examples: 338 * 1. tableize("RawScaledScorer") => "raw_scaled_scorers" 339 * 2. tableize("egg_and_ham") => "egg_and_hams" 340 * 3. tableize("fancyCategory") => "fancy_categories" 341 */ 342 public function tableize($className) 343 { 344 if ($result = $this->getCache($className, 'tableize')) { 345 return $result; 346 } 347 348 $result = $this->pluralize($this->underscore($className)); 349 $result = str_replace('/', '_', $result); 350 return $this->setCache($className, 'tableize', $result); 351 } 352 353 /** 354 * Creates a class name from a table name like Rails does for table names 355 * to models. 356 * 357 * Examples: 358 * 1. classify("egg_and_hams") => "EggAndHam" 359 * 2. classify("post") => "Post" 360 */ 361 public function classify($tableName) 362 { 363 if ($result = $this->getCache($tableName, 'classify')) { 364 return $result; 365 } 366 $result = $this->camelize($this->singularize($tableName)); 367 368 // classes use underscores instead of slashes for namespaces 369 $result = str_replace('/', '_', $result); 370 return $this->setCache($tableName, 'classify', $result); 371 } 372 373 /** 374 * Creates a foreign key name from a class name. 375 * 376 * $separateClassNameAndIdWithUnderscore sets whether the method should put 377 * '_' between the name and 'id'. 378 * 379 * Examples: 380 * 1. foreignKey("Message") => "message_id" 381 * 2. foreignKey("Message", false) => "messageid" 382 * 3. foreignKey("Fax_Job") => "fax_job_id" 383 */ 384 public function foreignKey($className, $separateClassNameAndIdWithUnderscore = true) 385 { 386 throw new Exception('not implemented yet'); 387 } 388 389 /** 390 * Turns a number into an ordinal string used to denote the position in an 391 * ordered sequence such as 1st, 2nd, 3rd, 4th. 392 * 393 * Examples: 394 * 1. ordinalize(1) => "1st" 395 * 2. ordinalize(2) => "2nd" 396 * 3. ordinalize(1002) => "1002nd" 397 * 4. ordinalize(1003) => "1003rd" 398 */ 399 public function ordinalize($number) 400 { 401 throw new Exception('not implemented yet'); 402 } 403 404 /** 405 * Clears the inflection cache. 406 */ 407 public function clearCache() 408 { 409 $this->_cache = array(); 410 } 411 412 /** 413 * Retuns a cached inflection. 414 * 415 * @return string | false 416 */ 417 public function getCache($word, $rule) 418 { 419 return isset($this->_cache[$word . '|' . $rule]) ? 420 $this->_cache[$word . '|' . $rule] : false; 421 } 422 423 /** 424 * Caches an inflection. 425 * 426 * @param string $word The word being inflected. 427 * @param string $rule The inflection rule. 428 * @param string $value The inflected value of $word. 429 * 430 * @return string The inflected value 431 */ 432 public function setCache($word, $rule, $value) 433 { 434 $this->_cache[$word . '|' . $rule] = $value; 435 return $value; 436 } 437 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body