2008-09-23 117 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] =>瑞士

相关问题