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);
Возможно, есть лучшие способы сделать это, но это должно сработать.