# et # ###################################################################### # # Correction de dimanche 11 novembre 2007 # Dans la version 5.2.2 de PHP, le comportement de la fonction # substr() a changé. Auparavant substr($nombre, -6) retournait # $nombre dans le cas où sa longueur était inférieure à 6, mais # maintenant cela retourne false. Merci à Benjamin (de la société # Dreamnex) et à David Duret pour m'avoir signalé le problème et # sa solution. # ###################################################################### define('NEL_SEPTANTE', 0x0001); define('NEL_HUITANTE', 0x0002); define('NEL_OCTANTE', 0x0004); define('NEL_NONANTE', 0x0008); define('NEL_BELGIQUE', NEL_SEPTANTE|NEL_NONANTE); define('NEL_VVF', NEL_SEPTANTE|NEL_HUITANTE|NEL_NONANTE); define('NEL_ARCHAIQUE', NEL_SEPTANTE|NEL_OCTANTE|NEL_NONANTE); define('NEL_SANS_MILLIARD', 0x0010); define('NEL_AVEC_ZILLIARD', 0x0020); define('NEL_TOUS_ZILLIONS', 0x0040); define('NEL_RECTIF_1990', 0x0100); define('NEL_ORDINAL', 0x0200); define('NEL_NIEME', 0x0400); # Le tableau associatif $NEL contient toutes les variables utilisées # de façon globale dans ce module. ATTENTION : ce nom est assez court, # et cela pourrait poser des problèmes de collision avec une autre # variable si plusieurs modules sont inclus dans le même programme. $NEL = array( '1-99' => array( # 0-19 '', 'un', 'deux', 'trois', 'quatre', 'cinq', 'six', 'sept', 'huit', 'neuf', 'dix', 'onze', 'douze', 'treize', 'quatorze', 'quinze', 'seize', 'dix-sept', 'dix-huit', 'dix-neuf', # 20-29 'vingt', 'vingt et un', 'vingt-deux', 'vingt-trois', 'vingt-quatre', 'vingt-cinq', 'vingt-six', 'vingt-sept', 'vingt-huit', 'vingt-neuf', # 30-39 'trente', 'trente et un', 'trente-deux', 'trente-trois', 'trente-quatre', 'trente-cinq', 'trente-six', 'trente-sept', 'trente-huit', 'trente-neuf', # 40-49 'quarante', 'quarante et un', 'quarante-deux', 'quarante-trois', 'quarante-quatre', 'quarante-cinq', 'quarante-six', 'quarante-sept', 'quarante-huit', 'quarante-neuf', # 50-59 'cinquante', 'cinquante et un', 'cinquante-deux', 'cinquante-trois', 'cinquante-quatre', 'cinquante-cinq', 'cinquante-six', 'cinquante-sept', 'cinquante-huit', 'cinquante-neuf', # 60-69 'soixante', 'soixante et un', 'soixante-deux', 'soixante-trois', 'soixante-quatre', 'soixante-cinq', 'soixante-six', 'soixante-sept', 'soixante-huit', 'soixante-neuf', # 70-79 'septante', 'septante et un', 'septante-deux', 'septante-trois', 'septante-quatre', 'septante-cinq', 'septante-six', 'septante-sept', 'septante-huit', 'septante-neuf', # 80-89 'huitante', 'huitante et un', 'huitante-deux', 'huitante-trois', 'huitante-quatre', 'huitante-cinq', 'huitante-six', 'huitante-sept', 'huitante-huit', 'huitante-neuf', # 90-99 'nonante', 'nonante et un', 'nonante-deux', 'nonante-trois', 'nonante-quatre', 'nonante-cinq', 'nonante-six', 'nonante-sept', 'nonante-huit', 'nonante-neuf' ), 'illi' => array('', 'm', 'b', 'tr', 'quatr', 'quint', 'sext'), 'maxilli' => 0, # voir plus loin 'de_maxillions' => '', # voir plus loin 'septante' => false, # valeurs possibles : (false|true) 'huitante' => false, # valeurs possibles : (false|true|'octante') 'nonante' => false, # valeurs possibles : (false|true) 'zillions' => false, # valeurs possibles : (false|true) 'zilliard' => 1, # valeurs possibles : (0|1|2) 'rectif' => false, # valeurs possibles : (false|true) 'ordinal' => false, # valeurs possibles : (false|true|'nieme') 'separateur' => ' ' ); # Si le tableau $NEL['illi'] s'arrête à 'sext', alors les deux valeurs # suivantes sont respectivement '6' et ' de sextillions'. $NEL['maxilli'] = count($NEL['illi']) - 1; $NEL['de_maxillions'] = " de {$NEL['illi'][$NEL['maxilli']]}illions"; function enlettres_options($options, $separateur=NULL) { global $NEL; if ($options !== NULL) { $NEL['septante'] = ($options & NEL_SEPTANTE) ? true : false; $NEL['huitante'] = ($options & NEL_OCTANTE) ? 'octante' : (($options & NEL_HUITANTE) ? true : false); $NEL['nonante'] = ($options & NEL_NONANTE) ? true : false; $NEL['zillions'] = ($options & NEL_TOUS_ZILLIONS) ? true : false; $NEL['zilliard'] = ($options & NEL_AVEC_ZILLIARD) ? 2 : (($options & NEL_SANS_MILLIARD) ? 0 : 1); $NEL['rectif'] = ($options & NEL_RECTIF_1990) ? true : false; $NEL['ordinal'] = ($options & NEL_NIEME) ? 'nieme' : (($options & NEL_ORDINAL) ? true : false); } if ($separateur !== NULL) { $NEL['separateur'] = $separateur; } } function enlettres_par3($par3) { global $NEL; if ($par3 == 0) return ''; $centaine = floor($par3 / 100); $par2 = $par3 % 100; $dizaine = floor($par2 / 10); # On traite à part les particularités du français de référence # 'soixante-dix', 'quatre-vingts' et 'quatre-vingt-dix'. $nom_par2 = NULL; switch ($dizaine) { case 7: if ($NEL['septante'] === false) { if ($par2 == 71) $nom_par2 = 'soixante et onze'; else $nom_par2 = 'soixante-' . $NEL['1-99'][$par2 - 60]; } break; case 8: if ($NEL['huitante'] === false) { if ($par2 == 80) $nom_par2 = 'quatre-vingts'; else $nom_par2 = 'quatre-vingt-' . $NEL['1-99'][$par2 - 80]; } break; case 9: if ($NEL['nonante'] === false) { $nom_par2 = 'quatre-vingt-' . $NEL['1-99'][$par2 - 80]; } break; } if ($nom_par2 === NULL) { $nom_par2 = $NEL['1-99'][$par2]; if (($dizaine == 8) and ($NEL['huitante'] === 'octante')) { $nom_par2 = str_replace('huitante', 'octante', $nom_par2); } } # Après les dizaines et les unités, il reste à voir les centaines switch ($centaine) { case 0: return $nom_par2; case 1: return rtrim("cent {$nom_par2}"); } # Assertion : $centaine = 2 .. 9 $nom_centaine = $NEL['1-99'][$centaine]; if ($par2 == 0) return "{$nom_centaine} cents"; return "{$nom_centaine} cent {$nom_par2}"; } function enlettres_zilli($idx) { # Noms des 0ème à 9ème zillions static $petit = array( 'n', 'm', 'b', 'tr', 'quatr', 'quint', 'sext', 'sept', 'oct', 'non' ); # Composantes des 10ème à 999ème zillions static $unite = array( '<', 'un<', 'duo<', 'tredéci<', 'ms>viginti<', 'ns>triginta<', 'ns>quadraginta<', 'ns>quinquaginta<', 'né>sexaginta<', 'né>septuaginta<', 'mxs>octoginta<', 'é>nonaginta<' ); static $centaine = array( '>', 'nxs>cent', 'né>ducent', 'ns>trécent', 'ns>quadringent', 'ns>quingent', 'né>sescent', 'né>septingent', 'mxs>octingent', 'é>nongent' ); # Règles d'assimilation aux préfixes latins, modifiées pour accentuer # un éventuel 'é' de fin de préfixe. # (1) Si on trouve une lettre deux fois entre < > on la garde. # S'il y a plusieurs lettres dans ce cas, on garde la première. # (2) Sinon on efface tout ce qui est entre < >. # (3) On remplace "treé" par "tré", "seé" par "sé", "septeé" par "septé" # et "noveé" par "nové". # (4) En cas de dizaine sans centaine, on supprime la voyelle en trop. # Par exemple "déciilli" devient "décilli" et "trigintailli" devient # "trigintilli". # # Il est à noter que ces règles PERL (en particulier la première qui # est la plus complexe) sont *très* fortement inspirées du programme # de Nicolas Graner. On pourrait même parler de plagiat s'il n'avait # pas été au courant que je reprenais son code. # Voir # et # static $recherche = array( '/<[a-zé]*?([a-zé])[a-zé]*\1[a-zé]*>/', # (1) '/<[a-zé]*>/', # (2) '/eé/', # (3) '/[ai]illi/' # (4) ); static $remplace = array( '\\1', # (1) '', # (2) 'é', # (3) 'illi' # (4) ); $nom = ''; while ($idx > 0) { $p = $idx % 1000; $idx = floor($idx/1000); if ($p < 10) { $nom = $petit[$p] . 'illi' . $nom; } else { $nom = $unite[$p % 10] . $dizaine[floor($p/10) % 10] . $centaine[floor($p/100)] . 'illi' . $nom; } } return preg_replace($recherche, $remplace, $nom); } function enlettres_illions($idx) { global $NEL; if ($idx == 0) { return ''; } if ($NEL['zillions']) { return enlettres_zilli($idx) . 'ons'; } $suffixe = ''; while ($idx > $NEL['maxilli']) { $idx -= $NEL['maxilli']; $suffixe .= $NEL['de_maxillions']; } return "{$NEL['illi'][$idx]}illions{$suffixe}"; } function enlettres_avec_illiards($idx) { global $NEL; if ($idx == 0) return false; switch ($NEL['zilliard']) { case 0: return false; case 2: return true; } return ($idx == 1); } function enlettres($nombre, $options=NULL, $separateur=NULL) { global $NEL; if ($options !== NULL or $separateur !== NULL) { $NELsave = $NEL; enlettres_options($options, $separateur); $nom = enlettres($nombre); $NEL = $NELsave; return $nom; } # On ne garde que les chiffres, puis on supprime les 0 du début $nombre = preg_replace('/[^0-9]/', '', $nombre); $nombre = ltrim($nombre, '0'); if ($nombre == '') { if ($NEL['ordinal'] === 'nieme') return 'zéroïème'; else return 'zéro'; } $table_noms = array(); for ($idx = 0; $nombre != ''; $idx++) { $par6 = (int)((strlen($nombre) < 6) ? $nombre : substr($nombre, -6)); $nombre = substr($nombre, 0, -6); if ($par6 == 0) continue; $nom_par3_sup = enlettres_par3(floor($par6 / 1000)); $nom_par3_inf = enlettres_par3($par6 % 1000); $illions = enlettres_illions($idx); if (enlettres_avec_illiards($idx)) { if ($nom_par3_inf != '') { $table_noms[$illions] = $nom_par3_inf; } if ($nom_par3_sup != '') { $illiards = preg_replace('/illion/', 'illiard', $illions, 1); $table_noms[$illiards] = $nom_par3_sup; } } else { switch($nom_par3_sup) { case '': $nom_par6 = $nom_par3_inf; break; case 'un': $nom_par6 = rtrim("mille {$nom_par3_inf}"); break; default: $nom_par3_sup = preg_replace('/(vingt|cent)s/', '\\1', $nom_par3_sup); $nom_par6 = rtrim("{$nom_par3_sup} mille {$nom_par3_inf}"); break; } $table_noms[$illions] = $nom_par6; } } $nom_enlettres = ''; foreach ($table_noms as $nom => $nombre) { ## # $nombre est compris entre 'un' et # 'neuf cent nonante-neuf mille neuf cent nonante-neuf' # (ou variante avec 'quatre-vingt-dix-neuf') ## # $nom peut valoir '', 'millions', 'milliards', 'billions', ... # 'sextillions', 'sextilliards', 'millions de sextillions', # 'millions de sextilliards', etc. ## # Rectifications orthographiques de 1990 if ($NEL['rectif']) { $nombre = str_replace(' ', '-', $nombre); } # Nom (éventuel) et accord (éventuel) des substantifs $nom = rtrim("{$nombre} {$nom}"); if ($nombre == 'un') { # Un seul million, milliard, etc., donc au singulier # noter la limite de 1 remplacement, pour ne supprimer que le premier 's' # dans 'billions de sextillions de sextillions' $nom = preg_replace('/(illion|illiard)s/', '\\1', $nom, 1); } # Ajout d'un séparateur entre chaque partie if ($nom_enlettres == '') { $nom_enlettres = $nom; } else { $nom_enlettres = $nom . $NEL['separateur'] . $nom_enlettres; } } if ($NEL['ordinal'] === false) { # Nombre cardinal : le traitement est fini return $nom_enlettres; } # Aucun pluriel dans les ordinaux $nom_enlettres = preg_replace('/(cent|vingt|illion|illiard)s/', '\\1', $nom_enlettres); if ($NEL['ordinal'] !== 'nieme') { # Nombre ordinal simple (sans '-ième') return $nom_enlettres; } if ($nom_enlettres === 'un') { # Le féminin n'est pas traité ici. On fait la supposition # qu'il est plus facile de traiter ce cas à part plutôt # que de rajouter une option rien que pour ça. return 'premier'; } switch (substr($nom_enlettres, -1)) { case 'e': # quatre, onze à seize, trente à nonante, mille # exemple : quatre -> quatrième return substr($nom_enlettres, 0, -1) . 'ième'; case 'f': # neuf -> neuvième return substr($nom_enlettres, 0, -1) . 'vième'; case 'q': # cinq -> cinquième return $nom_enlettres . 'uième'; } # Tous les autres cas. # Exemples: deuxième, troisième, vingtième, trente et unième, # neuf centième, un millionième, quatre-vingt milliardième. return $nom_enlettres . 'ième'; } ?>