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>


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.


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.


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.


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()

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.


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 !!!!


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;

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());

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.


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.


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.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.