O DOMDocument do PHP loadHTML não codifica UTF-8 corretamente

194

Estou tentando analisar um pouco de HTML usando DOMDocument, mas quando o faço, de repente perco minha codificação (pelo menos é assim que me parece).

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

O resultado desse código é que recebo vários caracteres que não são japoneses. No entanto, se eu fizer:

echo $profile;

é exibido corretamente. Eu tentei saveHTML e saveXML, e nenhum é exibido corretamente. Estou usando o PHP 5.3.

O que eu vejo:

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

O que deve ser mostrado:

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

Edição: simplifiquei o código para cinco linhas, para que você possa testá-lo.

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

Aqui está o html retornado:

<div lang="ja"><p>イリノイ州シカゴã«ã¦ã€ã‚¢ã‚¤ãƒ«ãƒ©ãƒ³ãƒ‰ç³»ã®å®¶åº­ã«ã€</p></div>
<div lang="ja"><p>イリノイ州シカゴにて、アイルランド系の家庭に、</p></div>
Um pouco A.
fonte
Isso pode ajudá-lo. stackoverflow.com/questions/1580543/…
frustratedtech
Obrigado. Eu verifiquei tudo isso e nada ajudou. Eu não entendo ????, mas algum outro texto estranho. Vou tentar colá-lo aqui, mas não sei como o site o exibirá.
A. ligeiramente
Tente usar utf8_encode
Webnet
Tentei sem sucesso. Retornou os mesmos caracteres de antes.
Ligeiramente A.

Respostas:

513

DOMDocument::loadHTMLtratará sua corda como estando na ISO-8859-1, a menos que você diga o contrário. Isso resulta em seqüências de caracteres UTF-8 sendo interpretadas incorretamente.

Se a sua sequência não contiver uma declaração de codificação XML, você pode acrescentar uma para fazer com que a sequência seja tratada como UTF-8:

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

Se você não pode saber se a sequência já conterá essa declaração, há uma solução alternativa no SmartDOMDocument que deve ajudá-lo:

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

Essa não é uma ótima solução, mas como nem todos os caracteres podem ser representados na ISO-8859-1 (como essas katana), é a alternativa mais segura.

cmbuckley
fonte
1
Sim, conseguiu. Obrigado pela ajuda. Tentei saveHTML, saveXML, não achei que o problema estivesse ocorrendo durante o carregamento.
A. A.
4
A chamada mb_convert_encoding funcionou para mim, enquanto o prefixo da declaração de codificação não funcionou. Provavelmente porque o documento já tinha uma declaração conflitante. Muito obrigado - poupou-me muito tempo perseguindo isso.
Peter Bagnall
1
$dom->loadHTML('<?xml encoding="utf-8" ?>' . $content);corrigi-o para mim no PHP7 (por isso ainda é um problema) - esse é um problema realmente irritante, porque eu defini utf8 no documento HTML (com <meta charset="UTF-8" />) mas que não tem efeito, parece precisar da parte <? xml, que é totalmente não intuitivo.
Iquito
11
Ainda em 2017, essa resposta é relevante e funcionou para mim também. Eu tinha meu metatag de banco de dados, multibyte, html e DOM, todos definidos como utf8 e ainda tinha codificação incorreta ao importar o nó de um DOC para outro. php.net/manual/en/function.mb-convert-encoding.php foi a correção.
Louis Loudog Trottier
6
$dom->loadHTML(mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8'));funciona bem! Obrigado,
vee
66

O problema está com saveHTML()e saveXML(), ambos não funcionam corretamente no Unix. Eles não salvam caracteres UTF-8 corretamente quando usados ​​no Unix, mas funcionam no Windows.

A solução alternativa é muito simples:

Se você tentar o padrão, receberá o erro que descreveu

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

Tudo o que você precisa fazer é salvar da seguinte maneira:

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

Essa linha de código fará com que seus caracteres UTF-8 sejam salvos corretamente. Use a mesma solução alternativa se você estiver usando saveXML().


Atualizar

Conforme sugerido por " Jack M " na seção de comentários abaixo e verificado por " Pamela " e " Marco Aurélio Deleu ", a seguinte variação pode funcionar no seu caso:

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

Nota

  1. Caracteres em inglês não causam nenhum problema quando você usa saveHTML()sem parâmetros (porque os caracteres em inglês são salvos como caracteres de byte único em UTF-8)

  2. O problema ocorre quando você possui caracteres de vários bytes (como chinês, russo, árabe, hebraico, etc.)

Eu recomendo a leitura deste artigo: http://coding.smashingmagazine.com/2012/06/06/all-about-unicode-utf8-character-sets/ . Você entenderá como o UTF-8 funciona e por que você tem esse problema. Você levará cerca de 30 minutos, mas é um tempo bem gasto.

Greeso
fonte
5
Eu tive que utf8_decode enquanto usava esta solução. Obrigado!
Jack M.
9
Isso tinha que se tornar utf8_decode ($ dom-> saveHTML (dom-> documentElement)) para preservar meus caracteres especiais. Caso contrário, eles apenas se tornaram outra coisa. Apenas mencioná-lo no caso de ajudar alguém.
Jack M.
4
Obrigado @MrJack. Eu também tive que fazer o mesmo para torná-lo exibir sem os caracteres estranhos$str = utf8_decode($dom->saveHTML($dom->documentElement));
Pamela
1
utf8_decode($dom->saveHTML($dom->documentElement));fez perfeitamente para mim.
Marco Aurélio Deleu 20/10
2
Você salvou minha vida com isso. Procurei esta resposta EM TODA PARTE! Obrigado!
Paulo Hgo 28/03
15

Verifique se o arquivo de origem real está salvo como UTF-8 (você pode tentar os BOM Chars não recomendados com UTF-8 para ter certeza).

Também no caso de HTML, verifique se você declarou a codificação correta usando metatags:

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

Se for um CMS (como você marcou sua pergunta no Joomla), pode ser necessário definir as configurações apropriadas para a codificação.

Hossein
fonte
Entendo o que você está dizendo, mas não tenho problemas para exibir os personagens. se eu fizer "echo $ profile;" Funciona bem. é quando o DomDocument se apega a ele que começa a falhar.
A. ligeiramente
2
Sua meta impede que o saveHTML codifique tudo acima do ASCII em entidades. A solução que eu estava procurando :)
sod
2
Como uma observação lateral, a <meta charset="UTF-8">tag mais recente não funciona com DOMDocument.
Taylan
10

