Question

Je n'ai actuellement aucune idée de la manière de trier un tableau contenant des chaînes encodées en UTF-8 en PHP. Le tableau provient d'un serveur LDAP, donc le tri via une base de données (ce ne serait pas un problème) n'est pas une solution. Ce qui suit ne fonctionne pas sur ma machine de développement Windows (bien que je pense que cela devrait au moins être une solution possible):

$array=array('Birnen', 'Äpfel', 'Ungetüme', 'Apfel', 'Ungetiere', 'Österreich');
$oldLocal=setlocale(LC_COLLATE, "0");
var_dump(setlocale(LC_COLLATE, 'German_Germany.65001'));
usort($array, 'strcoll');
var_dump(setlocale(LC_COLLATE, $oldLocal));
var_dump($array);

Le résultat est:

string(20) "German_Germany.65001"
string(1) "C"
array(6) {
  [0]=>
  string(6) "Birnen"
  [1]=>
  string(9) "Ungetiere"
  [2]=>
  string(6) "Äpfel"
  [3]=>
  string(5) "Apfel"
  [4]=>
  string(9) "Ungetüme"
  [5]=>
  string(11) "Österreich"
}

Ceci est un non-sens complet. L'utilisation de 1252 comme page de code pour setlocale () donne une autre sortie, qui reste tout à fait fausse:

string(19) "German_Germany.1252"
string(1) "C"
array(6) {
  [0]=>
  string(11) "Österreich"
  [1]=>
  string(6) "Äpfel"
  [2]=>
  string(5) "Apfel"
  [3]=>
  string(6) "Birnen"
  [4]=>
  string(9) "Ungetüme"
  [5]=>
  string(9) "Ungetiere"
}

Existe-t-il un moyen de trier un tableau contenant des paramètres régionaux pour les chaînes UTF-8?

Je viens de noter que cela semble être un problème de PHP sous Windows, car le même extrait de code avec de_DE.utf8 utilisé comme environnement local fonctionne sur une machine Linux. Néanmoins, une solution à ce problème spécifique à Windows serait bien ...

Était-ce utile?

La solution 3

