PHP DOMDocument loadHTML неправильно кодує UTF-8


195

Я намагаюся проаналізувати деякий HTML за допомогою DOMDocument, але коли я це роблю, я раптом втрачаю кодування (принаймні так мені здається).

$profile = "<div><p>various japanese characters</p></div>";
$dom = new DOMDocument();
$dom->loadHTML($profile); 

$divs = $dom->getElementsByTagName('div');

foreach ($divs as $div) {
    echo $dom->saveHTML($div);
}

Результатом цього коду є те, що я отримую купу символів, які не є японцями. Однак якщо мені це зробити:

echo $profile;

він відображається правильно. Я спробував saveHTML і saveXML, і не відображав належним чином. Я використовую PHP 5.3.

Що я бачу:

ã¤ãªãã¤å·ã·ã«ã´ã«ã¦ãã¢ã¤ã«ã©ã³ãç³»ã®å®¶åº­ã«ã9人åå¼ã®5çªç®ã¨ãã¦çã¾ãããå½¼ãå«ãã¦4人ã俳åªã«ãªã£ããç¶è¦ªã¯æ¨æã®ã»ã¼ã«ã¹ãã³ã§ãæ¯è¦ªã¯éµä¾¿å±ã®å®¢å®¤ä¿ã ã£ããé«æ ¡æ代ã¯ã­ã£ãã£ã®ã¢ã«ãã¤ãã«å¤ãã¿ãæè²è³éãåããªããã«ããªãã¯ç³»ã®é«æ ¡ã¸é²å­¦ã

Що слід показати:

イリノイ州シカゴにて、アイルランド系の家庭に、9人兄弟の5番目として生まれる。彼を含めて4人が俳優になった。父親は木材のセールスマンで、母親は郵便局の客室係だった。高校時代はキャディのアルバイトに勤しみ、教育資金を受けながらカトリック系の高校へ進学

EDIT: Я спростив код до п'яти рядків, щоб ви могли його протестувати самостійно.

$profile = "<div lang=ja><p>イリノイ州シカゴにて、アイルランド系の家庭に、</p></div>";
$dom = new DOMDocument();
$dom->loadHTML($profile);
echo $dom->saveHTML();
echo $profile;

Ось HTML, який повертається:

<div lang="ja"><p>イリノイ州シカゴã«ã¦ã€ã‚¢ã‚¤ãƒ«ãƒ©ãƒ³ãƒ‰ç³»ã®å®¶åº­ã«ã€</p></div>
<div lang="ja"><p>イリノイ州シカゴにて、アイルランド系の家庭に、</p></div>

Це може вам допомогти. stackoverflow.com/questions/1580543/…
frustratedtech

Дякую. Я все це перевірив і нічого не допомогло. Я не отримую ????, але якийсь інший дивний текст. Я спробую вставити його сюди, але не знаю, як сайт його відображатиме.
Трохи А.

Спробуйте скористатися utf8_encode
Webnet

Спробував без успіху. Поверталися ті самі символи, що і раніше.
Трохи А.

Відповіді:


514

DOMDocument::loadHTMLбуде вважати вашу рядок як ISO-8859-1, якщо ви не скажете це інше. Це призводить до того, що рядки UTF-8 трактуються неправильно.

Якщо ваша рядок не містить декларації кодування XML, ви можете додати її, щоб спричинити обробку рядка як UTF-8:

$profile = '<p>イリノイ州シカゴにて、アイルランド系の家庭に、9</p>';
$dom = new DOMDocument();
$dom->loadHTML('<?xml encoding="utf-8" ?>' . $profile);
echo $dom->saveHTML();

Якщо ви не можете знати, чи рядок буде містити таку заяву, у SmartDOMDocument є вирішення, яке повинно вам допомогти:

