PHP DOMDokładanie dokumentu HTML nie poprawnie koduje UTF-8

194

Próbuję parsować trochę HTML przy użyciu DOMDocument, ale kiedy to robię, nagle tracę kodowanie (przynajmniej tak mi się wydaje).

$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);
}

Wynikiem tego kodu jest to, że otrzymuję mnóstwo znaków, które nie są japońskie. Jeśli jednak to zrobię:

echo $profile;

wyświetla się poprawnie. Próbowałem saveHTML i saveXML i żadne z nich nie wyświetla się poprawnie. Używam PHP 5.3.

Co widzę:

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

Co należy pokazać:

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

EDYCJA: Uprościłem kod do pięciu wierszy, abyś mógł go przetestować samodzielnie.

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

Oto zwracany HTML:

<div lang="ja"><p>イリノイ州シカゴã«ã¦ã€ã‚¢ã‚¤ãƒ«ãƒ©ãƒ³ãƒ‰ç³»ã®å®¶åº­ã«ã€</p></div>
<div lang="ja"><p>イリノイ州シカゴにて、アイルランド系の家庭に、</p></div>
Nieco A.
źródło
To może ci pomóc. stackoverflow.com/questions/1580543/…
frustratedtech
Dzięki. Sprawdziłem wszystkie i nic nie pomogło. Nie dostaję ????, ale jakiś inny dziwny tekst. Spróbuję go tutaj wkleić, ale nie wiem, jak witryna go wyświetli.
Nieznacznie A.
Spróbuj użyć utf8_encode
Webnet
Próbowałem bez powodzenia. Zwrócono takie same znaki jak poprzednio.
Nieznacznie A.

Odpowiedzi:

513

DOMDocument::loadHTMLpotraktuje twój ciąg znaków jako zgodny z ISO-8859-1, chyba że powiesz inaczej. Powoduje to niepoprawną interpretację ciągów UTF-8.

Jeśli Twój ciąg nie zawiera deklaracji kodowania XML, możesz ją poprzedzić, aby traktować ciąg jako UTF-8:

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

Jeśli nie wiesz, czy ciąg będzie już zawierał taką deklarację, w programie SmartDOMDocument istnieje obejście, które powinno ci pomóc:

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

To nie jest świetne obejście, ale ponieważ nie wszystkie znaki mogą być reprezentowane w ISO-8859-1 (jak te katana), jest to najbezpieczniejsza alternatywa.

