Php не может найти способ разбить строки utf-8 - PullRequest
4 голосов
/ 03 декабря 2011

Я только начал заниматься php и боюсь, что мне нужна помощь, чтобы понять, как работать со строками utf-8.

Я работаю в Ubuntu 11.10 x86, php версии 5.3.6-13ubuntu3.2.У меня есть файл в кодировке utf-8 (vim :set encoding подтверждает это), и я затем приступаю к его чтению с использованием

$file = fopen("file.txt", "r");
while(!feof($file)){
    $line = fgets($file);
    //...
}
fclose($file);
  • с использованием mb_detect_encoding($line) отчетов UTF-8
  • Если я сделаю echo $line, я смогу правильно увидеть строку (без искаженных символов) в браузере
    • , поэтому я думаю, что все в порядке с браузером и apache.Хотя я искал в своей конфигурации apache AddDefaultCharset и попытался добавить метатеги http для кодировки символов (на всякий случай)

Когда я пытаюсь разделитьстрока, использующая $arr = mb_split(';',$line) поля полученного массива, содержат искаженные символы utf-8 (mb_detect_encoding($arr[0]) также сообщает utf-8).

Так что echo $arr[0] приведет к чему-то вроде этого: ΑΘΗÎÎ.

Я пробовал установить mb_detect_order('utf-8'), mb_internal_encoding('utf-8'), но ничего не изменилось.Я также попытался вручную определить utf-8, используя это регулярное выражение w3 perl , потому что я где-то читал, что mb_detect_encoding иногда может потерпеть неудачу (миф?), Но результаты были такими же.

Так что мойВопрос в том, как правильно разделить строку?Идет ли по пути mb_ неправильный путь?Чего мне не хватает?

Спасибо за вашу помощь!

