438 lines
12 KiB
PHP
438 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 2007-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* @category Horde
|
|
* @package Support
|
|
* @license http://www.horde.org/licenses/bsd
|
|
*/
|
|
|
|
/**
|
|
* Horde Inflector class.
|
|
*
|
|
* @todo Add the locale-bubbling pattern from
|
|
* Horde_Date_Parser/Horde_Support_Numerizer
|
|
*
|
|
* @category Horde
|
|
* @package Support
|
|
* @license http://www.horde.org/licenses/bsd
|
|
*/
|
|
class Horde_Support_Inflector
|
|
{
|
|
/**
|
|
* Inflection cache
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_cache = array();
|
|
|
|
/**
|
|
* Rules for pluralizing English nouns.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_pluralizationRules = array(
|
|
'/move$/i' => 'moves',
|
|
'/sex$/i' => 'sexes',
|
|
'/child$/i' => 'children',
|
|
'/man$/i' => 'men',
|
|
'/foot$/i' => 'feet',
|
|
'/person$/i' => 'people',
|
|
'/(quiz)$/i' => '$1zes',
|
|
'/^(ox)$/i' => '$1en',
|
|
'/(m|l)ouse$/i' => '$1ice',
|
|
'/(matr|vert|ind)ix|ex$/i' => '$1ices',
|
|
'/(x|ch|ss|sh)$/i' => '$1es',
|
|
'/([^aeiouy]|qu)ies$/i' => '$1y',
|
|
'/([^aeiouy]|qu)y$/i' => '$1ies',
|
|
'/(?:([^f])fe|([lr])f)$/i' => '$1$2ves',
|
|
'/sis$/i' => 'ses',
|
|
'/([ti])um$/i' => '$1a',
|
|
'/(buffal|tomat)o$/i' => '$1oes',
|
|
'/(bu)s$/i' => '$1ses',
|
|
'/(alias|status)$/i' => '$1es',
|
|
'/(octop|vir)us$/i' => '$1i',
|
|
'/(ax|test)is$/i' => '$1es',
|
|
'/s$/i' => 's',
|
|
'/$/' => 's',
|
|
);
|
|
|
|
/**
|
|
* Rules for singularizing English nouns.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_singularizationRules = array(
|
|
'/cookies$/i' => 'cookie',
|
|
'/moves$/i' => 'move',
|
|
'/sexes$/i' => 'sex',
|
|
'/children$/i' => 'child',
|
|
'/men$/i' => 'man',
|
|
'/feet$/i' => 'foot',
|
|
'/people$/i' => 'person',
|
|
'/databases$/i'=> 'database',
|
|
'/(quiz)zes$/i' => '\1',
|
|
'/(matr)ices$/i' => '\1ix',
|
|
'/(vert|ind)ices$/i' => '\1ex',
|
|
'/^(ox)en/i' => '\1',
|
|
'/(alias|status)es$/i' => '\1',
|
|
'/([octop|vir])i$/i' => '\1us',
|
|
'/(cris|ax|test)es$/i' => '\1is',
|
|
'/(shoe)s$/i' => '\1',
|
|
'/(o)es$/i' => '\1',
|
|
'/(bus)es$/i' => '\1',
|
|
'/([m|l])ice$/i' => '\1ouse',
|
|
'/(x|ch|ss|sh)es$/i' => '\1',
|
|
'/(m)ovies$/i' => '\1ovie',
|
|
'/(s)eries$/i' => '\1eries',
|
|
'/([^aeiouy]|qu)ies$/i' => '\1y',
|
|
'/([lr])ves$/i' => '\1f',
|
|
'/(tive)s$/i' => '\1',
|
|
'/(hive)s$/i' => '\1',
|
|
'/([^f])ves$/i' => '\1fe',
|
|
'/(^analy)ses$/i' => '\1sis',
|
|
'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
|
|
'/([ti])a$/i' => '\1um',
|
|
'/(n)ews$/i' => '\1ews',
|
|
'/(.*)s$/i' => '\1',
|
|
);
|
|
|
|
/**
|
|
* An array of words with the same singular and plural spellings.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_uncountables = array(
|
|
'aircraft',
|
|
'cannon',
|
|
'deer',
|
|
'equipment',
|
|
'fish',
|
|
'information',
|
|
'money',
|
|
'moose',
|
|
'rice',
|
|
'series',
|
|
'sheep',
|
|
'species',
|
|
'swine',
|
|
);
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* Stores a map of the uncountable words for quicker checks.
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->_uncountables_keys = array_flip($this->_uncountables);
|
|
}
|
|
|
|
/**
|
|
* Adds an uncountable word.
|
|
*
|
|
* @param string $word The uncountable word.
|
|
*/
|
|
public function uncountable($word)
|
|
{
|
|
$this->_uncountables[] = $word;
|
|
$this->_uncountables_keys[$word] = true;
|
|
}
|
|
|
|
/**
|
|
* Singular English word to pluralize.
|
|
*
|
|
* @param string $word Word to pluralize.
|
|
*
|
|
* @return string Plural form of $word.
|
|
*/
|
|
public function pluralize($word)
|
|
{
|
|
if ($plural = $this->getCache($word, 'pluralize')) {
|
|
return $plural;
|
|
}
|
|
|
|
if (isset($this->_uncountables_keys[$word])) {
|
|
return $word;
|
|
}
|
|
|
|
foreach ($this->_pluralizationRules as $regexp => $replacement) {
|
|
$plural = preg_replace($regexp, $replacement, $word, -1, $matches);
|
|
if ($matches > 0) {
|
|
return $this->setCache($word, 'pluralize', $plural);
|
|
}
|
|
}
|
|
|
|
return $this->setCache($word, 'pluralize', $word);
|
|
}
|
|
|
|
/**
|
|
* Plural English word to singularize.
|
|
*
|
|
* @param string $word Word to singularize.
|
|
*
|
|
* @return string Singular form of $word.
|
|
*/
|
|
public function singularize($word)
|
|
{
|
|
if ($singular = $this->getCache($word, 'singularize')) {
|
|
return $singular;
|
|
}
|
|
|
|
if (isset($this->_uncountables_keys[$word])) {
|
|
return $word;
|
|
}
|
|
|
|
foreach ($this->_singularizationRules as $regexp => $replacement) {
|
|
$singular = preg_replace($regexp, $replacement, $word, -1, $matches);
|
|
if ($matches > 0) {
|
|
return $this->setCache($word, 'singularize', $singular);
|
|
}
|
|
}
|
|
|
|
return $this->setCache($word, 'singularize', $word);
|
|
}
|
|
|
|
/**
|
|
* Camel-cases a word.
|
|
*
|
|
* @todo Do we want locale-specific or locale-independent camel casing?
|
|
*
|
|
* @param string $word The word to camel-case.
|
|
* @param string $firstLetter Whether to upper or lower case the first.
|
|
* letter of each slash-separated section.
|
|
*
|
|
* @return string Camelized $word
|
|
*/
|
|
public function camelize($word, $firstLetter = 'upper')
|
|
{
|
|
if ($camelized = $this->getCache($word, 'camelize' . $firstLetter)) {
|
|
return $camelized;
|
|
}
|
|
|
|
$camelized = $word;
|
|
if (Horde_String::lower($camelized) != $camelized &&
|
|
strpos($camelized, '_') !== false) {
|
|
$camelized = str_replace('_', '/', $camelized);
|
|
}
|
|
if (strpos($camelized, '/') !== false) {
|
|
$camelized = str_replace('/', '/ ', $camelized);
|
|
}
|
|
if (strpos($camelized, '_') !== false) {
|
|
$camelized = strtr($camelized, '_', ' ');
|
|
}
|
|
|
|
$camelized = str_replace(' ', '', Horde_String::ucwords($camelized));
|
|
|
|
if ($firstLetter == 'lower') {
|
|
$parts = array();
|
|
foreach (explode('/', $camelized) as $part) {
|
|
$part[0] = Horde_String::lower($part[0]);
|
|
$parts[] = $part;
|
|
}
|
|
$camelized = implode('/', $parts);
|
|
}
|
|
|
|
return $this->setCache($word, 'camelize' . $firstLetter, $camelized);
|
|
}
|
|
|
|
/**
|
|
* Capitalizes all the words and replaces some characters in the string to
|
|
* create a nicer looking title.
|
|
*
|
|
* Titleize is meant for creating pretty output.
|
|
*
|
|
* See:
|
|
* - http://daringfireball.net/2008/05/title_case
|
|
* - http://daringfireball.net/2008/08/title_case_update
|
|
*
|
|
* Examples:
|
|
* 1. titleize("man from the boondocks") => "Man From The Boondocks"
|
|
* 2. titleize("x-men: the last stand") => "X Men: The Last Stand"
|
|
*/
|
|
public function titleize($word)
|
|
{
|
|
throw new Exception('not implemented yet');
|
|
}
|
|
|
|
/**
|
|
* The reverse of camelize().
|
|
*
|
|
* Makes an underscored form from the expression in the string.
|
|
*
|
|
* Examples:
|
|
* 1. underscore("ActiveRecord") => "active_record"
|
|
* 2. underscore("ActiveRecord_Errors") => "active_record_errors"
|
|
*
|
|
* @todo Do we want locale-specific or locale-independent lowercasing?
|
|
*/
|
|
public function underscore($camelCasedWord)
|
|
{
|
|
$word = $camelCasedWord;
|
|
if ($result = $this->getCache($word, 'underscore')) {
|
|
return $result;
|
|
}
|
|
$result = Horde_String::lower(preg_replace('/([a-z])([A-Z])/', "\${1}_\${2}", $word));
|
|
return $this->setCache($word, 'underscore', $result);
|
|
}
|
|
|
|
/**
|
|
* Replaces underscores with dashes in the string.
|
|
*
|
|
* Example:
|
|
* 1. dasherize("puni_puni") => "puni-puni"
|
|
*/
|
|
public function dasherize($underscoredWord)
|
|
{
|
|
if ($result = $this->getCache($underscoredWord, 'dasherize')) {
|
|
return $result;
|
|
}
|
|
|
|
$result = str_replace('_', '-', $this->underscore($underscoredWord));
|
|
return $this->setCache($underscoredWord, 'dasherize', $result);
|
|
}
|
|
|
|
/**
|
|
* Capitalizes the first word and turns underscores into spaces and strips
|
|
* _id.
|
|
*
|
|
* Like titleize(), this is meant for creating pretty output.
|
|
*
|
|
* Examples:
|
|
* 1. humanize("employee_salary") => "Employee salary"
|
|
* 2. humanize("author_id") => "Author"
|
|
*/
|
|
public function humanize($lowerCaseAndUnderscoredWord)
|
|
{
|
|
$word = $lowerCaseAndUnderscoredWord;
|
|
if ($result = $this->getCache($word, 'humanize')) {
|
|
return $result;
|
|
}
|
|
|
|
$result = ucfirst(str_replace('_', ' ', $this->underscore($word)));
|
|
if (substr($result, -3, 3) == ' id') {
|
|
$result = str_replace(' id', '', $result);
|
|
}
|
|
return $this->setCache($word, 'humanize', $result);
|
|
}
|
|
|
|
/**
|
|
* Removes the module part from the expression in the string.
|
|
*
|
|
* Examples:
|
|
* 1. demodulize("Fax_Job") => "Job"
|
|
* 1. demodulize("User") => "User"
|
|
*/
|
|
public function demodulize($classNameInModule)
|
|
{
|
|
$result = explode('_', $classNameInModule);
|
|
return array_pop($result);
|
|
}
|
|
|
|
/**
|
|
* Creates the name of a table like Rails does for models to table names.
|
|
*
|
|
* This method uses the pluralize() method on the last word in the string.
|
|
*
|
|
* Examples:
|
|
* 1. tableize("RawScaledScorer") => "raw_scaled_scorers"
|
|
* 2. tableize("egg_and_ham") => "egg_and_hams"
|
|
* 3. tableize("fancyCategory") => "fancy_categories"
|
|
*/
|
|
public function tableize($className)
|
|
{
|
|
if ($result = $this->getCache($className, 'tableize')) {
|
|
return $result;
|
|
}
|
|
|
|
$result = $this->pluralize($this->underscore($className));
|
|
$result = str_replace('/', '_', $result);
|
|
return $this->setCache($className, 'tableize', $result);
|
|
}
|
|
|
|
/**
|
|
* Creates a class name from a table name like Rails does for table names
|
|
* to models.
|
|
*
|
|
* Examples:
|
|
* 1. classify("egg_and_hams") => "EggAndHam"
|
|
* 2. classify("post") => "Post"
|
|
*/
|
|
public function classify($tableName)
|
|
{
|
|
if ($result = $this->getCache($tableName, 'classify')) {
|
|
return $result;
|
|
}
|
|
$result = $this->camelize($this->singularize($tableName));
|
|
|
|
// classes use underscores instead of slashes for namespaces
|
|
$result = str_replace('/', '_', $result);
|
|
return $this->setCache($tableName, 'classify', $result);
|
|
}
|
|
|
|
/**
|
|
* Creates a foreign key name from a class name.
|
|
*
|
|
* $separateClassNameAndIdWithUnderscore sets whether the method should put
|
|
* '_' between the name and 'id'.
|
|
*
|
|
* Examples:
|
|
* 1. foreignKey("Message") => "message_id"
|
|
* 2. foreignKey("Message", false) => "messageid"
|
|
* 3. foreignKey("Fax_Job") => "fax_job_id"
|
|
*/
|
|
public function foreignKey($className, $separateClassNameAndIdWithUnderscore = true)
|
|
{
|
|
throw new Exception('not implemented yet');
|
|
}
|
|
|
|
/**
|
|
* Turns a number into an ordinal string used to denote the position in an
|
|
* ordered sequence such as 1st, 2nd, 3rd, 4th.
|
|
*
|
|
* Examples:
|
|
* 1. ordinalize(1) => "1st"
|
|
* 2. ordinalize(2) => "2nd"
|
|
* 3. ordinalize(1002) => "1002nd"
|
|
* 4. ordinalize(1003) => "1003rd"
|
|
*/
|
|
public function ordinalize($number)
|
|
{
|
|
throw new Exception('not implemented yet');
|
|
}
|
|
|
|
/**
|
|
* Clears the inflection cache.
|
|
*/
|
|
public function clearCache()
|
|
{
|
|
$this->_cache = array();
|
|
}
|
|
|
|
/**
|
|
* Retuns a cached inflection.
|
|
*
|
|
* @return string | false
|
|
*/
|
|
public function getCache($word, $rule)
|
|
{
|
|
return isset($this->_cache[$word . '|' . $rule]) ?
|
|
$this->_cache[$word . '|' . $rule] : false;
|
|
}
|
|
|
|
/**
|
|
* Caches an inflection.
|
|
*
|
|
* @param string $word The word being inflected.
|
|
* @param string $rule The inflection rule.
|
|
* @param string $value The inflected value of $word.
|
|
*
|
|
* @return string The inflected value
|
|
*/
|
|
public function setCache($word, $rule, $value)
|
|
{
|
|
$this->_cache[$word . '|' . $rule] = $value;
|
|
return $value;
|
|
}
|
|
}
|