Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
95.00% covered (success)
95.00%
19 / 20
CRAP
98.24% covered (success)
98.24%
167 / 170
iaPalabra
0.00% covered (danger)
0.00%
0 / 1
95.00% covered (success)
95.00%
19 / 20
113
98.24% covered (success)
98.24%
167 / 170
 ucwords
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 is_plural
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
 is_singular
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 pluraliza
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
 pluraliza1word
100.00% covered (success)
100.00%
1 / 1
12
100.00% covered (success)
100.00%
22 / 22
 addSuffix
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
4 / 4
 singulariza
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
 singulariza1word
100.00% covered (success)
100.00%
1 / 1
19
100.00% covered (success)
100.00%
28 / 28
 variants
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
9 / 9
 acentua
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
 acentua1word
100.00% covered (success)
100.00%
1 / 1
9
100.00% covered (success)
100.00%
17 / 17
 putAccent
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 swapAccent
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
4 / 4
 desAcentua
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 generoPalabra
100.00% covered (success)
100.00%
1 / 1
8
100.00% covered (success)
100.00%
15 / 15
 palabraGeneroPara
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
7 / 7
 elLa
100.00% covered (success)
100.00%
1 / 1
8
100.00% covered (success)
100.00%
8 / 8
 unUna
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
3 / 3
 comaAndOr
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
 comaY
0.00% covered (danger)
0.00%
0 / 1
20.42
84.21% covered (success)
84.21%
16 / 19
<?php
/**
 * @author Informática Asocaida SA de CV
 * @version 1.0.1
 * @copyright 2017
 */
namespace ia\Lib;
use ia\Util\Str;
//@TODO is_plura/singular cuando frase, quitar punctuation? match case poner isupper ojo con acentos!
/**
 * iaPalabra ayuda a ucwords, acentuar,  pluralizar, generos, desacentuar, listar, artículos y pro nombres en español
 *
 */
