2008-09-23 2 views
22

I currentyl не имеет понятия о том, как отсортировать массив, который содержит кодированные строки UTF-8 в PHP. Массив поставляется с сервера LDAP, поэтому сортировка по базе данных (без проблем) не является решением. Ниже не работает на моей машине развития окна (хотя я думаю, что это должно быть по крайней мере, одно из возможных решений):Как отсортировать массив строк UTF-8?

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

Выход:

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

Это полная чушь , Используя 1252 как кодовая для setlocale() дает другой выход, но все еще явно неправильны один:

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

Есть ли способ, чтобы отсортировать массив с UTF-8 строк локали известно?

Только что отметил, что это похоже на проблему с PHP на Windows, поскольку тот же фрагмент с , используемый в качестве языкового стандарта, работает на машине Linux. Тем не менее решение этой Windows, конкретной проблемы было бы неплохо ...

+1

Здесь все работает отлично (см. Мой пост ниже), вы уверены, что это не имеет никакого отношения к конфигурации машины? – Huppie 2008-09-23 11:26:19

+0

Обратите внимание, что порядок сортировки зависит от языка. На немецком языке A и Ä иногда могут быть отсортированы так, как если бы они были одним и тем же письмом, а иногда Ä можно сортировать так, как это было на самом деле «AE». Шведский, однако, Ä приходит в конце алфавита. Carl – 2008-09-24 08:16:04

+0

Вы правы - это свойство уважается, используя правильную локаль и strcoll() для сортировки. Проблема здесь в том, что на Windows strcoll(), похоже, проблема, когда входные строки кодируются UTF-8. – 2008-09-24 08:57:12

ответ

5

В конце концов, эта проблема не может быть решена простым способом без использования перекодировать строки (UTF-8 → Windows-1252 или ISO-8859-1), как было предложено TΖΩΤΖΙΟΥ из-за очевидной ошибки PHP, обнаруженной Huppie. Чтобы подвести итог этой проблеме, я создал следующий фрагмент кода, который наглядно демонстрирует, что проблема заключается в функции strcoll() при использовании кодовой страницы 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); 

Результат:

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

Тот же фрагмент кода работает на компьютере Linux без каких-либо проблем, продуцирующих следующий вывод:

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

Сниппет также работает при использовании Windows-1252 (ISO-8859-1) (конечно, необходимо изменить кодировки mb_ * и локаль).

Я подал сообщение об ошибке на bugs.php.net: Bug #46165 strcoll() does not work with UTF-8 strings on Windows. Если у вас возникли те же проблемы, вы можете дать свои отзывы команде PHP на странице отчета об ошибках (две другие, вероятно, связанные ошибки были классифицированы как bogus - Я не думаю, что эта ошибка bogus ;-).

Спасибо всем.

3

Это очень сложный issue, так как UTF-8 кодируются данные могут содержать любые символы Unicode (т.е. символы от многих 8-битных кодировок которые обобщают по-разному в разных местах).

Возможно, если вы преобразовали данные UTF-8 в Unicode (не знакомы с функциями юникода PHP, извините), а затем нормализовали их на NFD or NFKD, а затем сортировка по кодовым точкам может дать вам некоторую сортировку, которая имела бы смысл для вас (например, A "до" Ä ").

Проверьте ссылки, которые я предоставил.

EDIT: поскольку вы указываете, что ваши входные данные ясны (я предполагаю, что все они попадают в кодовую страницу «windows-1252»), тогда вы должны сделать следующее преобразование: UTF-8 → Юникод → Windows-1252, on которые кодированные данные Windows-1252 выполняют сортировку, выбирающую локаль «CP1252».

+0

Спасибо за эту информацию - я посмотрю ссылки. Но я сомневаюсь, что это результат стоит того, что я просто хочу, чтобы отсортировать список названий стран и штатов. Возможно, существует более простое решение. – 2008-09-23 11:35:46

+0

Кажется, это разумное решение ... Я попробую сортировать преобразованный массив. Вы правы, что Windows-1252 должен охватывать все используемые символы. – 2008-09-23 12:20:01

+6

Что значит конвертировать UTF-8 в Unicode. UTF-8 - кодировка символов переменной длины для Unicode. – grom 2008-09-23 12:46:42

0

Использование вашего примера с кодовой страницей 1252 отлично работает здесь, на моей машине разработки окон.

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

... чик ...

Это было с PHP 5.2.6. Кстати.


Приведенный выше пример неправильный, он использует кодировку ASCII вместо UTF-8. Я сделал след strcoll() вызывает и посмотрите, что я нашел:

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

дает:

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

я нашел некоторые bug reports, которые были заподозрены быть bogus ... Лучше всего у вас есть подает сообщение об ошибке, я полагаю, хотя ...

-1

Ваша сортировка должна соответствовать набору символов. Поскольку ваши данные кодируются в кодировке UTF-8, вы должны использовать сортировку UTF-8. Его можно было бы назвать по-разному на разных платформах, но хорошее предположение было бы .

В системах UNIX, вы можете получить список установленных локалей с помощью команды

locale -a 
6

Обновление по этому вопросу:

Несмотря на то, обсуждение этой проблемы показало, что мы могли бы обнаружили PHP ошибка с strcoll() и/или setlocale(), это явно не тот случай. Проблема скорее является ограничением реализации Windows CRT setlocale() (PHP setlocale() - это всего лишь тонкая оболочка вокруг вызова CRT). Ниже приводится цитата из MSDN page "setlocale, _wsetlocale":

Набор доступных языков, страны/кодов региона, а также кодовые страницы включает в себя все те, которые поддерживаются в Win32 NLS API кроме кодовых страниц, которые требуют более два байта на символ , такие как UTF-7 и UTF-8. Если вы предоставите кодовую страницу, такую ​​как UTF-7 или UTF-8, setlocale завершится с ошибкой, возвращая NULL. Набор языков и код страны/региона, поддерживаемый setlocale указан на языке и Страна/регион Строки.

В этом случае невозможно использовать языковые операции со строками в PHP на Windows, если строки кодируются в нескольких байтах.

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

Печать:

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) 

Collator класс определяется в PECL intl extension. Он распространяется с PHP 5.3, но может быть отключено для некоторых сборок. Например. в Debian он находится в пакете php5-intl.

Collator::compare полезен для usort.

0

I found this following helper function, чтобы преобразовать все буквы строки в буквы ASCII, очень полезные здесь.

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

После этого простой array_multisort() дает вам то, что вы хотите.

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

Конечно, вы можете сделать вспомогательную функцию более совершенной. Но пока это выглядит неплохо.

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

Я столкнулся с той же проблемой с немецким «Umlaute». После некоторых исследований, это работает для меня:

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

Результат:

Массив
(
[0] => Ägypten
[1] => Англия
[2] => Франция
[3] => Österreich
[4] => Schweiz
)

Смежные вопросы