cmbuckley
źródło
1
Tak, to zrobiło. Dziękuję za pomoc Próbowałem saveHTML, saveXML, nie sądziłem, że problem mógł nadejść podczas ładowania.
Nieznacznie A.
4
Wywołanie mb_convert_encoding zadziałało dla mnie, a przygotowanie deklaracji kodowania nie. Prawdopodobnie dlatego, że dokument miał już sprzeczną deklarację. Wielkie dzięki - zaoszczędziłem dużo czasu na ściganie tego.
Peter Bagnall
1
$dom->loadHTML('<?xml encoding="utf-8" ?>' . $content);naprawiłem to dla mnie w PHP7 (więc nadal jest to problem) - jest to naprawdę denerwujący problem, ponieważ zdefiniowałem utf8 w dokumencie HTML (z <meta charset="UTF-8" />), ale to nie ma efektu, wydaje się, że potrzebuje części <? xml, która jest całkowicie nieintuicyjny.
iquito,
11
Jeszcze w 2017 roku ta odpowiedź jest istotna i działała również dla mnie. Miałem bazę danych, wielobajtowy, metatag HTML i kodowanie DOM ustawione na utf8 i nadal miałem złe kodowanie podczas importowania węzła z jednego DOC do drugiego. php.net/manual/en/function.mb-convert-encoding.php był poprawką.
Louis Loudog Trottier
6
$dom->loadHTML(mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8'));działa świetnie! Dziękuję
vee
66

Problemem jest to, ze saveHTML()i saveXML(), oba z nich nie działają poprawnie w systemie Unix. Nie zapisują poprawnie znaków UTF-8, gdy są używane w Uniksie, ale działają w systemie Windows.

Obejście tego problemu jest bardzo proste:

Jeśli spróbujesz domyślnie, otrzymasz opisany błąd

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

Wszystko, co musisz zrobić, to zapisać w następujący sposób:

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

Ten wiersz kodu zapewni prawidłowe zapisanie znaków UTF-8. Jeśli używasz, użyj tego samego obejścia saveXML().


Aktualizacja

Zgodnie z sugestią „ Jack M ” w sekcji komentarzy poniżej i potwierdzoną przez „ Pamelę ” i „ Marco Aurélio Deleu ” w twoim przypadku może działać następująca odmiana:

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

Uwaga

  1. Angielskie znaki nie powodują żadnego problemu, gdy używasz saveHTML()bez parametrów (ponieważ angielskie znaki są zapisywane jako znaki jednobajtowe w UTF-8)

  2. Problem występuje, gdy masz znaki wielobajtowe (takie jak chiński, rosyjski, arabski, hebrajski itp.)

Zalecam przeczytanie tego artykułu: http://coding.smashingmagazine.com/2012/06/06/all-about-unicode-utf8-character-sets/ . Zrozumiesz, jak działa UTF-8 i dlaczego masz ten problem. Zajmie ci to około 30 minut, ale jest to czas dobrze spędzony.

Greeso
źródło
5
Podczas korzystania z tego rozwiązania musiałem utf8_decode. Dzięki!
Jack M.
9
Musiało to być utf8_decode ($ dom-> saveHTML (dom-> documentElement)), aby zachować moje znaki specjalne. W przeciwnym razie stały się po prostu czymś innym. Wystarczy wspomnieć o tym na wypadek, gdyby pomógł komuś innemu.
Jack M.
4
Dzięki @MrJack. Musiałem także zrobić to samo, aby wyświetlać bez dziwnych postaci$str = utf8_decode($dom->saveHTML($dom->documentElement));
Pamela
1
utf8_decode($dom->saveHTML($dom->documentElement));zrobiłem to idealnie dla mnie.
Marco Aurélio Deleu,
2
Uratowałeś mi dzięki temu życie. Szukałem tej odpowiedzi WSZĘDZIE! Dziękuję Ci!
Paulo Hgo
15

Upewnij się, że prawdziwy plik źródłowy jest zapisany jako UTF-8 (aby się upewnić, możesz spróbować niezalecanych znaków BOM z UTF-8).

Również w przypadku HTML upewnij się, że zadeklarowałeś prawidłowe kodowanie za pomocą metatagów:

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

Jeśli jest to CMS (ponieważ otagowałeś swoje pytanie Joomla), być może będziesz musiał skonfigurować odpowiednie ustawienia dla kodowania.

Hossein
źródło
Rozumiem, co mówisz, ale nie mam problemów z wyświetlaniem postaci. jeśli zrobię „echo $ profile;” to działa dobrze. gdy DomDocument go uchwyci, zaczyna się zawodzić.
Nieznacznie A.
2
Twoja meta uniemożliwia saveHTML kodowanie wszystkiego powyżej ASCII w byty. Rozwiązanie, którego szukałem :)
sod
2
Na marginesie, nowszy <meta charset="UTF-8">tag nie działa z DOMDocument.
Taylan
10

Możesz poprzedzić utf-8kodowanie linii wymuszające , tak jak to:

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

Następnie możesz przejść do kodu, który już masz, na przykład:

$doc->saveXML()
Ivan
źródło
10

Zajęło mi to trochę czasu, aby zrozumieć, ale oto moja odpowiedź.

