PHP DOM UTF-8 проблема
Прежде всего, моя база данных использует Windows-1250 как собственную кодировку. Я вывожу данные как UTF-8. Я использую функцию iconv() по всему сайту для преобразования строк Windows-1250 в строки UTF-8, и она отлично работает.
Проблема заключается в том, что я использую PHP DOM для анализа некоторого HTML-кода, хранящегося в базе данных (HTML-код является выводом из редактора WYSIWYG и недопустим, он не имеет тегов html, head, body и т. Д.).
HTML может выглядеть примерно так, например:
<p>Hello</p>
Вот метод, который я использую для анализа определенного HTML из базы данных:
private function ParseSlideContent($slideContent)
{
var_dump(iconv('Windows-1250', 'UTF-8', $slideContent)); // this outputs the HTML ok with all special characters
$doc = new DOMDocument('1.0', 'UTF-8');
// hack to preserve UTF-8 characters
$html = iconv('Windows-1250', 'UTF-8', $slideContent);
$doc->loadHTML('<?xml encoding="UTF-8">' . $html);
$doc->preserveWhiteSpace = false;
foreach($doc->getElementsByTagName('img') as $t) {
$path = trim($t->getAttribute('src'));
$t->setAttribute('src', '/clientarea/utils/locate-image?path=' . urlencode($path));
}
foreach ($doc->getElementsByTagName('object') as $o) {
foreach ($o->getElementsByTagName('param') as $p) {
$path = trim($p->getAttribute('value'));
$p->setAttribute('value', '/clientarea/utils/locate-flash?path=' . urlencode($path));
}
}
foreach ($doc->getElementsByTagName('embed') as $e) {
if (true === $e->hasAttribute('pluginspage')) {
$path = trim($e->getAttribute('src'));
$e->setAttribute('src', '/clientarea/utils/locate-flash?path=' . urlencode($path));
} else {
$path = end(explode('data/media/video/', trim($e->getAttribute('src'))));
$path = 'data/media/video/' . $path;
$path = '/clientarea/utils/locate-video?path=' . urlencode($path);
$width = $e->getAttribute('width') . 'px';
$height = $e->getAttribute('height') . 'px';
$a = $doc->createElement('a', '');
$a->setAttribute('href', $path);
$a->setAttribute('style', "display:block;width:$width;height:$height;");
$a->setAttribute('class', 'player');
$e->parentNode->replaceChild($a, $e);
$this->slideContainsVideo = true;
}
}
$html = trim($doc->saveHTML());
$html = explode('<body>', $html);
$html = explode('</body>', $html[1]);
return $html[0];
}
Вывод описанного выше метода - это мусор, в котором все специальные символы заменены на странные вещи, такие как šÄ.
Еще кое-что. Это работает на моем сервере разработки.
Это не работает на производственном сервере, хотя.
Какие-либо предложения?
PHP версия производственного сервера: PHP версия 5.2.0RC4-dev
PHP версия сервера разработки: PHP версия 5.2.13
ОБНОВИТЬ:
Я сам работаю над решением проблемы. У меня есть вдохновение из этого отчета об ошибках PHP (хотя на самом деле это не ошибка): http://bugs.php.net/bug.php?id=32547
Это мое предлагаемое решение. Я попробую завтра и сообщу, работает ли он:
private function ParseSlideContent($slideContent)
{
var_dump(iconv('Windows-1250', 'UTF-8', $slideContent)); // this outputs the HTML ok with all special characters
$doc = new DOMDocument('1.0', 'UTF-8');
// hack to preserve UTF-8 characters
$html = iconv('Windows-1250', 'UTF-8', $slideContent);
$doc->loadHTML('<?xml encoding="UTF-8">' . $html);
$doc->preserveWhiteSpace = false;
// this might work
// it basically just adds head and meta tags to the document
$html = $doc->getElementsByTagName('html')->item(0);
$head = $doc->createElement('head', '');
$meta = $doc->createElement('meta', '');
$meta->setAttribute('http-equiv', 'Content-Type');
$meta->setAttribute('content', 'text/html; charset=utf-8');
$head->appendChild($meta);
$body = $doc->getElementsByTagName('body')->item(0);
$html->removeChild($body);
$html->appendChild($head);
$html->appendChild($body);
foreach($doc->getElementsByTagName('img') as $t) {
$path = trim($t->getAttribute('src'));
$t->setAttribute('src', '/clientarea/utils/locate-image?path=' . urlencode($path));
}
foreach ($doc->getElementsByTagName('object') as $o) {
foreach ($o->getElementsByTagName('param') as $p) {
$path = trim($p->getAttribute('value'));
$p->setAttribute('value', '/clientarea/utils/locate-flash?path=' . urlencode($path));
}
}
foreach ($doc->getElementsByTagName('embed') as $e) {
if (true === $e->hasAttribute('pluginspage')) {
$path = trim($e->getAttribute('src'));
$e->setAttribute('src', '/clientarea/utils/locate-flash?path=' . urlencode($path));
} else {
$path = end(explode('data/media/video/', trim($e->getAttribute('src'))));
$path = 'data/media/video/' . $path;
$path = '/clientarea/utils/locate-video?path=' . urlencode($path);
$width = $e->getAttribute('width') . 'px';
$height = $e->getAttribute('height') . 'px';
$a = $doc->createElement('a', '');
$a->setAttribute('href', $path);
$a->setAttribute('style', "display:block;width:$width;height:$height;");
$a->setAttribute('class', 'player');
$e->parentNode->replaceChild($a, $e);
$this->slideContainsVideo = true;
}
}
$html = trim($doc->saveHTML());
$html = explode('<body>', $html);
$html = explode('</body>', $html[1]);
return $html[0];
}
3 ответа
Ваш "хак" не имеет смысла.
Вы конвертируете HTML-файл Windows-1250 в UTF-8 и затем добавляете <?xml encoding="UTF-8">
, Это не сработает. Расширение DOM для файлов HTML:
- Принимает кодировку, указанную в мета-http-эквиваленте для "content-type".
- В противном случае предполагается ISO-8859-1
Я предлагаю вам вместо этого конвертировать из Windows-1250 в ISO-8859-1 и ничего не добавлять.
РЕДАКТИРОВАТЬ Предложение не очень хорошо, потому что Windows-1250 имеет символы, которые не в ISO-8859-1. Поскольку вы имеете дело с фрагментами без meta
элементы для типа контента, вы можете добавить свои собственные для принудительной интерпретации как UTF-8:
<?php
//script and output are in UTF-8
/* Simulate HTML fragment in Windows-1250 */
$html = <<<XML
<p>ĄĽź ‰ ‡ … á (some exist on win-1250, but not LATIN1 or even win-1252)</p>
XML;
$htmlInterm = iconv("UTF-8", "Windows-1250", $html); //convert
/* Append meta header to force UTF-8 interpretation and convert into UTF-8 */
$htmlInterm =
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />" .
iconv("Windows-1250", "UTF-8", $htmlInterm);
/* Omit libxml warnings */
libxml_use_internal_errors(true);
/* Build DOM */
$d = new domdocument;
$d->loadHTML($htmlInterm);
var_dump($d->getElementsByTagName("body")->item(0)->textContent); //correct UTF-8
дает:
string (79) "ĄĽź ‰ ‡… á (некоторые существуют на win-1250, но не на LATIN1 или даже на win-1252)"
Два решения.
Вы можете установить кодировку в качестве заголовка:
<?php header("Content-Type", "text/html; charset=utf-8"); ?>
Или вы можете установить его как мета-тег:
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
РЕДАКТИРОВАТЬ: в случае, если оба из них установлены правильно, выполните следующие действия:
- Создайте небольшую страницу с символом UTF-8.
- Напишите страницу тем же способом, который у вас уже есть.
- Используйте Fiddler или Wireshark для проверки необработанных байтов, передаваемых в ваших средах DEV и PROD. Вы также можете дважды проверить заголовки, используя Fiddler/Wireshark.
Если вы уверены, что отправляется правильный заголовок, то лучший шанс найти ошибку - начать смотреть на необработанные байты. Одинаковые байты, отправленные в идентичный браузер, дадут тот же результат, поэтому вам нужно начать искать, почему они не идентичны. Fiddler/Wireshark поможет с этим.
У меня такая же проблема. Мое исправление заключалось в использовании notepad++ и установке кодировки php-документа на "UTF-8 без BOM". Надеюсь, что это помогает любому из других.