質問

現在、PHP で UTF-8 でエンコードされた文字列を含む配列をソートする方法がわかりません。配列は LDAP サーバーから取得されるため、データベースを介して並べ替えても (問題ないでしょう) 解決策にはなりません。以下は私の Windows 開発マシンでは機能しません (ただし、これは少なくとも解決策として考えられるはずだと思います)。

$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 の問題であると思われることに注意してください。 de_DE.utf8 ロケールとして使用されるものは、Linux マシン上で動作します。それでも、この Windows 固有の問題の解決策があれば良いのですが...

役に立ちましたか?

解決 3

結局、この問題は、Huppie によって発見された明らかな PHP のバグのため、ΤΖΩΤΖΙΟΥ によって示唆されているように、再コード化された文字列 (UTF-8 → Windows-1252 または 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: バグ #46165 Windows では strcoll() が UTF-8 文字列で機能しない. 。同じ問題が発生した場合は、バグレポート ページで PHP チームにフィードバックを送信できます (おそらく関連する他の 2 つのバグは次のように分類されています) インチキな - このバグはないと思います インチキな ;-).

あなた方全員に感謝します。

他のヒント

$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 拡張子. 。PHP 5.3 ソースとともに配布されますが、一部のビルドでは無効になる場合があります。例えば。Debian では、パッケージ php5-intl にあります。

Collator::compare に役立ちます usort.

この問題に関する最新情報:

この問題に関する議論の結果、PHP のバグが発見された可能性があることが明らかになりましたが、 strcoll() および/または setlocale(), 、これは明らかにそうではありません。問題はむしろ、Windows CRT 実装の制限です。 setlocale() (PHP setlocale() は CRT 呼び出しの単なる薄いラッパーです)。以下はその引用です MSDN ページ「setlocale、_wsetlocale」:

利用可能な言語、国/地域コード、およびコードページのセットには、Win32 NLS APIによってサポートされているすべてのページが含まれています UTF-7やUTF-8など、文字ごとに3バイト以上を必要とするコードページを除きます。UTF-7やUTF-8などのコードページを提供すると、setlocaleが失敗し、nullを返します。 Setlocaleがサポートする言語と国/地域のコードのセットは、言語および国/地域の文字列にリストされています。

したがって、文字列がマルチバイトでエンコードされている場合、Windows 上の PHP 内でロケールを認識した文字列操作を使用することはできません。

これは非常に複雑です 問題, 、UTF-8 でエンコードされたデータには任意の Unicode 文字を含めることができるため (つまり、ロケールごとに照合方法が異なる多くの 8 ビット エンコーディングの文字)。

おそらく、UTF-8 データを Unicode に変換し (PHP の Unicode 関数について詳しくありません、ごめんなさい)、それらを次のように正規化したとします。 NFDまたはNFKD そして、コードポイントでソートすると、意味のある照合順序が得られる可能性があります (つまり、「Ä」の前に「A」)。

私が提供したリンクを確認してください。

編集:入力データが明確であると述べているため(すべて「windows-1252」コードページに分類されると思います)、次の変換を行う必要があります。UTF-8 → Unicode → Windows-1252。Windows-1252 でエンコードされたデータは、「CP1252」ロケールを選択して並べ替えられます。

コードページ 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 で発生しました。ところで。


上記の例は 間違っている, 、UTF-8 の代わりに ASCII エンコードを使用します。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

いくつか見つけました バグレポート フラグが立てられているもの インチキな...最善の策はバグレポートを提出することだと思いますが...

次のヘルパー関数が見つかりました 文字列のすべての文字を 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"
}

私はドイツ語の「ウムラウテ」でも同じ問題に直面しています。いくつかの調査の後、これは私にとってはうまくいきました:

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

結果:

配列
(
[0] => エジプト
[1] => イングランド
[2] => フランス
[3] => エスターライヒ
[4] => シュヴァイツ
)

照合順序は文字セットと一致する必要があります。データは UTF-8 でエンコードされているため、UTF-8 照合順序を使用する必要があります。プラットフォームごとに異なる名前が付けられる可能性がありますが、おそらく次のようになります。 de_DE.utf8.

UNIX システムでは、次のコマンドを使用して、現在インストールされているロケールのリストを取得できます。

locale -a
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top