Проблема 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];
 }

person Richard Knop    schedule 23.08.2010    source источник
comment
Убедились ли вы, что отправляете соответствующий заголовок Content-type? т.е. если вы открываете страницу в firefox, проверьте, что View->Charset Encoding установлено в UTF8.   -  person Raoul Duke    schedule 23.08.2010
comment
Вы пробовали метод сохранения: $doc-›save();   -  person Cem Kalyoncu    schedule 23.08.2010
comment
@Cem Я попробую. Подождите несколько минут.   -  person Richard Knop    schedule 23.08.2010


Ответы (3)


Ваш "хак" не имеет смысла.

Вы конвертируете HTML-файл Windows-1250 в UTF-8, а затем добавляете <?xml encoding="UTF-8">. Это не сработает. Расширение DOM для файлов HTML:

  • Принимает кодировку, указанную в мета-http-equiv для «типа контента».
  • В противном случае предполагается ISO-8859-1.

Вместо этого я предлагаю вам преобразовать Windows-1250 в ISO-8859-1 и ничего не добавлять.

EDIT Предложение не очень хорошее, потому что в 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) "ĄĽź ‰ ‡ … á (some exist on win-1250, but not LATIN1 or even win-1252)"
person Artefacto    schedule 23.08.2010
comment
Если вы работали с неанглийскими данными (cp1250 или другими), вы знаете, что этот хак иногда является единственным способом заставить PHP DOM сохранить специальные символы UTF-8. Это также упоминается в документации PHP. Вы можете попробовать создать базу данных cp1250, извлечь оттуда некоторые данные и проанализировать их с помощью PHP DOM. Это настоящая боль. - person Richard Knop; 23.08.2010
comment
@Rich Это также упоминается в документации PHP. Ссылку пожалуйста. Примечания пользователя не являются частью документации. - person Artefacto; 23.08.2010
comment
@Artefacto Здесь комментарий пользователя (php.net/manual/en/domdocument .loadhtml.php). Это третий комментарий сверху. Я знаю, что это не официально, но иногда это единственный способ. Это не единственный раз, когда комбинация Windows-1250 + PHP DOM вызывает у меня головную боль. Тем не менее, я просто немного поспал, и у меня есть идея, как это решить (хотя не уверен, что это сработает). Я попробую это завтра, если это не сработает, я, вероятно, начну вознаграждение за этот вопрос. - person Richard Knop; 24.08.2010
comment
@Artefacto У меня есть идея о том, в чем может быть проблема, отсюда: bugs.php.net /bug.php?id=32547 А теперь я иду спать. - person Richard Knop; 24.08.2010
comment
@Artefacto Если я решу это, я, вероятно, впервые добавлю комментарий в документацию PHP: D - person Richard Knop; 24.08.2010
comment
@Rich Что касается отчета об ошибке - это то, что я говорил. Если вы не поместите туда мета, предполагается, что это ISO-8859-1. Однако моего решения также недостаточно, потому что в Windows-1250 есть символы, которых нет в ISO-8859-1. - person Artefacto; 24.08.2010
comment
@Artefacto Проверьте мой обновленный вопрос. У меня нет возможности проверить это дома, но я проверю это завтра на работе. И, кстати, $doc-›loadHTML('‹?xml encoding=UTF-8›' . $html); хак необходим, потому что HTML из базы данных недействителен. Это вывод WYSIWYG-редактора, что-то вроде «‹p›Hello‹/p›». Так что у него нет тегов html, head, body. И если я использую метод loadHTML() для такого недопустимого HTML, PHP DOM сойдет с ума и испортит все символы UTF-8. - person Richard Knop; 24.08.2010
comment
Спасибо тебе за это. PHP, не поддерживающий UTF-8 как первоклассный гражданин, иногда заставляет меня хотеть переключаться между языками. - person Xunnamius; 21.05.2013

Два решения.

Вы можете установить кодировку в качестве заголовка:

<?php header("Content-Type", "text/html; charset=utf-8"); ?>

Или вы можете установить его как META-тег:

<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">

РЕДАКТИРОВАТЬ: если оба они установлены правильно, сделайте следующее:

  • Создайте маленькую страницу с символом UTF-8.
  • Напишите страницу тем же методом, который у вас уже есть.
  • Используйте Fiddler или Wireshark для проверки необработанных байтов, передаваемых в средах DEV и PROD. Вы также можете дважды проверить заголовки, используя Fiddler/Wireshark.

Если вы уверены, что отправляется правильный заголовок, то ваш лучший шанс найти ошибку — начать просматривать необработанные байты. Идентичные байты, отправленные в идентичный браузер, дадут одинаковый результат, поэтому вам нужно начать искать, почему они не идентичны. Fiddler/Wireshark поможет с этим.

person riwalk    schedule 23.08.2010
comment
Я не думаю, что это решит проблему, если это действительно работает с var_dump - person Cem Kalyoncu; 23.08.2010
comment
Он упоминает, что это работает на его сервере разработки, а это означает, что весьма вероятно, что байты записываются правильно. Наиболее вероятная проблема заключается в том, что байты читаются неправильно, и это должно исправить это. - person riwalk; 23.08.2010
comment
Заголовок отправлен корректно. Также есть правильный метатег. - person Richard Knop; 23.08.2010
comment
Хорошо, я попробую использовать скрипача. Кстати, я думаю, что проблема вызвана PHP DOM. Я думаю, что это портит восточноевропейские символы UTF-8. Знаете ли вы какую-либо альтернативу PHP DOM, которую я мог бы использовать для анализа HTML? - person Richard Knop; 23.08.2010

У меня такая же проблема. Мое исправление заключалось в использовании notepad ++ и настройке кодировки php-документа на «UTF-8 без спецификации». Надеюсь, это поможет любому другому.

person user2494874    schedule 18.08.2013