Você pode prefixar uma linha que reforça a utf-8codificação, assim:

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

E você pode continuar com o código que já possui, como:

$doc->saveXML()
Ivan
fonte
10

Demorei um pouco para descobrir, mas aqui está a minha resposta.

Antes de usar o DomDocument, usaria file_get_contents para recuperar URLs e depois processá-los com funções de string. Talvez não seja o melhor, mas rápido. Depois de me convencer de que Dom era tão rápido, tentei o seguinte:

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

Isso falhou espetacularmente na preservação da codificação UTF-8, apesar das metatags apropriadas, configurações de php e todo o restante dos remédios oferecidos aqui e em outros lugares. Aqui está o que funciona:

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

etc. Agora está tudo certo com o mundo. Espero que isto ajude.

Sam
fonte
Só queria acrescentar à minha resposta acima que outra maneira de resolver isso é a seguinte, sugerida em outro lugar também: if ($ dom-> loadHTML ('<? Xml encoding = "UTF-8">'. $ Str) = = false). Depois de postar minha resposta, encontrei uma ocasião em que minha primeira sugestão falhou, mas a segunda funcionou.
Sam
Funciona para mim mesmo sem os parâmetros DomDocument('1.0', 'UTF-8'). Mas no meu caso, apenas html parcial é carregado.
JKB 17/06
5

Você deve alimentar o DOMDocument com uma versão do seu HTML com um cabeçalho que faça sentido. Assim como HTML5.

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

talvez seja uma boa ideia manter seu html o mais válido possível, para que você não entre em problemas quando iniciar a consulta ... por volta de :-) e fique longe htmlentities!!!! Isso é um necessário e para trás desperdiçando recursos. mantenha seu código insano !!!!

Lazaros Kosmidis
fonte
5

Estou usando o php 7.3.8 em um manjaro e estava trabalhando com conteúdo em persa. Isso resolveu meu problema:

$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
fonte
Exatamente o mesmo conselho foi dado por Sam anos antes nesta mesma página. Não publique informações redundantes.
mickmackusa 13/06
4

Funciona bem para mim:

$dom = new \DOMDocument;
$dom->loadHTML(utf8_decode($html));
...
return  utf8_encode( $dom->saveHTML());
mMo
fonte
2
Tenha cuidado, utf8_decode poderá perder informações (substituída por uma ?)
jwal
2

Use-o para obter o resultado correto

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

Esta operação

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

É ruim, porque símbolos especiais como & lt; , & gt; pode estar no perfil $ e eles não serão convertidos duas vezes após mb_convert_encoding. É o buraco para XSS e HTML incorreto.

Alexander Goncharov
fonte
1

A única coisa que funcionou para mim foi a resposta aceita de

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

CONTUDO

Isso trouxe novas questões, de ter <?xml encoding="utf-8" ?>na saída do documento.

A solução para mim foi então fazer

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

Algumas soluções me disseram que, para remover o xmlcabeçalho, eu precisava executar

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

Isso não funcionou para mim como para um documento parcial (por exemplo, um documento com duas <p>tags), apenas uma das <p>tags em que foi devolvida.

Luke Madhanga
fonte
0

O problema é que, quando você adiciona parâmetro à função DOMDocument :: saveHTML (), perde a codificação. Em alguns casos, você precisará evitar o uso do parâmetro e usar a função de string antiga para encontrar o que está procurando.

Acho que a resposta anterior funciona para você, mas como essa solução alternativa não funcionou para mim, estou adicionando essa resposta para ajudar as pessoas que podem estar no meu caso.

copndz
fonte
0

Também pode codificar como abaixo .... reunidos em 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
fonte