class iaPalabra {
    protected static $vocalesConAcento = ['á'=>'a','é'=>'e','í'=>'i','ó'=>'o','ú'=>'u','Á'=>'A','É'=>'E','Í'=>'I','Ó'=>'O','Ú'=>'U'];
    protected static $vocalesSinAcento = ['a'=>'á','e'=>'é','i'=>'í','o'=>'ó','u'=>'ú','A'=>'Á','E'=>'É','I'=>'Í','O'=>'Ó','U'=>'Ú'];
    protected static $sinPlural = [
        'este'=>1,
        'oeste'=>1,
        'norte'=>1,
        'sur'=>1,
        'sed'=>1,
        'hambre'=>1,
        'calor'=>1,
        'cariz'=>1,
        'tez'=>1,
        'caos'=>1,
        'salud'=>1,
        'grima'=>1,
        'café'=>1,
        'té'=>1,
        'nupcias'=>1,
        'tenazas'=>1,
        'víveres'=>1,
        'aledaños'=>1,
        'gárgaras'=>1,
        'modales'=>1,
        'enseres'=>1,
        'honorarios'=>1,
        'kg'=>1,
        'm'=>1,
        'km'=>1,
        'cm'=>1,
        'mts'=>1,
        'n/a'=>1,
        'n'=>1,
        'usd'=>1,
        'lb'=>1,
        'ft'=>1,
        'in'=>1,
        'ie'=>1,
        'al'=>1,
        'del' => 1,
    ];
    protected static $excepcionPlural = [
        'el' => 'los',
        'un' => 'unos',
        'ha' => 'han',
    ];
    protected static $excepcionSingular = [
        'los' => 'el',
        'unos' => 'un',
        'han' => 'ha',
    ];
    protected static $regExpTokenizePhrase ='/([\\p{P}\\p{S}!¡¿\\?\\,\\.;: \\s]+)/muS';
    /**
     * Title case o apitalize, pone en mayusuculas cada palabra, exceptuando articulos
     *
     * @param string $palabra titulo o frase a capitalizar
     * @return string $palabra en title case
     */
    public static function ucwords($palabra) {
        return str_replace(
            array(' Y ', ' E ', ' O ',' De ',' Del ',' La ', ' El ', ' Ella ', ' Los ', ' Las ', ' Un ', ' Una ', ' Unas ', ' Unos ',
                'Cdmx','Df','Cp','S/c','N/a',
                'Ii', 'Iii',  'IIi','Iv','Vii','iX','Xi','Xii','Xv','Xvi','Xvii','Xviii','Xx','Xxx'
            ),
            array(' y ', ' e ', ' o ',' de ',' del ',' la ', ' el ', ' ella ', ' los ', ' las ', ' un ', ' una ', ' unas ', ' unos ',
                'CDMX','DF','CP','S/C','N/A',
                'II', 'III', 'III','IV','VII','IX','XI','XII','XV','XVI','XVII','XVIII','XX','XXXX'
            ),
            mb_convert_case($palabra,  MB_CASE_TITLE, "UTF-8")
         );
    }
///////////////////
// Plural / singluar
//////////////////
    /**
     *
     *
     * @param string $palabra
     * @return boolean true es plural, false es singular
     */
    public static function is_plural($palabra) {
        $lowercase = mb_convert_case($palabra, MB_CASE_LOWER);
        if(!empty(self::$sinPlural[$lowercase])) {
            return false;
        }
        if(mb_substr($lowercase, -1) === 's') {
            return true;
        }
        return false;
    }
    public static function is_singular($palabra) {
        return !self::is_plural($palabra);
    }
    /**
     * Pasa a plural 1 o varias palabra
     *
     * @param string $palabra
     * @return string $palabra en plural
     */
    public static function pluraliza($palabra) {
        $ret = [];
        $split = preg_split(self::$regExpTokenizePhrase, $palabra, 0, PREG_SPLIT_DELIM_CAPTURE + PREG_SPLIT_NO_EMPTY);
        foreach($split as $w) {
            $ret[] = preg_match(self::$regExpTokenizePhrase, $w) ? $w : self::pluraliza1word($w);
        }
        return implode('', $ret);
    }
    /**
     * Pasa a plural 1 palabra
     *
     * @param string $palabra
     * @return string $palabra en plural
     */
    protected static function pluraliza1word($palabra) {
        $lowercase = mb_convert_case($palabra, MB_CASE_LOWER);
        if(isset(self::$sinPlural[$lowercase])) {
            return $palabra;
        }
        if(isset(self::$excepcionPlural[$lowercase])) {
            if($lowercase === $palabra) {
                return self::$excepcionPlural[$lowercase];
            }
            if( mb_convert_case($palabra, MB_CASE_TITLE) === $palabra) {
                return  mb_convert_case(self::$excepcionPlural[$lowercase], MB_CASE_TITLE);
            }
            return mb_convert_case(self::$excepcionPlural[$lowercase], MB_CASE_UPPER);
        }
        $c = mb_substr($lowercase, -1);
        if($c === 'x') {
            return $palabra;
        }
        $c2 = mb_substr($palabra, -2, 1);
        // aguda terminan en s o x pierden acento y terminan con es
        if(($c === 's' || $c === 'n') && array_key_exists($c2, self::$vocalesConAcento )) {
            return self::addSuffix(mb_substr($palabra,0,-2), self::$vocalesConAcento[$c2].$c.'es');
        }
        if(preg_match("/[aáeéiíoóuúü]/iuS", $c, $ingore)) {
            return self::addSuffix($palabra, 's');
        }
        if(preg_match("/[bcdfghjklmnpqrstvwxyz]/iS", $c2, $ignore)) {
            return self::addSuffix($palabra, 's');
        }
        if(preg_match("/[bcdfghjklmnpqrstvwxy]/iS", $c, $ignore)) {
            return self::addSuffix($palabra, 'es');
        }
        // if($c === 'z') {
            return self::addSuffix(mb_substr($palabra, 0, -1), 'ces');
        // }
        // return self::addSuffix($palabra, 'es');
    }
    /**
     * Append suffix to word preseving case of the word's last letter
     *
     * @param string $palabra
     * @param string $suffix
     * @return string $palabra.$suffix preseving case of the word's last letter
     */
    protected static function addSuffix($palabra, $suffix) {
        $c = mb_substr($palabra, -1);
        if(ctype_lower($c) || $c === mb_convert_case($c, MB_CASE_LOWER) ) {
            return $palabra.mb_convert_case($suffix, MB_CASE_LOWER);
        }
        return $palabra.mb_convert_case($suffix, MB_CASE_UPPER);
    }
    /**
     * Pasa a singular 1 o varias palabra
     *
     * @param string $palabra
     * @return string $palabra en plural
     */
    public static function singulariza($palabra) {
        $ret = [];
        $split = preg_split(self::$regExpTokenizePhrase, $palabra, 0, PREG_SPLIT_DELIM_CAPTURE + PREG_SPLIT_NO_EMPTY);
        foreach($split as $w) {
            $ret[] = preg_match(self::$regExpTokenizePhrase, $w) ? $w : self::singulariza1word($w);
        }
        return implode('', $ret);
    }
    /**
     * Pasa a singular 1 palabra
     *
     * @param string $palabra
     * @return string $palabra en singular
     */
    protected static function singulariza1word($palabra) {
        $lowercase =  mb_convert_case($palabra, MB_CASE_LOWER);
        if(!empty(self::$sinPlural[$lowercase])) {
            return $palabra;
        }
        if(isset(self::$excepcionSingular[$lowercase])) {
            if($lowercase === $palabra) {
                return self::$excepcionSingular[$lowercase];
            }
            if( mb_convert_case($palabra, MB_CASE_TITLE) === $palabra) {
               return  mb_convert_case(self::$excepcionSingular[$lowercase], MB_CASE_TITLE);
            }
            return mb_convert_case(self::$excepcionSingular[$lowercase], MB_CASE_UPPER);
        }
        $sub2 = mb_substr($lowercase,-2);
        if(strcmp('es', $sub2) === 0 || strcmp('as', $sub2) === 0 ) {
            $c3 = mb_substr($lowercase, -3, 1);
            if($c3 === 'h' || $c3 === 'l') {
                return substr($palabra, 0, -1);
            }
            if(($c3 === 'n' || $c3 === 's') && mb_strlen($lowercase) > 5) {
                return self::putAccent(mb_substr($palabra, 0, -2), -2);
            }
            //bla,ble,... bra,bre,...
            if($c3 === 'r' || $c3 === 'l') {
                //$c4 = mb_substr($lowercase, -4, 1);
                if(mb_substr($lowercase, -4, 1) === 'b') {
                    return mb_substr($palabra, 0, -1);
                }
            }
            if($c3 === 'c') {
                if(mb_substr($palabra, -3, 1) === 'C') {
                    return mb_substr($palabra, 0, -3).'Z';
                }
                return mb_substr($palabra, 0, -3).'z';
            }
            if(preg_match("/[bcdfghjklmnqrsvwxy]/S", $c3, $ignore)) {
                return mb_substr($palabra, 0, -2);
            }
        }
        //$c = mb_substr($lowercase, -1);
        if( mb_substr($lowercase, -1) === 's') {
            return mb_substr($palabra, 0, -1);
        }
        return $palabra;
    }
    /**
     * Regresa la palabra y su singluar o plural
     *
     * @param string $palabra
     * @return array [$palabra, $plabra_en_singluar_o_plural]
     */
    public static function variants($palabra) {
        $ret = [$palabra];
        if(self::is_plural($palabra)) {
            $singular = self::singulariza($palabra);
            if($singular !== $palabra) {
                $ret[] = $singular;
            }
        } else {
            $plural = self::pluraliza($palabra);
            if($plural !== $palabra) {
                $ret[] = $plural;
            }
        }
        return $ret;
    }
/* Accents / diacritics */
    // Las palabras agudas se escriben con tilde cuando terminan en vocal, en n o en s
    // un hiato, es decir una vocal cerrada (i u) tónica junto a una vocal abierta (a e o). Los hiatos siempre se escriben con tilde. Por ejemplo: raíz.
    // terminan en mente si el adjetivo lleva tilde, el adverbio conserva la tilde. Pero en caso contrario, si el adjetivo no lleva tilde el adverbio tampoco
    /**
     * Pone acentos la plabra/frase recibida
     *
     * @param string $palabra
     * @return  string $palabra con acentos
     */
    public static function acentua($palabra) {
        $ret = [];
        $split = preg_split(self::$regExpTokenizePhrase, $palabra, 0, PREG_SPLIT_DELIM_CAPTURE + PREG_SPLIT_NO_EMPTY);
        foreach($split as $w) {
            $ret[] = preg_match(self::$regExpTokenizePhrase, $w) ? $w : self::acentua1word($w);
        }
        return implode('', $ret);
    }
    /**
     * Pone acentos la plabra recibida
     *
     * @param string $palabra
     * @return  string $palabra con acentos
     */
    protected static function acentua1word($palabra) {
        if(preg_match("/[áéíóú]/iuS", $palabra, $ingore)) {
            return $palabra; // already accented, only 1 accent per word
        }
        static $comunes = [
            'maximo' => 2,
            'maximos' => 2,
            'minimo' => 2,
            'minimos' => 2,
            'mexico' => 2,
            'numero' => 2,
            'numeros' => 2,
            'estandar' => 4,
            'estandares' => 4,
            'ultimo' => 1,
            'ultima' => 1,
            'ultimos' => 1,
            'ultimas' => 1,
            'cafe' => -1,
            'poliza' => 2,
            'deposito' => 4,
            'almacen' => -2,
            'telefono' => 4,
            'imagenes' => 3,
            'dificil' => 4,
            'facil' => 2,
            'indice' => 1,
            'icono' => 1,
            'grafica' => 2,
            'grafico' => 2,
            'mercancia' => -3,
            'fisico' => 2,
            'angulo' => 1,
            'ingles' => 5,
            'frances' => 6,
            'aleman' => 5,
            'jose' => 4,
            'raul' => 3,
            'maria' => 4,
            'articulo' =>4,
        ];
        $lowercase = mb_convert_case($palabra, MB_CASE_LOWER);
        if(array_key_exists($lowercase, $comunes)) {
            return self::putAccent($palabra, $comunes[$lowercase]);
        }
        $strlen = strlen($lowercase);
        $sub3 = mb_substr($lowercase, -3);
        if( $strlen >5  &&  strcmp("ion", $sub3) === 0 ) {
            return self::putAccent($palabra, -2);
        }
        if( $strlen > 3 &&  strcmp("zon", $sub3) === 0 ) {
            return self::putAccent($palabra, -2);
        }
        if(strcmp("aiz", $sub3) === 0 ) {
            return self::putAccent($palabra, -2);
        }
        if(strcmp("eria", mb_substr($lowercase, -4)) === 0 ) {
            return self::putAccent($palabra, -2);
        }
        return $palabra;
    }
    /**
     * Quita el acento en posicion $n
     *
     * @param string $palabra
     * @param int $n
     * @return string con el acento en posicion $n eliminado
     */
    /*
    protected static function removeAccent($palabra, $n) {
        return self::swapAccent($palabra, $n, self::$vocalesConAcento);
    }
    */
    /**
     *  Pone el acento en posicion $n
     *
     * @param string $palabra
     * @param int $n
     * @return string $palabra con el acento en la posicion $n
     */
    protected static function putAccent($palabra, $n) {
        return self::swapAccent($palabra, $n, self::$vocalesSinAcento);
    }
    /**
     *
     *
     * @param string $palabra
     * @param int $n
     * @param array $vowels
     * @return string con la vocal en posicion $n cambiada por el valor de $vowels con indice vocal en $n
     */
    protected static function swapAccent($palabra, $n, $vowels) {
        $c = mb_substr($palabra, $n>0 ? $n-1 : $n, 1);
        if(array_key_exists($c, $vowels)) {
            return Str::replacePos($palabra, $vowels[$c], $n);
        }
        return $palabra;
    }
    /**
     * Quita acentos y caracteres especiales
     *
     * @param string $palabra
     * @return string sin acentos ni caracteres especiales
     */
    public static function desAcentua($palabra) {
        return \URLify::downcode($palabra);
    }
    /* Gender */
    protected static $generoVocal = [
            'a' => 'f',
            'A' => 'f',
            'á' => 'f',
            'Á' => 'f',
            'e' => 'm',
            'E' => 'm',
            'é' => 'm',
            'É' => 'm',
            'i' => 'm',
            'I' => 'm',
            'í' => 'm',
            'Í' => 'm',
            'o' => 'm',
            'O' => 'm',
            'ó' => 'm',
            'Ó' => 'm',
            'u' => 'm',
            'U' => 'm',
            'ú' => 'm',
            'Ú' => 'm',
        ];
    protected static $excepcioneGenero = array(
            'clave' => 'f',
            'calle' => 'f',
            'azucar' => 'm',
            'calle' => 'f',
            'coliflor' => 'f',
            'clima' => 'm',
            'decision'=>'f',
            'dia' => 'm',
            'diagrama' => 'm',
            'esquema' => 'm',
            'estandar' => 'm',
            'foto' => 'f',
            'flor' => 'f',
            'fuente' => 'f',
            'fuentes' => 'f',
            'idioma' => 'm',
            'imagen' => 'f',
            'imagenes' => 'f',
            'imágenes' => 'f',
            'labor' => 'f',
            'mano' => 'f',
            'radio' => 'f',
            'mapa' => 'm',
            'planeta' => 'm',
            'poema' => 'm',
            'programa' => 'm',
            'telegrama' => 'm',
            'tema' => 'm',
            'tranvia' => 'm',
            'vacaciones'=>'f',
            'vacacion'=>'f',
        );
    /**
     * Regresa f para femnino, m masculino
     *
     * @param string $palabra
     * @return string f: feminno, m: masculino
     */
    public static function generoPalabra($palabra) {
        $lowercase = self::desAcentua( mb_convert_case(self::is_plural($palabra) ? self::singulariza($palabra) : $palabra, MB_CASE_LOWER) );
        if(array_key_exists($lowercase, self::$excepcioneGenero)) {
            return self::$excepcioneGenero[$lowercase];
        }
        // terminan en ema, ama, tud, cion, sion => f. ref  http://profelacera.weebly.com/uploads/3/8/1/5/3815235/genero_de_las_palabras.pdf
        if(mb_substr($lowercase,-3) === 'tud') {
            return 'f';
        }
        $sub4 = mb_substr($lowercase,-4);
        if($sub4 === 'cion' || $sub4 === 'sion') {
            return 'f';
        }
        $c = mb_substr($lowercase, -1);
        if(!empty(self::$generoVocal[$c])) {
            return self::$generoVocal[$c];
        }
        $c2 = mb_substr($lowercase, -2, 1);
        if(!empty(self::$generoVocal[$c2])) {
            return self::$generoVocal[$c2];
        }
        return 'm';
    }
    // palabraGeneroPara('requerida', 'numero') // requerido, invalido, erroneo
    /**
     * Ajusta el genero de $palabra para usarse con $para
     *
     * @param string $palabra
     * @param string $para
     * @return string $palabra con el genero correcto para usarse con $para
     */
    public static function palabraGeneroPara($palabra, $para) {
        $c = mb_substr($palabra, -1);
        if(preg_match("/[bcdfghjklmnpqrstvwxyz]/iS", $c, $ignore)) {
            return $palabra;
        }
        static $vocalLowercase = ['a'=>1, 'á'=>1, 'e'=>1, 'é'=>1, 'i'=>1, 'í'=>1, 'o'=>1, 'ó'=>1, 'u'=>1, 'ú'=>1];
        if( self::generoPalabra($para) === 'f') {
            return mb_substr($palabra, 0, -1).(empty($vocalLowercase[$c]) ? 'A' : 'a');
        }
        return mb_substr($palabra, 0, -1).(empty($vocalLowercase[$c]) ? 'O' : 'o');
    }
    /* Articles */
    /**
     * Regresa el articulo (el, los, la las) adecuada para $palabra
     *
     * @param string $palabra
     * @return string el articulo (el, los, la las) adecuada para $palabra
     */
    public static function elLa($palabra) {
        $c1 = mb_convert_case(mb_substr($palabra, 0 ,1), MB_CASE_LOWER);
        $sub2 = mb_convert_case(mb_substr($palabra, 0 ,2), MB_CASE_LOWER);
        if($c1 === 'a' || $c1 === 'á' || $sub2 == 'ha'|| $sub2 == 'há') {
            // si inicia con a o ha, es el a..., excepto hache, nombre propios La Haya
            $genero = 'm';
        } else {
            $genero = self::generoPalabra($palabra);
        }
        if($genero === 'f') {
            return self::is_plural($palabra) ? 'las' : 'la';
        }
        return self::is_plural($palabra) ? 'los' : 'el';
    }
    /**
     * Regresa el articulo (un, unos, una unas) adecuada para $palabra
     *
     * @param string $palabra
     * @return string el articulo (un, unos, una unas) adecuada para $palabra
     */
    public static function unUna($palabra) {
        if(self::generoPalabra($palabra) === 'f') {
            return self::is_plural($palabra) ? 'unas' : 'una';
        }
        return self::is_plural($palabra) ? 'unos' : 'un';
    }
    /* Or/And phrase list */
    /**
     *
     *
     * @param array $array
     * @param string $conjunction
     * @return string
     */
    public static function comaAndOr($array, $conjunction = 'and') {
        if(empty($array)) {
            return '';
        }
        if(count($array) === 1) {
            return reset($array);
        }
        // asort($array, SORT_NATURAL); // duda ordenar o como viene?
        $last = array_pop($array);
        return implode(', ', $array)." $conjunction $last";
    }
    /**
     *
     *
     * @param array $array
     * @param string $conjunction
     * @return string
     */
    public static function comaY($array, $conjunction = 'y') {
        if(empty($array)) {
            return '';
        }
        if(count($array) === 1) {
            return reset($array);
        }
        //asort($array, SORT_NATURAL); // duda ordenar o como viene?
        $last = trim(array_pop($array));
        $primerChar = mb_substr($last, 0, 1);
        if($conjunction === 'y' || $conjunction === 'Y') {
            $secondChar = mb_substr($last, 1, 1);
            // no cambia si “ia” o “hia”, “ie” o “hie” y “io” o “hio”, eg hummus y hierbabuena
            if($primerChar === 'I' || $primerChar === 'i' && !in_array($secondChar, ['a', 'A', 'o', 'O'], false) ) {
                $conjunction = 'e';
            } elseif( ($primerChar === 'H' || $primerChar === 'h') && ($secondChar === 'i' || $secondChar === 'I') && !in_array($last[2], ['a', 'A', 'e', 'E', 'o', 'O'], false)) {
                $conjunction = 'e';
            } elseif(in_array($primerChar, ['I','i','Í','í'], true)) {
                $conjunction = 'e';
            }
        } else {
            $secondChar = mb_substr($last, 1, 1);
            if(in_array($conjunction, ['o', 'O', 'Ó', 'ó'], false) &&
                ( in_array($primerChar, ['O','Ó','o','ó','8'], true) || ( ($primerChar === 'H' || $primerChar === 'h') && in_array($secondChar, ['O','Ó','o','ó','8'], true)) )
            ) {
                $conjunction = 'u';
            }
        }
        return implode(', ', $array) . ' ' . $conjunction . ' ' . $last;
    }
}