$profile = '<p>イリノイ州シカゴにて、アイルランド系の家庭に、9</p>';
$dom = new DOMDocument();
$dom->loadHTML(mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8'));
echo $dom->saveHTML();

Це не дуже вдале рішення, але оскільки не всі символи можуть бути представлені в ISO-8859-1 (як ці катани), це найбезпечніша альтернатива.


1
Так, це і вдалося. Дякую за твою допомогу. Я спробував saveHTML, saveXML, не думав, що проблема може виникнути під час завантаження.
Трохи А.

4
Виклик mb_convert_encoding працював на мене, тоді як попереднє оголошення кодування не відбулося. Ймовірно, тому, що в документі вже була суперечлива декларація. Велике спасибі - врятувало мені багато часу, переслідуючи це.
Пітер Баньялл

1
$dom->loadHTML('<?xml encoding="utf-8" ?>' . $content);виправили це для мене в PHP7 (тому це все-таки проблема) - це справді прикрою проблемою, тому що я визначив utf8 в HTML-документі (з <meta charset="UTF-8" />), але це не має ніякого ефекту, здається, потрібна частина <? xml, яка є абсолютно неінтуїтивним.
ікіто

11
Ще в 2017 році ця відповідь є актуальною і спрацювала і для мене. У мене була база даних, мультибайт, мета-тег html та кодування DOM, все встановлено на utf8, і все ще було погано кодування при імпорті вузла з одного DOC в інший. php.net/manual/en/function.mb-convert-encoding.php було виправлено.
Луї Лудог Троттьє

6
$dom->loadHTML(mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8'));працює чудово! Дякую,
vee

67

Проблема з saveHTML()і saveXML()обидва вони не можуть коректно працювати в Unix. Вони не зберігають символи UTF-8 при використанні в Unix, але працюють у Windows.

Вирішення питання дуже просте:

Якщо ви спробуєте за замовчуванням, ви отримаєте описану помилку

$str = $dom->saveHTML(); // saves incorrectly

Все, що вам потрібно зробити, це зберегти:

$str = $dom->saveHTML($dom->documentElement); // saves correctly

Цей рядок коду дозволить правильно зберегти ваші символи UTF-8. Використовуйте той самий спосіб вирішення, якщо ви використовуєте saveXML().


Оновлення

Як запропонував " Джек М " у розділі коментарів нижче та підтверджений " Памелою " та " Марко Ауреліо Делеу ", у вашому випадку може працювати така версія:

$str = utf8_decode($dom->saveHTML($dom->documentElement));

Примітка

  1. Англійські символи не викликають жодних проблем при використанні saveHTML()без параметрів (адже англійські символи зберігаються як однобайтові символи в UTF-8)

  2. Проблема виникає, коли у вас є багатобайтові символи (такі як китайська, російська, арабська, іврит, ... тощо).

Я рекомендую прочитати цю статтю: http://coding.smashingmagazine.com/2012/06/06/all-about-unicode-utf8-character-sets/ . Ви зрозумієте, як працює UTF-8 і чому виникає ця проблема. Це займе у вас близько 30 хвилин, але час добре витрачений.


5
Мені довелося utf8_decode під час використання цього рішення. Дякую!
Джек М.

9
Це могло стати utf8_decode ($ dom-> saveHTML (dom-> documentElement)), щоб зберегти мої спеціальні символи. Інакше вони просто стали чимось іншим. Просто згадуючи про це, якщо це допомагає комусь іншому.
Джек М.

4
Дякую @MrJack Я також повинен був зробити те ж саме, щоб відобразити його без дивних персонажів$str = utf8_decode($dom->saveHTML($dom->documentElement));
Памела

1
utf8_decode($dom->saveHTML($dom->documentElement));зробив це ідеально для мене.
Marco Aurélio Deleu

2
Ти врятував мені життя цим. Я шукав цю відповідь КОЖНО! Дякую!
Пауло Хго

15

Переконайтеся, що реальний вихідний файл збережений як UTF-8 (Ви можете навіть спробувати нерекомендовані BOM Chars з UTF-8, щоб переконатися).

Також у випадку HTML переконайтеся, що ви оголосили правильне кодування за допомогою metaтегів:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

Якщо це CMS (як ви помітили своє запитання Joomla), можливо, вам знадобиться налаштувати відповідні налаштування для кодування.


Я розумію, що ви говорите, але у мене немає проблем із відображенням персонажів. якщо я роблю "echo $ profile;" це чудово працює. саме тоді, коли DomDocument здобуває його, він починає виходити з ладу.
Трохи А.

2
Ваша мета заважає saveHTML кодувати все вище ASCII в сутності. Я шукав рішення :)
держ.

2
Як бічна примітка, новіший <meta charset="UTF-8">тег не працює з DOMDocument.
Тейлан

10

Ви можете встановити префікс рядкового utf-8кодування, наприклад:

@$doc->loadHTML('<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $profile);

Потім ви можете продовжити код, який у вас уже є, наприклад:

$doc->saveXML()

10

Це знадобило мені час, щоб зрозуміти, але ось моя відповідь.

Перш ніж використовувати DomDocument, я б використовував file_get_contents для отримання URL-адрес, а потім обробляти їх за допомогою рядкових функцій. Мабуть, не найкращий спосіб, але швидкий. Переконавшись, що Дом був так само швидким, спершу спробував наступне:

$dom = new DomDocument('1.0', 'UTF-8');
if ($dom->loadHTMLFile($url) == false) { // read the url
    // error message
}
else {
    // process
}

Це надзвичайно не вдалося зберегти кодування UTF-8, незважаючи на належні метатеги, налаштування php та всі інші засоби захисту, запропоновані тут та в інших місцях. Ось що працює:

$dom = new DomDocument('1.0', 'UTF-8');
$str = file_get_contents($url);
if ($dom->loadHTML(mb_convert_encoding($str, 'HTML-ENTITIES', 'UTF-8')) == false) {
}

і т. д. Тепер все в порядку зі світом. Сподіваюся, це допомагає.


Я просто хотів додати до своєї відповіді вище, що ще один спосіб вирішити це, наступний, запропонований в іншому місці: if ($ dom-> loadHTML ('<? Xml encoding = "UTF-8">'. $ Str) = = помилково). Після публікації своєї відповіді я знайшов привід, коли моя перша пропозиція не вдалася, але друга спрацювала.
Сем

Працює для мене навіть без парам в DomDocument('1.0', 'UTF-8'). Але в моєму випадку завантажується лише частковий html.
JKB

5

Ви повинні подати DOMDocument версію свого HTML із заголовком, який має сенс. Так само, як HTML5.

$profile ='<?xml version="1.0" encoding="'.$_encoding.'"?>'. $html;

можливо, це гарна ідея, щоб ваш HTML був максимально дійсним, так що ви не будете виникати проблем, коли ви почнете запитувати ... навколо :-) і тримайтеся подалі від htmlentities!!!! Це необхідне туди і назад витрачаючи ресурси. тримайте свій код божевільним !!!!


5

Я використовую php 7.3.8 на манджаро, і я працював з перською мовою. Це вирішило мою проблему:

$html = 'hi</b><p>سلام<div>の家庭に、9 ☆';
$doc = new DOMDocument('1.0', 'UTF-8');
$doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
print $doc->saveHTML($doc->documentElement) . PHP_EOL . PHP_EOL;

Цю саму пораду Сем дав роками раніше на цій самій сторінці. Будь ласка, не повідомляйте про зайву інформацію.
mickmackusa

4

Мені подобається:

$dom = new \DOMDocument;
$dom->loadHTML(utf8_decode($html));
...
return  utf8_encode( $dom->saveHTML());

2
Будьте уважні, utf8_decode може втратити інформацію (замінено на a ?)
jwal

2

Використовуйте його для правильного результату

$dom = new DOMDocument();
$dom->loadHTML('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $profile);
echo $dom->saveHTML();
echo $profile;

Ця операція

mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8');

Це поганий спосіб, оскільки спеціальні символи типу & lt; , & gt; може бути у профілі $, і вони не перетворяться двічі після mb_convert_encoding. Це отвір для XSS та неправильного HTML.


1

Єдине, що працювало для мене, - це прийнята відповідь

$profile = '<p>イリノイ州シカゴにて、アイルランド系の家庭に、9</p>';
$dom = new DOMDocument();
$dom->loadHTML('<?xml encoding="utf-8" ?>' . $profile);
echo $dom->saveHTML();

ЗАРАЗ

Це спричинило нові проблеми, що <?xml encoding="utf-8" ?>стосуються виходу документа.

Тоді для мене було рішення

foreach ($doc->childNodes as $xx) {
    if ($xx instanceof \DOMProcessingInstruction) {
        $xx->parentNode->removeChild($xx);
    }
}

Деякі рішення казали мені, що для видалення xmlзаголовка я повинен був виконати

$dom->saveXML($dom->documentElement);

Для мене це не спрацювало, як для часткового документа (наприклад, документ з двома <p>тегами), лише один з <p>тегів, куди повертається.


0

Проблема полягає в тому, що додаючи параметр до функції DOMDocument :: saveHTML (), ви втрачаєте кодування. У кількох випадках вам потрібно буде уникати використання параметра та використовувати стару функцію рядка, щоб знайти те, що ви шукаєте.

Я думаю, що попередня відповідь працює для вас, але оскільки цей спосіб не працює для мене, я додаю цю відповідь, щоб допомогти ppl, який може бути у моєму випадку.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.