Finalement, ce problème ne peut pas être résolu de manière simple sans utiliser des chaînes recodées (UTF-8 & # 8594; Windows-1252 ou ISO-8859-1) comme suggéré par & # 932; & # 918; & # 937; & # 932; & # 918; & # 921; & # 927; & # 933; en raison d'un bogue PHP évident tel que découvert par Huppie. Pour résumer le problème, j’ai créé le fragment de code suivant qui montre clairement que le problème est lié à la fonction strcoll () lors de l’utilisation de la page de codes Windows-UTF-8 65001.

function traceStrColl($a, $b) {
    $outValue=strcoll($a, $b);
    echo "$a $b $outValue\r\n";
    return $outValue;
}

$locale=(defined('PHP_OS') && stristr(PHP_OS, 'win')) ? 'German_Germany.65001' : 'de_DE.utf8';

$string="ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜabcdefghijklmnopqrstuvwxyzäöüß";
$array=array();
for ($i=0; $i<mb_strlen($string, 'UTF-8'); $i++) {
    $array[]=mb_substr($string, $i, 1, 'UTF-8');
}
$oldLocale=setlocale(LC_COLLATE, "0");
var_dump(setlocale(LC_COLLATE, $locale));
usort($array, 'traceStrColl');
setlocale(LC_COLLATE, $oldLocale);
var_dump($array);

Le résultat est:

string(20) "German_Germany.65001"
a B 2147483647
[...]
array(59) {
  [0]=>
  string(1) "c"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "s"
  [3]=>
  string(1) "C"
  [4]=>
  string(1) "k"
  [5]=>
  string(1) "D"
  [6]=>
  string(2) "ä"
  [7]=>
  string(1) "E"
  [8]=>
  string(1) "g"
  [...]

Le même fragment de code fonctionne sur une machine Linux sans aucun problème en produisant la sortie suivante:

string(10) "de_DE.utf8"
a B -1
[...]
array(59) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "A"
  [2]=>
  string(2) "ä"
  [3]=>
  string(2) "Ä"
  [4]=>
  string(1) "b"
  [5]=>
  string(1) "B"
  [6]=>
  string(1) "c"
  [7]=>
  string(1) "C"
  [...]

L'extrait de code fonctionne également lors de l'utilisation de chaînes codées Windows-1252 (ISO-8859-1) (bien sûr, les codages mb_ * et les paramètres régionaux doivent être modifiés à ce moment-là).

J'ai rédigé un rapport de bogue sur bugs.php.net : Bug n ° 46165 strcoll () ne fonctionne pas avec les chaînes UTF-8 sous Windows . Si vous rencontrez le même problème, vous pouvez faire part de vos commentaires à l'équipe PHP sur la page de rapport de bogue (deux autres bogues probablement liés, qui ont été classés comme Faux - je ne pense pas que cela le bogue est faux ; -).

Merci à vous tous.

Autres conseils

$a = array( 'Кръстев', 'Делян1', 'делян1', 'Делян2', 'делян3', 'кръстев' );
$col = new \Collator('bg_BG');
$col->asort( $a );
var_dump( $a );

Impressions:

array
  2 => string 'делян1' (length=11)
  1 => string 'Делян1' (length=11)
  3 => string 'Делян2' (length=11)
  4 => string 'делян3' (length=11)
  5 => string 'кръстев' (length=14)
  0 => string 'Кръстев' (length=14)

La classe Collator est définie dans extension int PECL. . Il est distribué avec les sources PHP 5.3 mais peut être désactivé pour certaines versions. Par exemple. dans Debian, il se trouve dans le paquet php5-intl.

Collator :: compare est utile pour usort .

Mise à jour sur ce problème:

Même si la discussion autour de ce problème a révélé que nous aurions pu découvrir un bogue PHP avec strcoll () et / ou < code> setlocale () , ce n'est clairement pas le cas. Le problème est plutôt une limitation de l’implémentation Windows CRT de setlocale () (PHP setlocale () n’est qu’une mince couche autour de l’appel CRT). Ce qui suit est une citation de la page MSDN & set; setlocale, _wsetlocale " . :

  

L'ensemble des langues disponibles,   codes de pays / région et pages de code   comprend tous ceux pris en charge par le   Win32 NLS API sauf les pages de code qui   nécessite plus de deux octets par   caractère, tels que UTF-7 et UTF-8. Si   vous fournissez une page de code comme UTF-7 ou   UTF-8, setlocale échouera en retournant   NULL. : ensemble de langues et   codes de pays / région pris en charge par   setlocale est listé dans Langues et   Pays / Région Chaînes.

Il est donc impossible d'utiliser des opérations de chaîne tenant compte des paramètres régionaux dans PHP sous Windows lorsque les chaînes sont codées sur plusieurs octets.

Il s'agit d'un problème très complexe, car les données codées en UTF-8 peuvent contenir n'importe quel caractère Unicode. (c’est-à-dire des caractères provenant de nombreux codages 8 bits qui s’assemblent différemment selon les paramètres régionaux).

Peut-être que si vous convertissiez vos données UTF-8 en Unicode (désolé, non familier avec les fonctions PHP Unicode), puis que vous les normalisiez en NFD ou NFKD , puis le tri sur les points de code peut donner un classement qui aurait du sens pour vous (c'est-à-dire "A" avant "Ä").

Vérifiez les liens que j'ai fournis.

EDIT: puisque vous indiquez que vos données d'entrée sont claires (je suppose qu'elles appartiennent toutes à la page de codes "windows-1252"), vous devez alors effectuer la conversion suivante: UTF-8 ? Unicode ? Windows-1252, sous triez les données codées par Windows-1252 en sélectionnant la touche "CP1252". locale.

L’utilisation de votre exemple avec la page de code 1252 a parfaitement fonctionné ici sur ma machine de développement Windows.

$array=array('Birnen', 'Äpfel', 'Ungetüme', 'Apfel', 'Ungetiere', 'Österreich');
$oldLocal=setlocale(LC_COLLATE, "0");
var_dump(setlocale(LC_COLLATE, 'German_Germany.1252'));
usort($array, 'strcoll');
var_dump(setlocale(LC_COLLATE, $oldLocal));
var_dump($array);

... snip ...

C'était avec PHP 5.2.6. btw.


L'exemple ci-dessus est faux . Il utilise le codage ASCII au lieu de UTF-8. J'ai tracé les appels strcoll () et regardé ce que j'ai trouvé:

function traceStrColl($a, $b) {
    $outValue = strcoll($a, $b);
    echo "$a $b $outValue\r\n";
    return $outValue;
}

$array=array('Birnen', 'Äpfel', 'Ungetüme', 'Apfel', 'Ungetiere', 'Österreich');
setlocale(LC_COLLATE, 'German_Germany.65001');
usort($array, 'traceStrColl');
print_r($array);

donne:

Ungetüme Äpfel 2147483647
Ungetüme Birnen 2147483647
Ungetüme Apfel 2147483647
Ungetüme Ungetiere 2147483647
Österreich Ungetüme 2147483647
Äpfel Ungetiere 2147483647
Äpfel Birnen 2147483647
Apfel Äpfel 2147483647
Ungetiere Birnen 2147483647

J'ai trouvé des rapports de bogue marqués comme étant fictif ... Le meilleur pari que vous ayez est de déposer un rapport de bogue, je suppose cependant ...

J'ai trouvé cette fonction d'assistance suivante pour convertir toutes les lettres d'une chaîne en lettres ASCII très utiles.

function _all_letters_to_ASCII($string) {
  return strtr(utf8_decode($string), 
    utf8_decode('ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ'),
    'SOZsozYYuAAAAAAACEEEEIIIIDNOOOOOOUUUUYsaaaaaaaceeeeiiiionoooooouuuuyy');
}

Après cela, un simple array_multisort () vous donne ce que vous voulez.

$array = array('Birnen', 'Äpfel', 'Ungetüme', 'Apfel', 'Ungetiere', 'Österreich');
$reference_array = $array;

foreach ($reference_array as $key => &$value) {
  $value = _all_letters_to_ASCII($value);
}
var_dump($reference_array);

array_multisort($reference_array, $array);
var_dump($array);

Bien sûr, vous pouvez adapter la fonction d’aide aux besoins les plus avancés. Mais pour le moment, ça a l'air plutôt bien.

array(6) {
  [0]=> string(6) "Birnen"
  [1]=> string(5) "Apfel"
  [2]=> string(8) "Ungetume"
  [3]=> string(5) "Apfel"
  [4]=> string(9) "Ungetiere"
  [5]=> string(10) "Osterreich"
}

array(6) {
  [0]=> string(5) "Apfel"
  [1]=> string(6) "Äpfel"
  [2]=> string(6) "Birnen"
  [3]=> string(11) "Österreich"
  [4]=> string(9) "Ungetiere"
  [5]=> string(9) "Ungetüme"
}

Je suis confronté au même problème avec l'allemand "Umlaute". Après quelques recherches, cela a fonctionné pour moi:

$laender =array("Österreich", "Schweiz", "England", "France", "Ägypten");  
$laender = array_map("utf8_decode", $laender);  
setlocale(LC_ALL,"de_DE@euro", "de_DE", "deu_deu");  
sort($laender, SORT_LOCALE_STRING);  
$laender = array_map("utf8_encode", $laender);  
print_r($laender);

Le résultat:

  

Tableau
      (
      [0] = > Egypte
      [1] = > Angleterre
      [2] = > France
      [3] = > Österreich
      [4] = > Schweiz
      )

Votre classement doit correspondre au jeu de caractères. Étant donné que vos données sont encodées en UTF-8, vous devez utiliser un classement UTF-8. Il pourrait être nommé différemment sur différentes plates-formes, mais une bonne hypothèse serait de_DE.utf8 .

Sur les systèmes UNIX, vous pouvez obtenir la liste des locales actuellement installées à l'aide de la commande

.
locale -a
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top