Przed użyciem DomDocument chciałbym użyć file_get_contents do pobrania adresów URL, a następnie przetworzenia ich za pomocą funkcji łańcuchowych. Być może nie najlepszy sposób, ale szybki. Po przekonaniu się, że Dom był równie szybki, najpierw spróbowałem:

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

Nie udało się to spektakularnie w zachowaniu kodowania UTF-8 pomimo odpowiednich metatagów, ustawień php i wszystkich innych środków zaradczych oferowanych tutaj i gdzie indziej. Oto, co działa:

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

itp. Teraz wszystko jest w porządku ze światem. Mam nadzieję że to pomoże.

Sam
źródło
Chciałem tylko dodać do powyższej odpowiedzi, że inny sposób rozwiązania tego problemu jest następujący, sugerowany również w innym miejscu: if ($ dom-> loadHTML ('<? Xml encoding = "UTF-8">'. $ Str) = = fałsz). Po opublikowaniu mojej odpowiedzi znalazłem okazję, w której moja pierwsza sugestia zawiodła, ale druga zadziałała.
Sam
Działa dla mnie nawet bez parametrów DomDocument('1.0', 'UTF-8'). Ale w moim przypadku ładowany jest tylko częściowy HTML.
JKB
5

Musisz nakarmić DOMDocument wersję swojego HTML z sensownym nagłówkiem. Podobnie jak HTML5.

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

być może dobrym pomysłem jest, aby utrzymać swój HTML tak ważny, jak to tylko możliwe, abyś nie wpadał w problemy, gdy zaczniesz kwerendę ... w okolicy :-) i trzymaj się z dala od htmlentities!!!! Jest to konieczne marnowanie zasobów w obie strony. trzymaj swój kod szalony !!!!

Lazaros Kosmidis
źródło
5

Używam php 7.3.8 na manjaro i pracowałem z perskimi treściami. To rozwiązało mój problem:

$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;
sajed zarrinpour
źródło
Dokładnie tę samą radę dał Sam wiele lat wcześniej na tej samej stronie. Proszę nie publikować zbędnych informacji.
mickmackusa
4

Prace znalezione dla mnie:

$dom = new \DOMDocument;
$dom->loadHTML(utf8_decode($html));
...
return  utf8_encode( $dom->saveHTML());
mMo
źródło
2
Uważaj, utf8_decode może utracić informacje (zastąpione przez a ?)
jwal
2

Użyj go, aby uzyskać poprawny wynik

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

Ta operacja

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

To zły sposób, ponieważ specjalne symbole, takie jak & lt; , & gt; mogą być w profilu $ i nie będą konwertowane dwukrotnie po mb_convert_encoding. Jest to dziura dla XSS i niepoprawnego HTML.

Alexander Goncharov
źródło
1

Jedyną rzeczą, która działała dla mnie, była zaakceptowana odpowiedź

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

JEDNAK

Doprowadziło to do powstania nowych problemów związanych <?xml encoding="utf-8" ?>z otrzymaniem dokumentu.

Miałem wtedy rozwiązanie

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

Niektóre rozwiązania mówiły mi, że aby usunąć xmlnagłówek, musiałem wykonać

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

Nie działało to dla mnie jak w przypadku częściowego dokumentu (np. Dokumentu z dwoma <p>znacznikami), tylko jeden ze <p>znaczników, który został zwrócony.

Luke Madhanga
źródło
0

Problem polega na tym, że dodając parametr do funkcji DOMDocument :: saveHTML (), tracisz kodowanie. W kilku przypadkach musisz uniknąć użycia parametru i użyć starej funkcji ciągu, aby znaleźć to, czego szukasz.

Myślę, że poprzednia odpowiedź działa dla ciebie, ale ponieważ to obejście nie zadziałało dla mnie, dodaję tę odpowiedź, aby pomóc ppl, który może być w moim przypadku.

copndz
źródło
0

Może również kodować jak poniżej .... zebrane z https://davidwalsh.name/domdocument-utf8-problem

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