سؤال

ليس لدي أدنى فكرة عن كيفية فرز مصفوفة تحتوي على سلاسل UTF-8 المشفرة في PHP.يأتي المصفوفة من خادم 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؟

لاحظت للتو أنه يبدو أن هذه مشكلة PHP على Windows، كما هو الحال في نفس المقتطف مع de_DE.utf8 تستخدم كلغة محلية تعمل على جهاز Linux.ومع ذلك، فإن الحل لهذه المشكلة الخاصة بنظام التشغيل Windows سيكون أمرًا رائعًا...

هل كانت مفيدة؟

المحلول 3

في نهاية المطاف، لا يمكن حل هذه المشكلة بطريقة بسيطة دون استخدام سلاسل مُعاد ترميزها (UTF-8 → Windows-1252 أو ISO-8859-1) كما اقترح ΤΖΩΤΖΙΟΥ بسبب خطأ PHP واضح كما اكتشفه Huppie.لتلخيص المشكلة، قمت بإنشاء مقتطف التعليمات البرمجية التالي الذي يوضح بوضوح أن المشكلة تكمن في وظيفة strcoll() عند استخدام صفحة الرموز 65001 Windows-UTF-8.

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 strcoll() لا يعمل مع سلاسل UTF-8 على نظام التشغيل Windows.إذا واجهت نفس المشكلة، فيمكنك تقديم تعليقاتك إلى فريق PHP على صفحة تقرير الأخطاء (تم تصنيف خطأين آخرين، ربما يكونان مرتبطين، على أنهما زائف - لا أعتقد أن هذا الخطأ هو زائف ;-).

شكرا لكم جميعا.

نصائح أخرى

$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 الدولي.يتم توزيعه مع مصادر PHP 5.3 ولكن قد يتم تعطيله لبعض الإصدارات.على سبيل المثالفي دبيان يوجد في الحزمة php5-intl .

Collator::compare مفيد ل usort.

تحديث حول هذه المشكلة:

على الرغم من أن المناقشة حول هذه المشكلة كشفت أنه كان من الممكن أن نكتشف خطأ PHP باستخدام strcoll() و/أو setlocale(), من الواضح أن هذا ليس هو الحال.المشكلة هي بالأحرى وجود قيود على تطبيق Windows CRT لـ setlocale() (PHP setlocale() هو مجرد غلاف رفيع حول مكالمة CRT).وفيما يلي اقتباس من صفحة MSDN "setlocale، _wsetlocale":

تتضمن مجموعة اللغات المتاحة ورموز البلد/المنطقة وصفحات التعليمات البرمجية جميع تلك التي تدعمها Win32 NLS API باستثناء صفحات التعليمات البرمجية التي تتطلب أكثر من بايت لكل حرف ، مثل UTF-7 و UTF-8.إذا قمت بتقديم صفحة رمز مثل UTF-7 أو UTF-8 ، فسوف تفشل SetLocale ، مع إرجاع NULL. يتم سرد مجموعة من رموز اللغة والبلد/المنطقة التي تدعمها Setlocale في سلاسل اللغة والبلد/المنطقة.

ولذلك فمن المستحيل استخدام عمليات سلسلة مدركة للإعدادات المحلية داخل PHP على نظام التشغيل Windows عندما تكون السلاسل مشفرة متعددة البايت.

هذا أمر معقد للغاية مشكلة, ، نظرًا لأن البيانات المشفرة بـ UTF-8 يمكن أن تحتوي على أي حرف Unicode (أي.أحرف من العديد من ترميزات 8 بت والتي يتم تجميعها بشكل مختلف في لغات مختلفة).

ربما إذا قمت بتحويل بيانات UTF-8 الخاصة بك إلى Unicode (لست على دراية بوظائف PHP Unicode، آسف) ثم قمت بتطبيعها إلى نفد أو نفكد ومن ثم فإن الفرز على نقاط الكود قد يعطي بعض الترتيب الذي قد يكون منطقيًا بالنسبة لك (على سبيل المثال "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.بالمناسبة.


المثال أعلاه هو خطأ, ، فهو يستخدم ترميز 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

لقد وجدت بعض تقارير الأخطاء التي تم وضع علامة عليها كونها زائف...أفضل رهان لديك هو تقديم تقرير بالأخطاء على ما أعتقد ...

أنا وجدت هذه الوظيفة المساعدة التالية لتحويل جميع أحرف السلسلة إلى أحرف 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"
}

أواجه نفس المشكلة مع "Umlaute" الألمانية.بعد بعض البحث، نجح هذا بالنسبة لي:

$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