Add Inflector component (from StringUtil of PropertyAccess)
This commit is contained in:
parent
93e09feeba
commit
8abebf6417
226
src/Symfony/Component/Inflector/Inflector.php
Normal file
226
src/Symfony/Component/Inflector/Inflector.php
Normal file
@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Inflector;
|
||||
|
||||
/**
|
||||
* Converts words between singular and plural forms.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Inflector
|
||||
{
|
||||
/**
|
||||
* Map English plural to singular suffixes.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @see http://english-zone.com/spelling/plurals.html
|
||||
* @see http://www.scribd.com/doc/3271143/List-of-100-Irregular-Plural-Nouns-in-English
|
||||
*/
|
||||
private static $pluralMap = array(
|
||||
// First entry: plural suffix, reversed
|
||||
// Second entry: length of plural suffix
|
||||
// Third entry: Whether the suffix may succeed a vocal
|
||||
// Fourth entry: Whether the suffix may succeed a consonant
|
||||
// Fifth entry: singular suffix, normal
|
||||
|
||||
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
|
||||
array('a', 1, true, true, array('on', 'um')),
|
||||
|
||||
// nebulae (nebula)
|
||||
array('ea', 2, true, true, 'a'),
|
||||
|
||||
// services (service)
|
||||
array('secivres', 8, true, true, 'service'),
|
||||
|
||||
// mice (mouse), lice (louse)
|
||||
array('eci', 3, false, true, 'ouse'),
|
||||
|
||||
// geese (goose)
|
||||
array('esee', 4, false, true, 'oose'),
|
||||
|
||||
// fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
|
||||
array('i', 1, true, true, 'us'),
|
||||
|
||||
// men (man), women (woman)
|
||||
array('nem', 3, true, true, 'man'),
|
||||
|
||||
// children (child)
|
||||
array('nerdlihc', 8, true, true, 'child'),
|
||||
|
||||
// oxen (ox)
|
||||
array('nexo', 4, false, false, 'ox'),
|
||||
|
||||
// indices (index), appendices (appendix), prices (price)
|
||||
array('seci', 4, false, true, array('ex', 'ix', 'ice')),
|
||||
|
||||
// selfies (selfie)
|
||||
array('seifles', 7, true, true, 'selfie'),
|
||||
|
||||
// movies (movie)
|
||||
array('seivom', 6, true, true, 'movie'),
|
||||
|
||||
// news (news)
|
||||
array('swen', 4, true, true, 'news'),
|
||||
|
||||
// series (series)
|
||||
array('seires', 6, true, true, 'series'),
|
||||
|
||||
// babies (baby)
|
||||
array('sei', 3, false, true, 'y'),
|
||||
|
||||
// accesses (access), addresses (address), kisses (kiss)
|
||||
array('sess', 4, true, false, 'ss'),
|
||||
|
||||
// analyses (analysis), ellipses (ellipsis), funguses (fungus),
|
||||
// neuroses (neurosis), theses (thesis), emphases (emphasis),
|
||||
// oases (oasis), crises (crisis), houses (house), bases (base),
|
||||
// atlases (atlas)
|
||||
array('ses', 3, true, true, array('s', 'se', 'sis')),
|
||||
|
||||
// objectives (objective), alternative (alternatives)
|
||||
array('sevit', 5, true, true, 'tive'),
|
||||
|
||||
// drives (drive)
|
||||
array('sevird', 6, false, true, 'drive'),
|
||||
|
||||
// lives (life), wives (wife)
|
||||
array('sevi', 4, false, true, 'ife'),
|
||||
|
||||
// moves (move)
|
||||
array('sevom', 5, true, true, 'move'),
|
||||
|
||||
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff)
|
||||
array('sev', 3, true, true, array('f', 've', 'ff')),
|
||||
|
||||
// axes (axis), axes (ax), axes (axe)
|
||||
array('sexa', 4, false, false, array('ax', 'axe', 'axis')),
|
||||
|
||||
// indexes (index), matrixes (matrix)
|
||||
array('sex', 3, true, false, 'x'),
|
||||
|
||||
// quizzes (quiz)
|
||||
array('sezz', 4, true, false, 'z'),
|
||||
|
||||
// bureaus (bureau)
|
||||
array('suae', 4, false, true, 'eau'),
|
||||
|
||||
// roses (rose), garages (garage), cassettes (cassette),
|
||||
// waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
|
||||
// shoes (shoe)
|
||||
array('se', 2, true, true, array('', 'e')),
|
||||
|
||||
// tags (tag)
|
||||
array('s', 1, true, true, ''),
|
||||
|
||||
// chateaux (chateau)
|
||||
array('xuae', 4, false, true, 'eau'),
|
||||
);
|
||||
|
||||
/**
|
||||
* This class should not be instantiated.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singular form of a word.
|
||||
*
|
||||
* If the method can't determine the form with certainty, an array of the
|
||||
* possible singulars is returned.
|
||||
*
|
||||
* @param string $plural A word in plural form
|
||||
*
|
||||
* @return string|array The singular form or an array of possible singular
|
||||
* forms
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function singularize($plural)
|
||||
{
|
||||
$pluralRev = strrev($plural);
|
||||
$lowerPluralRev = strtolower($pluralRev);
|
||||
$pluralLength = strlen($lowerPluralRev);
|
||||
|
||||
// The outer loop iterates over the entries of the plural table
|
||||
// The inner loop $j iterates over the characters of the plural suffix
|
||||
// in the plural table to compare them with the characters of the actual
|
||||
// given plural suffix
|
||||
foreach (self::$pluralMap as $map) {
|
||||
$suffix = $map[0];
|
||||
$suffixLength = $map[1];
|
||||
$j = 0;
|
||||
|
||||
// Compare characters in the plural table and of the suffix of the
|
||||
// given plural one by one
|
||||
while ($suffix[$j] === $lowerPluralRev[$j]) {
|
||||
// Let $j point to the next character
|
||||
++$j;
|
||||
|
||||
// Successfully compared the last character
|
||||
// Add an entry with the singular suffix to the singular array
|
||||
if ($j === $suffixLength) {
|
||||
// Is there any character preceding the suffix in the plural string?
|
||||
if ($j < $pluralLength) {
|
||||
$nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]);
|
||||
|
||||
if (!$map[2] && $nextIsVocal) {
|
||||
// suffix may not succeed a vocal but next char is one
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$map[3] && !$nextIsVocal) {
|
||||
// suffix may not succeed a consonant but next char is one
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$newBase = substr($plural, 0, $pluralLength - $suffixLength);
|
||||
$newSuffix = $map[4];
|
||||
|
||||
// Check whether the first character in the plural suffix
|
||||
// is uppercased. If yes, uppercase the first character in
|
||||
// the singular suffix too
|
||||
$firstUpper = ctype_upper($pluralRev[$j - 1]);
|
||||
|
||||
if (is_array($newSuffix)) {
|
||||
$singulars = array();
|
||||
|
||||
foreach ($newSuffix as $newSuffixEntry) {
|
||||
$singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
|
||||
}
|
||||
|
||||
return $singulars;
|
||||
}
|
||||
|
||||
return $newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix);
|
||||
}
|
||||
|
||||
// Suffix is longer than word
|
||||
if ($j === $pluralLength) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert teeth to tooth, feet to foot
|
||||
if (false !== ($pos = strpos($plural, 'ee')) && strlen($plural) > 3 && 'feedback' !== $plural) {
|
||||
return substr_replace($plural, 'oo', $pos, 2);
|
||||
}
|
||||
|
||||
// Assume that plural and singular is identical
|
||||
return $plural;
|
||||
}
|
||||
}
|
19
src/Symfony/Component/Inflector/LICENSE
Normal file
19
src/Symfony/Component/Inflector/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2012-2016 Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
19
src/Symfony/Component/Inflector/README.md
Normal file
19
src/Symfony/Component/Inflector/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
Inflector Component
|
||||
===================
|
||||
|
||||
Inflector converts words between their singular and plural forms (English only).
|
||||
|
||||
Disclaimer
|
||||
----------
|
||||
|
||||
This component is currently marked as internal. Do not use it in your own code.
|
||||
Breaking changes may be introduced in the next minor version of Symfony, or the
|
||||
component itself might even be removed completely.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
168
src/Symfony/Component/Inflector/Tests/InflectorTest.php
Normal file
168
src/Symfony/Component/Inflector/Tests/InflectorTest.php
Normal file
@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Inflector\Tests;
|
||||
|
||||
use Symfony\Component\Inflector\Inflector;
|
||||
|
||||
class InflectorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function singularizeProvider()
|
||||
{
|
||||
// see http://english-zone.com/spelling/plurals.html
|
||||
// see http://www.scribd.com/doc/3271143/List-of-100-Irregular-Plural-Nouns-in-English
|
||||
return array(
|
||||
array('accesses', 'access'),
|
||||
array('addresses', 'address'),
|
||||
array('agendas', 'agenda'),
|
||||
array('alumnae', 'alumna'),
|
||||
array('alumni', 'alumnus'),
|
||||
array('analyses', array('analys', 'analyse', 'analysis')),
|
||||
array('antennae', 'antenna'),
|
||||
array('antennas', 'antenna'),
|
||||
array('appendices', array('appendex', 'appendix', 'appendice')),
|
||||
array('arches', array('arch', 'arche')),
|
||||
array('atlases', array('atlas', 'atlase', 'atlasis')),
|
||||
array('axes', array('ax', 'axe', 'axis')),
|
||||
array('babies', 'baby'),
|
||||
array('bacteria', array('bacterion', 'bacterium')),
|
||||
array('bases', array('bas', 'base', 'basis')),
|
||||
array('batches', array('batch', 'batche')),
|
||||
array('beaux', 'beau'),
|
||||
array('bees', array('be', 'bee')),
|
||||
array('boxes', 'box'),
|
||||
array('boys', 'boy'),
|
||||
array('bureaus', 'bureau'),
|
||||
array('bureaux', 'bureau'),
|
||||
array('buses', array('bus', 'buse', 'busis')),
|
||||
array('bushes', array('bush', 'bushe')),
|
||||
array('calves', array('calf', 'calve', 'calff')),
|
||||
array('cars', 'car'),
|
||||
array('cassettes', array('cassett', 'cassette')),
|
||||
array('caves', array('caf', 'cave', 'caff')),
|
||||
array('chateaux', 'chateau'),
|
||||
array('cheeses', array('chees', 'cheese', 'cheesis')),
|
||||
array('children', 'child'),
|
||||
array('circuses', array('circus', 'circuse', 'circusis')),
|
||||
array('cliffs', 'cliff'),
|
||||
array('crises', array('cris', 'crise', 'crisis')),
|
||||
array('criteria', array('criterion', 'criterium')),
|
||||
array('cups', 'cup'),
|
||||
array('data', array('daton', 'datum')),
|
||||
array('days', 'day'),
|
||||
array('discos', 'disco'),
|
||||
array('devices', array('devex', 'devix', 'device')),
|
||||
array('drives', 'drive'),
|
||||
array('drivers', 'driver'),
|
||||
array('dwarves', array('dwarf', 'dwarve', 'dwarff')),
|
||||
array('echoes', array('echo', 'echoe')),
|
||||
array('elves', array('elf', 'elve', 'elff')),
|
||||
array('emphases', array('emphas', 'emphase', 'emphasis')),
|
||||
array('faxes', 'fax'),
|
||||
array('feet', 'foot'),
|
||||
array('feedback', 'feedback'),
|
||||
array('foci', 'focus'),
|
||||
array('focuses', array('focus', 'focuse', 'focusis')),
|
||||
array('formulae', 'formula'),
|
||||
array('formulas', 'formula'),
|
||||
array('fungi', 'fungus'),
|
||||
array('funguses', array('fungus', 'funguse', 'fungusis')),
|
||||
array('garages', array('garag', 'garage')),
|
||||
array('geese', 'goose'),
|
||||
array('halves', array('half', 'halve', 'halff')),
|
||||
array('hats', 'hat'),
|
||||
array('heroes', array('hero', 'heroe')),
|
||||
array('hippopotamuses', array('hippopotamus', 'hippopotamuse', 'hippopotamusis')), //hippopotami
|
||||
array('hoaxes', 'hoax'),
|
||||
array('hooves', array('hoof', 'hoove', 'hooff')),
|
||||
array('houses', array('hous', 'house', 'housis')),
|
||||
array('indexes', 'index'),
|
||||
array('indices', array('index', 'indix', 'indice')),
|
||||
array('ions', 'ion'),
|
||||
array('irises', array('iris', 'irise', 'irisis')),
|
||||
array('kisses', 'kiss'),
|
||||
array('knives', 'knife'),
|
||||
array('lamps', 'lamp'),
|
||||
array('leaves', array('leaf', 'leave', 'leaff')),
|
||||
array('lice', 'louse'),
|
||||
array('lives', 'life'),
|
||||
array('matrices', array('matrex', 'matrix', 'matrice')),
|
||||
array('matrixes', 'matrix'),
|
||||
array('men', 'man'),
|
||||
array('mice', 'mouse'),
|
||||
array('moves', 'move'),
|
||||
array('movies', 'movie'),
|
||||
array('nebulae', 'nebula'),
|
||||
array('neuroses', array('neuros', 'neurose', 'neurosis')),
|
||||
array('news', 'news'),
|
||||
array('oases', array('oas', 'oase', 'oasis')),
|
||||
array('objectives', 'objective'),
|
||||
array('oxen', 'ox'),
|
||||
array('parties', 'party'),
|
||||
array('phenomena', array('phenomenon', 'phenomenum')),
|
||||
array('photos', 'photo'),
|
||||
array('pianos', 'piano'),
|
||||
array('plateaux', 'plateau'),
|
||||
array('poppies', 'poppy'),
|
||||
array('prices', array('prex', 'prix', 'price')),
|
||||
array('quizzes', 'quiz'),
|
||||
array('radii', 'radius'),
|
||||
array('roofs', 'roof'),
|
||||
array('roses', array('ros', 'rose', 'rosis')),
|
||||
array('sandwiches', array('sandwich', 'sandwiche')),
|
||||
array('scarves', array('scarf', 'scarve', 'scarff')),
|
||||
array('schemas', 'schema'), //schemata
|
||||
array('selfies', 'selfie'),
|
||||
array('series', 'series'),
|
||||
array('services', 'service'),
|
||||
array('sheriffs', 'sheriff'),
|
||||
array('shoes', array('sho', 'shoe')),
|
||||
array('spies', 'spy'),
|
||||
array('staves', array('staf', 'stave', 'staff')),
|
||||
array('stories', 'story'),
|
||||
array('strata', array('straton', 'stratum')),
|
||||
array('suitcases', array('suitcas', 'suitcase', 'suitcasis')),
|
||||
array('syllabi', 'syllabus'),
|
||||
array('tags', 'tag'),
|
||||
array('teeth', 'tooth'),
|
||||
array('theses', array('thes', 'these', 'thesis')),
|
||||
array('thieves', array('thief', 'thieve', 'thieff')),
|
||||
array('trees', array('tre', 'tree')),
|
||||
array('waltzes', array('waltz', 'waltze')),
|
||||
array('wives', 'wife'),
|
||||
|
||||
// test casing: if the first letter was uppercase, it should remain so
|
||||
array('Men', 'Man'),
|
||||
array('GrandChildren', 'GrandChild'),
|
||||
array('SubTrees', array('SubTre', 'SubTree')),
|
||||
|
||||
// Known issues
|
||||
//array('insignia', 'insigne'),
|
||||
//array('insignias', 'insigne'),
|
||||
//array('rattles', 'rattle'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider singularizeProvider
|
||||
*/
|
||||
public function testSingularize($plural, $singular)
|
||||
{
|
||||
$single = Inflector::singularize($plural);
|
||||
if (is_string($singular) && is_array($single)) {
|
||||
$this->fail("--- Expected\n`string`: ".$singular."\n+++ Actual\n`array`: ".implode(', ', $single));
|
||||
} elseif (is_array($singular) && is_string($single)) {
|
||||
$this->fail("--- Expected\n`array`: ".implode(', ', $singular)."\n+++ Actual\n`string`: ".$single);
|
||||
}
|
||||
|
||||
$this->assertEquals($singular, $single);
|
||||
}
|
||||
}
|
40
src/Symfony/Component/Inflector/composer.json
Normal file
40
src/Symfony/Component/Inflector/composer.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "symfony/inflector",
|
||||
"type": "library",
|
||||
"description": "Symfony Inflector Component",
|
||||
"keywords": [
|
||||
"string",
|
||||
"inflection",
|
||||
"singularize",
|
||||
"pluralize",
|
||||
"words",
|
||||
"symfony"
|
||||
],
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bernhard Schussek",
|
||||
"email": "bschussek@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.5.9"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\Inflector\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.1-dev"
|
||||
}
|
||||
}
|
||||
}
|
29
src/Symfony/Component/Inflector/phpunit.xml.dist
Normal file
29
src/Symfony/Component/Inflector/phpunit.xml.dist
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1" />
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Symfony Inflector Component Test Suite">
|
||||
<directory>./Tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./</directory>
|
||||
<exclude>
|
||||
<directory>./Resources</directory>
|
||||
<directory>./Tests</directory>
|
||||
<directory>./vendor</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\PropertyAccess;
|
||||
|
||||
use Symfony\Component\Inflector\Inflector;
|
||||
use Symfony\Component\PropertyAccess\Exception\AccessException;
|
||||
use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
|
||||
@ -680,7 +681,7 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||
$reflClass = new \ReflectionClass($class);
|
||||
$access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
|
||||
$camelized = $this->camelize($property);
|
||||
$singulars = (array) StringUtil::singularify($camelized);
|
||||
$singulars = (array) Inflector::singularize($camelized);
|
||||
|
||||
if (is_array($value) || $value instanceof \Traversable) {
|
||||
$methods = $this->findAdderAndRemover($reflClass, $singulars);
|
||||
|
@ -11,121 +11,17 @@
|
||||
|
||||
namespace Symfony\Component\PropertyAccess;
|
||||
|
||||
use Symfony\Component\Inflector\Inflector;
|
||||
|
||||
/**
|
||||
* Creates singulars from plurals.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated Deprecated since version 3.1, to be removed in 4.0. Use {@see Symfony\Component\Inflector\Inflector} instead.
|
||||
*/
|
||||
class StringUtil
|
||||
{
|
||||
/**
|
||||
* Map english plural to singular suffixes.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @see http://english-zone.com/spelling/plurals.html
|
||||
* @see http://www.scribd.com/doc/3271143/List-of-100-Irregular-Plural-Nouns-in-English
|
||||
*/
|
||||
private static $pluralMap = array(
|
||||
// First entry: plural suffix, reversed
|
||||
// Second entry: length of plural suffix
|
||||
// Third entry: Whether the suffix may succeed a vocal
|
||||
// Fourth entry: Whether the suffix may succeed a consonant
|
||||
// Fifth entry: singular suffix, normal
|
||||
|
||||
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
|
||||
array('a', 1, true, true, array('on', 'um')),
|
||||
|
||||
// nebulae (nebula)
|
||||
array('ea', 2, true, true, 'a'),
|
||||
|
||||
// services (service)
|
||||
array('secivres', 8, true, true, 'service'),
|
||||
|
||||
// mice (mouse), lice (louse)
|
||||
array('eci', 3, false, true, 'ouse'),
|
||||
|
||||
// geese (goose)
|
||||
array('esee', 4, false, true, 'oose'),
|
||||
|
||||
// fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
|
||||
array('i', 1, true, true, 'us'),
|
||||
|
||||
// men (man), women (woman)
|
||||
array('nem', 3, true, true, 'man'),
|
||||
|
||||
// children (child)
|
||||
array('nerdlihc', 8, true, true, 'child'),
|
||||
|
||||
// oxen (ox)
|
||||
array('nexo', 4, false, false, 'ox'),
|
||||
|
||||
// indices (index), appendices (appendix), prices (price)
|
||||
array('seci', 4, false, true, array('ex', 'ix', 'ice')),
|
||||
|
||||
// selfies (selfie)
|
||||
array('seifles', 7, true, true, 'selfie'),
|
||||
|
||||
// movies (movie)
|
||||
array('seivom', 6, true, true, 'movie'),
|
||||
|
||||
// news (news)
|
||||
array('swen', 4, true, true, 'news'),
|
||||
|
||||
// series (series)
|
||||
array('seires', 6, true, true, 'series'),
|
||||
|
||||
// babies (baby)
|
||||
array('sei', 3, false, true, 'y'),
|
||||
|
||||
// accesses (access), addresses (address), kisses (kiss)
|
||||
array('sess', 4, true, false, 'ss'),
|
||||
|
||||
// analyses (analysis), ellipses (ellipsis), funguses (fungus),
|
||||
// neuroses (neurosis), theses (thesis), emphases (emphasis),
|
||||
// oases (oasis), crises (crisis), houses (house), bases (base),
|
||||
// atlases (atlas)
|
||||
array('ses', 3, true, true, array('s', 'se', 'sis')),
|
||||
|
||||
// objectives (objective), alternative (alternatives)
|
||||
array('sevit', 5, true, true, 'tive'),
|
||||
|
||||
// drives (drive)
|
||||
array('sevird', 6, false, true, 'drive'),
|
||||
|
||||
// lives (life), wives (wife)
|
||||
array('sevi', 4, false, true, 'ife'),
|
||||
|
||||
// moves (move)
|
||||
array('sevom', 5, true, true, 'move'),
|
||||
|
||||
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff)
|
||||
array('sev', 3, true, true, array('f', 've', 'ff')),
|
||||
|
||||
// axes (axis), axes (ax), axes (axe)
|
||||
array('sexa', 4, false, false, array('ax', 'axe', 'axis')),
|
||||
|
||||
// indexes (index), matrixes (matrix)
|
||||
array('sex', 3, true, false, 'x'),
|
||||
|
||||
// quizzes (quiz)
|
||||
array('sezz', 4, true, false, 'z'),
|
||||
|
||||
// bureaus (bureau)
|
||||
array('suae', 4, false, true, 'eau'),
|
||||
|
||||
// roses (rose), garages (garage), cassettes (cassette),
|
||||
// waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
|
||||
// shoes (shoe)
|
||||
array('se', 2, true, true, array('', 'e')),
|
||||
|
||||
// tags (tag)
|
||||
array('s', 1, true, true, ''),
|
||||
|
||||
// chateaux (chateau)
|
||||
array('xuae', 4, false, true, 'eau'),
|
||||
);
|
||||
|
||||
/**
|
||||
* This class should not be instantiated.
|
||||
*/
|
||||
@ -143,80 +39,13 @@ class StringUtil
|
||||
*
|
||||
* @return string|array The singular form or an array of possible singular
|
||||
* forms
|
||||
*
|
||||
* @deprecated Deprecated since version 3.1, to be removed in 4.0. Use {@see Symfony\Component\Inflector\Inflector::singularize} instead.
|
||||
*/
|
||||
public static function singularify($plural)
|
||||
{
|
||||
$pluralRev = strrev($plural);
|
||||
$lowerPluralRev = strtolower($pluralRev);
|
||||
$pluralLength = strlen($lowerPluralRev);
|
||||
@trigger_error('StringUtil::singularify() is deprecated since version 3.1 and will be removed in 4.0. Use Symfony\Component\Inflector\Inflector::singularize instead.', E_USER_DEPRECATED);
|
||||
|
||||
// The outer loop iterates over the entries of the plural table
|
||||
// The inner loop $j iterates over the characters of the plural suffix
|
||||
// in the plural table to compare them with the characters of the actual
|
||||
// given plural suffix
|
||||
foreach (self::$pluralMap as $map) {
|
||||
$suffix = $map[0];
|
||||
$suffixLength = $map[1];
|
||||
$j = 0;
|
||||
|
||||
// Compare characters in the plural table and of the suffix of the
|
||||
// given plural one by one
|
||||
while ($suffix[$j] === $lowerPluralRev[$j]) {
|
||||
// Let $j point to the next character
|
||||
++$j;
|
||||
|
||||
// Successfully compared the last character
|
||||
// Add an entry with the singular suffix to the singular array
|
||||
if ($j === $suffixLength) {
|
||||
// Is there any character preceding the suffix in the plural string?
|
||||
if ($j < $pluralLength) {
|
||||
$nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]);
|
||||
|
||||
if (!$map[2] && $nextIsVocal) {
|
||||
// suffix may not succeed a vocal but next char is one
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$map[3] && !$nextIsVocal) {
|
||||
// suffix may not succeed a consonant but next char is one
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$newBase = substr($plural, 0, $pluralLength - $suffixLength);
|
||||
$newSuffix = $map[4];
|
||||
|
||||
// Check whether the first character in the plural suffix
|
||||
// is uppercased. If yes, uppercase the first character in
|
||||
// the singular suffix too
|
||||
$firstUpper = ctype_upper($pluralRev[$j - 1]);
|
||||
|
||||
if (is_array($newSuffix)) {
|
||||
$singulars = array();
|
||||
|
||||
foreach ($newSuffix as $newSuffixEntry) {
|
||||
$singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
|
||||
}
|
||||
|
||||
return $singulars;
|
||||
}
|
||||
|
||||
return $newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix);
|
||||
}
|
||||
|
||||
// Suffix is longer than word
|
||||
if ($j === $pluralLength) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert teeth to tooth, feet to foot
|
||||
if (false !== ($pos = strpos($plural, 'ee')) && strlen($plural) > 3 && 'feedback' !== $plural) {
|
||||
return substr_replace($plural, 'oo', $pos, 2);
|
||||
}
|
||||
|
||||
// Assume that plural and singular is identical
|
||||
return $plural;
|
||||
return Inflector::singularize($plural);
|
||||
}
|
||||
}
|
||||
|
@ -13,141 +13,17 @@ namespace Symfony\Component\PropertyAccess\Tests;
|
||||
|
||||
use Symfony\Component\PropertyAccess\StringUtil;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
class StringUtilTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function singularifyProvider()
|
||||
{
|
||||
// see http://english-zone.com/spelling/plurals.html
|
||||
// see http://www.scribd.com/doc/3271143/List-of-100-Irregular-Plural-Nouns-in-English
|
||||
// This is only a stub to make sure the BC layer works
|
||||
// Actual tests are in the Symfony Inflector component
|
||||
return array(
|
||||
array('accesses', 'access'),
|
||||
array('addresses', 'address'),
|
||||
array('agendas', 'agenda'),
|
||||
array('alumnae', 'alumna'),
|
||||
array('alumni', 'alumnus'),
|
||||
array('analyses', array('analys', 'analyse', 'analysis')),
|
||||
array('antennae', 'antenna'),
|
||||
array('antennas', 'antenna'),
|
||||
array('appendices', array('appendex', 'appendix', 'appendice')),
|
||||
array('arches', array('arch', 'arche')),
|
||||
array('atlases', array('atlas', 'atlase', 'atlasis')),
|
||||
array('axes', array('ax', 'axe', 'axis')),
|
||||
array('babies', 'baby'),
|
||||
array('bacteria', array('bacterion', 'bacterium')),
|
||||
array('bases', array('bas', 'base', 'basis')),
|
||||
array('batches', array('batch', 'batche')),
|
||||
array('beaux', 'beau'),
|
||||
array('bees', array('be', 'bee')),
|
||||
array('boxes', 'box'),
|
||||
array('boys', 'boy'),
|
||||
array('bureaus', 'bureau'),
|
||||
array('bureaux', 'bureau'),
|
||||
array('buses', array('bus', 'buse', 'busis')),
|
||||
array('bushes', array('bush', 'bushe')),
|
||||
array('calves', array('calf', 'calve', 'calff')),
|
||||
array('cars', 'car'),
|
||||
array('cassettes', array('cassett', 'cassette')),
|
||||
array('caves', array('caf', 'cave', 'caff')),
|
||||
array('chateaux', 'chateau'),
|
||||
array('cheeses', array('chees', 'cheese', 'cheesis')),
|
||||
array('children', 'child'),
|
||||
array('circuses', array('circus', 'circuse', 'circusis')),
|
||||
array('cliffs', 'cliff'),
|
||||
array('crises', array('cris', 'crise', 'crisis')),
|
||||
array('criteria', array('criterion', 'criterium')),
|
||||
array('cups', 'cup'),
|
||||
array('data', array('daton', 'datum')),
|
||||
array('days', 'day'),
|
||||
array('discos', 'disco'),
|
||||
array('devices', array('devex', 'devix', 'device')),
|
||||
array('drives', 'drive'),
|
||||
array('drivers', 'driver'),
|
||||
array('dwarves', array('dwarf', 'dwarve', 'dwarff')),
|
||||
array('echoes', array('echo', 'echoe')),
|
||||
array('elves', array('elf', 'elve', 'elff')),
|
||||
array('emphases', array('emphas', 'emphase', 'emphasis')),
|
||||
array('faxes', 'fax'),
|
||||
array('feet', 'foot'),
|
||||
array('feedback', 'feedback'),
|
||||
array('foci', 'focus'),
|
||||
array('focuses', array('focus', 'focuse', 'focusis')),
|
||||
array('formulae', 'formula'),
|
||||
array('formulas', 'formula'),
|
||||
array('fungi', 'fungus'),
|
||||
array('funguses', array('fungus', 'funguse', 'fungusis')),
|
||||
array('garages', array('garag', 'garage')),
|
||||
array('geese', 'goose'),
|
||||
array('halves', array('half', 'halve', 'halff')),
|
||||
array('hats', 'hat'),
|
||||
array('heroes', array('hero', 'heroe')),
|
||||
array('hippopotamuses', array('hippopotamus', 'hippopotamuse', 'hippopotamusis')), //hippopotami
|
||||
array('hoaxes', 'hoax'),
|
||||
array('hooves', array('hoof', 'hoove', 'hooff')),
|
||||
array('houses', array('hous', 'house', 'housis')),
|
||||
array('indexes', 'index'),
|
||||
array('indices', array('index', 'indix', 'indice')),
|
||||
array('ions', 'ion'),
|
||||
array('irises', array('iris', 'irise', 'irisis')),
|
||||
array('kisses', 'kiss'),
|
||||
array('knives', 'knife'),
|
||||
array('lamps', 'lamp'),
|
||||
array('leaves', array('leaf', 'leave', 'leaff')),
|
||||
array('lice', 'louse'),
|
||||
array('lives', 'life'),
|
||||
array('matrices', array('matrex', 'matrix', 'matrice')),
|
||||
array('matrixes', 'matrix'),
|
||||
array('men', 'man'),
|
||||
array('mice', 'mouse'),
|
||||
array('moves', 'move'),
|
||||
array('movies', 'movie'),
|
||||
array('nebulae', 'nebula'),
|
||||
array('neuroses', array('neuros', 'neurose', 'neurosis')),
|
||||
array('news', 'news'),
|
||||
array('oases', array('oas', 'oase', 'oasis')),
|
||||
array('objectives', 'objective'),
|
||||
array('oxen', 'ox'),
|
||||
array('parties', 'party'),
|
||||
array('phenomena', array('phenomenon', 'phenomenum')),
|
||||
array('photos', 'photo'),
|
||||
array('pianos', 'piano'),
|
||||
array('plateaux', 'plateau'),
|
||||
array('poppies', 'poppy'),
|
||||
array('prices', array('prex', 'prix', 'price')),
|
||||
array('quizzes', 'quiz'),
|
||||
array('radii', 'radius'),
|
||||
array('roofs', 'roof'),
|
||||
array('roses', array('ros', 'rose', 'rosis')),
|
||||
array('sandwiches', array('sandwich', 'sandwiche')),
|
||||
array('scarves', array('scarf', 'scarve', 'scarff')),
|
||||
array('schemas', 'schema'), //schemata
|
||||
array('selfies', 'selfie'),
|
||||
array('series', 'series'),
|
||||
array('services', 'service'),
|
||||
array('sheriffs', 'sheriff'),
|
||||
array('shoes', array('sho', 'shoe')),
|
||||
array('spies', 'spy'),
|
||||
array('staves', array('staf', 'stave', 'staff')),
|
||||
array('stories', 'story'),
|
||||
array('strata', array('straton', 'stratum')),
|
||||
array('suitcases', array('suitcas', 'suitcase', 'suitcasis')),
|
||||
array('syllabi', 'syllabus'),
|
||||
array('tags', 'tag'),
|
||||
array('teeth', 'tooth'),
|
||||
array('theses', array('thes', 'these', 'thesis')),
|
||||
array('thieves', array('thief', 'thieve', 'thieff')),
|
||||
array('trees', array('tre', 'tree')),
|
||||
array('waltzes', array('waltz', 'waltze')),
|
||||
array('wives', 'wife'),
|
||||
|
||||
// test casing: if the first letter was uppercase, it should remain so
|
||||
array('Men', 'Man'),
|
||||
array('GrandChildren', 'GrandChild'),
|
||||
array('SubTrees', array('SubTre', 'SubTree')),
|
||||
|
||||
// Known issues
|
||||
//array('insignia', 'insigne'),
|
||||
//array('insignias', 'insigne'),
|
||||
//array('rattles', 'rattle'),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,8 @@
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.5.9",
|
||||
"symfony/polyfill-php70": "~1.0"
|
||||
"symfony/polyfill-php70": "~1.0",
|
||||
"symfony/inflector": "~3.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\PropertyAccess\\": "" },
|
||||
|
Reference in New Issue
Block a user