Pergunta

Eu currentyl não tem nenhum indício sobre como classificar um array que contém UTF-8 cordas codificados em PHP. A matriz vem de um servidor LDAP para que classificar através de um banco de dados (não seria um problema) não é solução. O seguinte não funciona na minha máquina de desenvolvimento do Windows (embora eu acho que isso deve ser pelo menos uma solução possível):

$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);

A saída é:

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"
}

Este é um disparate completo. Usando 1252 como a página de código para setlocale() dá outra saída, mas ainda a um claramente errado:

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 uma maneira de classificar uma matriz com UTF-8 cordas locale conscientes?

Apenas observou que este parece ser o PHP no problema do Windows, como o mesmo trecho com de_DE.utf8 usado como obras de local em uma máquina Linux. No entanto, uma solução para este problema específico em Windows seria bom ...

Foi útil?

Solução 3

Finalmente, este problema não pode ser resolvido de forma simples, sem usar cordas recodificados (UTF-8 ? Windows 1252 ou ISO-8859-1) como sugerido por ??O????? devido a um bug PHP óbvia como descoberto por Huppie. Para resumir o problema, eu criei o seguinte trecho de código que demonstra claramente que o problema é a função strcoll () quando usando o Windows UTF-8-página de código 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);

O resultado é:

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"
  [...]

As mesmas obras trecho em uma máquina Linux, sem quaisquer problemas produzem o seguinte resultado:

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"
  [...]

O trecho também funciona quando usando cordas do Windows-1252 (ISO-8859-1) codificado (claro que o mb_ * codificações e o local deve ser alterado depois).

Eu arquivei um relatório de bug no bugs.php.net : Bug # 46165 strcoll () não funciona com UTF-8 cordas no Windows . Se você tiver o mesmo problema, você pode dar o seu feedback para a equipe PHP na página de relatório de bugs (outros dois, provavelmente relacionado, erros foram classificados como falso - Eu não acho que este bug é falso ; -).

Graças a todos vocês.

Outras dicas

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

Prints:

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)

A classe Collator é definido na PECL extensão intl . Ele é distribuído com o PHP 5.3 fontes, mas pode ser desativado por algum constrói. Por exemplo. no Debian que está no pacote php5-Intl.

Collator::compare é útil para usort.

Atualização sobre esse problema:

Mesmo que a discussão em torno este problema revelou que poderíamos ter descoberto um bug PHP com strcoll() e / ou setlocale() , este não é claramente O caso. O problema é sim uma limitação da implementação do Windows CRT de setlocale() (do PHP setlocale() é apenas um wrapper fino em torno da chamada CRT). O seguinte é uma citação do MSDN página "setlocale, _wsetlocale" :

O conjunto de idiomas disponíveis, códigos de país / região, e páginas de código inclui todos aqueles suportados pela Win32 NLS API , exceto páginas de código que requerem mais do que dois bytes por personagem, tal como UTF-7 e UTF-8. E se você fornece uma página de código como UTF-7 ou UTF-8, setlocale irá falhar, retornando NULL. O conjunto de linguagem e códigos de país / região apoiada por setlocale está listado em Língua e País / Região Strings.

Por isso, é impossível usar operações de cadeia de locale-aware dentro de PHP no Windows quando strings são multi-byte codificado.

Este é um muito complexo questão , desde UTF-8 dados codificados podem conter qualquer caractere Unicode (ou seja, a partir de diversos caracteres codificações de 8 bits, que intercalam de forma diferente em diferentes locais).

Talvez se você converteu seu UTF-8 dados em Unicode (não familiarizados com funções Unicode PHP, sorry) e depois normalizada-los em NFD ou NFKD e depois a classificação em pontos de código pode dar algum agrupamento que faria sentido para você (ou seja "a" antes de "a").

Verifique as ligações que eu forneci.

EDIT: uma vez que você menciona que os dados de entrada são claros (eu assumo todos eles caem nas "janelas-1252" página de código), então você deve fazer o seguinte conversão: UTF-8 ? Unicode ? Windows 1252, em que dados codificados do Windows-1252 fazer uma espécie de selecionar o locale "CP1252".

Usando o seu exemplo, com página de códigos 1252 funcionou perfeitamente bem aqui na minha máquina de desenvolvimento do 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 ...

Este foi com PHP 5.2.6. btw.


O exemplo acima é errado , ele usa ASCII codificação em vez de UTF-8. Eu fiz traçar o strcoll () chamadas e olha o que eu encontrei:

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);

dá:

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

Eu fiz encontrar algumas relatórios de bugs que foram sinalizados sendo falso ... A melhor aposta que você tem é a apresentação de um relatório de bugs Suponho embora ...

encontrado este seguinte função auxiliar para converter todas as letras de uma seqüência de letras ASCII muito úteis aqui.

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

Depois que uma simples array_multisort() dá-lhe o que quiser.

$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);

Claro que você pode fazer a função auxiliar atender às necessidades mais avançadas. Mas, por enquanto, parece muito bom.

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"
}

Eu sou confrontado com o mesmo problema com o alemão "trema". Depois de alguma pesquisa, isso funcionou para mim:

$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);

O resultado:

matriz
(
[0] => Ägypten
[1] => Inglaterra
[2] => França | [3] => Österreich
[4] => Schweiz
)

O seu agrupamento tem de corresponder ao conjunto de caracteres. Desde seus dados é UTF-8 codificado, você deve usar um agrupamento UTF-8. Pode ser nomeados de forma diferente em diferentes plataformas, mas um bom palpite seria de_DE.utf8.

Em sistemas UNIX, você pode obter uma lista de localidades atualmente instalados com o comando

locale -a
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top