2008-09-23 121 views
22

我currentyl沒有線索如何排序在PHP中包含UTF-8編碼字符串的數組。該數組來自LDAP服務器,因此通過數據庫進行排序(沒有問題)是沒有解決方案的。 下面我的Windows開發機器上不工作(雖然我認爲這應該是至少一個可能的解決方案):如何對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字符串數組語言環境感知?

剛纔注意到,這似乎是Windows上的PHP問題,因爲在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或由於由Huppie發現的明顯的PHP錯誤,因此由TZH_TZIO_0Y建議的ISO-8859-1)。 爲了總結這個問題,我創建了以下代碼片段,它清楚地表明問題在於使用65001 Windows-UTF-8代碼頁時的strcoll()函數。

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團隊(其他兩個,可能是相關的,錯誤已被列爲 - 我不認爲這個錯誤是 ;-)。

感謝大家。

3

這是一個非常複雜的issue,因爲UTF-8編碼的數據可以包含任何Unicode字符(從整理許多不同的8位編碼的字符,即在不同的地區)。如果你將你的UTF-8數據轉換爲Unicode(不熟悉PHP unicode函數,對不起),然後將它們歸一化爲NFD or NFKD,然後在代碼點上排序可能會給出一些對你有意義的歸類(即「一個「在」Ä「之前)。

檢查我提供的鏈接。編輯:既然你提到你的輸入數據是清晰的(我認爲它們都屬於「windows-1252」代碼頁),那麼你應該做以下轉換:UTF-8→Unicode→Windows-1252,開啓哪些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中使用你的例子在我的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); 

...略...

這是用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()(PHPs setlocale()只是CRT調用的一個薄包裝)的限制。以下爲MSDN page "setlocale, _wsetlocale"的引文:

一組可用的語言, 國家/地區代碼和代碼頁的 包括所有那些除了代碼頁由 的Win32 NLS API 支持了 需要超過每個 字符有兩個字節,如UTF-7和UTF-8。如果 您提供了UTF-7或 UTF-8代碼頁,則setlocale將失敗,返回 NULL。設置的語言和 支持的國家/地區代碼 setlocale列於語言和 國家/地區字符串。

因此,當字符串是多字節編碼時,不可能在Windows的PHP中使用區域感知字符串操作。

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::compareusort有用。

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] =>瑞士

相關問題