ОБНОВЛЕНИЕ : я добавляю примеры строк и эквиваленты base64 (спасибо @chris 'за его предложение)

1. original string: "ΑΘΗΝΑ;ΑΙΓΑΛΕΩ;12242;37.99452;23.6889"
2. base64 encoded: "zpHOmM6Xzp3OkTvOkc6ZzpPOkc6bzpXOqTsxMjI0MjszNy45OTQ1MjsyMy42ODg5"
3. first part (the equivalent of "ΑΘΗΝΑ") base64 encoded before splitting: "zpHOmM6Xzp3OkQ=="
4. first part ($arr[0] after splitting): "ΑΘΗÎΑ"
5. first part after splitting base64 encoded: "77u/zpHOmM6Xzp3OkQ=="

Хорошо, так что после этого, кажется, есть разница 77u/ между 3. и 5. которая в соответствии с этим является меткой спецификации utf-8.Так как я могу избежать этого?

ОБНОВЛЕНИЕ 2 : Я проснулся освеженным сегодня и с вашими советами я попробовал это снова.Кажется, что $line=fgets($file) правильно читает первую строку (без искаженных символов) и завершается с ошибкой для каждой последующей строки.Итак, я base64_encoded первая и вторая строки, и 77u/ бомба появилась в строке base64'd только в первой строке .Затем я открыл файл с ошибками в vim и ввел :set nobomb :w, чтобы сохранить файл без BOM.Запуск php снова показал, что первая строка также была искажена.Основываясь на remove_utf8_bom @ hakre, я добавил, что это дополнительная функция

function add_utf8_bom($str){
    $bom= "\xEF\xBB\xBF";
    return substr($str,0,3)===$bom?$str:$bom.$str;
}

и вуаля каждая строка теперь читается правильно.

Мне не очень нравится это решение,так как это кажется очень хакерским (я не могу поверить, что весь фреймворк / язык не обеспечивает способ иметь дело с нобомбированными строками).Так вы знаете альтернативный подход?В противном случае я продолжу описанное выше.

Спасибо @chris, @hakre и @jacob за уделенное время!

ОБНОВЛЕНИЕ 3 (решение) : получаетсяв конце концов, это была вещь браузера: недостаточно было добавить header('Content-type: text/html; charset=UTF-8') и метатеги вроде <meta http-equiv="Content-type" value="text/html; charset=UTF-8" />.Он также должен быть правильно заключен в секцию <html><body>, иначе браузер не сможет правильно понять кодировку.Спасибо @jake за его предложение.

Мораль истории: прежде всего я должен узнать больше о html, прежде чем пытаться писать код для браузера.Спасибо всем за помощь и терпение.

Ответы [ 4 ]

4 голосов
/ 04 декабря 2011

UTF-8 имеет очень приятную особенность - ASCII-совместимость.Под этим я подразумеваю, что:

  • Символы ASCII остаются неизменными при кодировании в UTF-8
  • , другие символы не будут кодироваться в символы ASCII

Это означает, что когда вы пытаетесь разбить строку UTF-8 точкой с запятой ;, которая является символом ASCII, вы можете просто использовать стандартные однобайтовые строковые функции.

В вашем примере вы можете простоиспользуйте explode(';',$utf8encodedText) и все должно работать как положено.

PS: поскольку кодировка UTF-8 без префикса , вы можете использовать explode() с любым кодированным разделителем UTF-8.

PPS: похоже, вы пытаетесь проанализировать файл CSV.Посмотрите на функцию fgetcsv () .Он должен прекрасно работать с строками в кодировке UTF-8, если вы используете символы ASCII для разделителей, кавычек и т. Д.

1 голос
/ 04 декабря 2011

Когда вы пишете сценарии отладки / тестирования на php, убедитесь, что вы выводите более или менее допустимую страницу HTML.

Мне нравится использовать файл PHP, подобный следующему:

<code><!DOCTYPE html>
<html>
  <head>
    <meta charset=utf-8>
    <title>Test page for project XY</title>
  </head>
  <body>
     <h1>Test Page</h1>
     <pre><?php
        echo print_r($_GET,1);
     ?>

Если вы не включите какие-либо теги HTML, браузер может интерпретировать файл как текстовый файл, и могут произойти любые странные вещи.В вашем случае я предполагаю, что браузер интерпретировал файл как текстовый файл с кодировкой Latin1.Я предполагаю, что это работало с BOM, потому что всякий раз, когда BOM присутствовал, браузер распознавал файл как файл UTF-8.

1 голос
/ 04 декабря 2011

Редактировать, я просто прочитал ваш пост ближе. Вы предполагаете, что это должно вывести false, потому что вы предлагаете, чтобы спецификация была введена mb_split ().

header('content-type: text/plain;charset=utf-8');
$s = "zpHOmM6Xzp3OkTvOkc6ZzpPOkc6bzpXOqTsxMjI0MjszNy45OTQ1MjsyMy42ODg5";
$str = base64_decode($s);

$peices = mb_split(';', $str);

var_dump(substr($str, 0, 10) === $peices[0]);
var_dump($peices);

Есть ли это? Для меня это работает как положено (bool true, а строки в массиве верны)

1 голос
/ 03 декабря 2011

Функция mb_split Документы должна быть в порядке, но вы должны также определить кодировку, которую она использует, с помощью mb_regex_encoding Документы :

mb_regex_encoding('UTF-8');

О mb_detect_encoding Документы : возможнопотерпеть неудачу, но это только потому, что вы никогда не сможете обнаружить кодировку.Вы либо знаете это, либо можете попробовать, но это все.Обнаружение кодирования - это в основном азартная игра, однако вы можете использовать строгий параметр с этой функцией и указать код (ы), которые вы ищете.

Как снять маску спецификации:

Вы можете отфильтровать строковый ввод и удалить бомбу UTF-8 с помощью небольшой вспомогательной функции:

/**
 * remove UTF-8 BOM if string has it at the beginning
 *
 * @param string $str
 * @return string
 */
function remove_utf8_bom($str)
{
   if ($bytes = substr($str, 0, 3) && $bytes === "\xEF\xBB\xBF") 
   {
       $str = substr($str, 3);
   }
   return $str;
}

Использование:

$line = remove_utf8_bom($line);

Возможно, есть лучшие способысделать это, но это должно сработать.

...