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". Надеюсь, что это помогает любому из других.

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