Php не может найти способ разбить строки utf-8

Я только начал заниматься 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/ bom появился только в строке base64'd первой строки. Затем я открыл оскорбительный файл в VIM и ввел :set nobomb:w сохранить файл без бомбы. Запуск php снова показал, что первая строка также была искажена. Основано на @ Хакре remove_utf8_bom я добавил, что это дополнительная функция

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 ответа

Решение

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

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

<!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);
     ?></pre>
  </body>
</html>

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

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

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

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

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

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

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

Отредактируйте, я просто прочитал ваш пост ближе. Вы предполагаете, что это должно вывести 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, а строки в массиве верны)

mb_splitФункция Docs должна быть в порядке, но вы должны также определить кодировку, которую она использует 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);

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

Другие вопросы по тегам