2012-07-03 113 views
50

網絡服務器以utf-8編碼提供響應,所有文件都以utf-8編碼保存,而且我所瞭解的所有設置都已設置爲utf-8編碼。PHP DomDocument無法處理utf-8字符(☆)

這裏有一個快速程序,以測試輸出工作:

<?php 
$html = <<<HTML 
<!doctype html> 
<html> 
<head> 
    <meta charset="utf-8"> 
    <title>Test!</title> 
</head> 
<body> 
    <h1>☆ Hello ☆ World ☆</h1> 
</body> 
</html> 
HTML; 

$dom = new DomDocument("1.0", "utf-8"); 
$dom->loadHTML($html); 

header("Content-Type: text/html; charset=utf-8"); 
echo($dom->saveHTML()); 

程序的輸出是:

<!DOCTYPE html> 
<html><head><meta charset="utf-8"><title>Test!</title></head><body> 
    <h1>&acirc;&#152;&#134; Hello &acirc;&#152;&#134; World &acirc;&#152;&#134;</h1> 
</body></html> 

這使得爲:

â~†你好â 〜†Worldâ


我會做什麼錯?我必須告訴DomDocument如何正確處理utf-8?

+0

感謝養育問題,類似的一個是:如何保持中國或其他外國語言,因爲它們將它們轉換爲代碼而不是?](HTTP://計算器.com/q/10237238/367456)但是你可能會認爲這是一種黑客行爲。 – hakre

+0

相關:[PHP請求#47875 - 無法設置HTML輸入編碼](https://bugs.php.net/bug.php?id=47875) – hakre

+1

奇怪的是:php文檔說: DOM擴展使用UTF-8編碼。使用utf8_encode()和utf8_decode()來處理ISO-8859-1編碼中的文本或其他編碼中的Iconv。' 請參閱:http://www.php.net/manual/en/intro.dom.php – jens

回答

107

DOMDocument::loadHTML()期望一個HTML字符串。

HTML使用ISO-8859-1編碼(ISO拉丁字母編號1)作爲默認規格。這是因爲更長,請參閱6.1. The HTML Document Character Set。實際上,這更多的是在普通網頁瀏覽器中對Windows-1252的默認支持。

因爲PHP的DOMDocument基於libxml並且帶有專爲HTML 4.0設計的HTMLparser,所以我回頭指出了這一點。

我想說假設你可以加載一個ISO-8859-1編碼的字符串是安全的。

您的字符串是UTF-8編碼的。把所有高於127/h7F的字符變成HTML Entities,你就沒事了。如果你不想這樣做你自己,那是什麼mb_convert_encodingHTML-ENTITIES目標編碼的作用:

  • 已命名實體的人物,會得到一個名爲entitiy。 € -> &euro;
  • 其他人獲得他們的數字(十進制)實體,例如, ☆ -> &#9734;

下面是一個代碼示例,使進步多一點可見用回調函數:

$html = preg_replace_callback('/[\x{80}-\x{10FFFF}]/u', function($match) { 
    list($utf8) = $match; 
    $entity = mb_convert_encoding($utf8, 'HTML-ENTITIES', 'UTF-8'); 
    printf("%s -> %s\n", $utf8, $entity); 
    return $entity; 
}, $html); 

爲您的字符串此示範輸出:

☆ -> &#9734; 
☆ -> &#9734; 
☆ -> &#9734; 

無論如何,這只是爲了更深入地觀察你的字符串。你想要它可以轉換成編碼loadHTML可以處理。這可以通過US-ASCII所有外轉換成HTML實體來完成:

$us_ascii = mb_convert_encoding($utf_8, 'HTML-ENTITIES', 'UTF-8'); 

要小心,你的投入實際上是UTF-8編碼。如果您甚至有混合編碼(可能會發生一些輸入),mb_convert_encoding只能處理每個字符串的一種編碼。我已經在上面概述瞭如何在正則表達式的幫助下更具體地進行字符串替換,所以我現在留下更多細節。

另一種選擇是提示的編碼。這可以在你的情況下,通過修改文件並添加

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

這是一個Content-Type的指定字符集來完成。對於不能通過網絡服務器訪問的HTML字符串(例如,保存在磁盤上或在您的示例中保存在字符串中),這也是最佳做法。 Web服務器通常設置爲響應頭。

如果你不小心放錯位置的警告,你可以將其添加在前面的字符串:

$dom = new DomDocument(); 
$dom->loadHTML('<meta http-equiv="content-type" content="text/html; charset=utf-8">'.$html); 

%的HTML 2.0規範,元素只能出現在文檔的<head>節,會自動放置在那裏。這也是發生在這裏的事情。輸出(漂亮地打印):

<!DOCTYPE html> 
<html> 
    <head> 
    <meta http-equiv="content-type" content="text/html; charset=utf-8"> 
    <meta charset="utf-8"> 
    <title>Test!</title> 
    </head> 
    <body> 
    <h1>☆ Hello ☆ World ☆</h1>  
    </body> 
</html> 
+2

@hakre:那很完美!你解決了我的嚴重問題,現在我沒有頭痛! – Aliweb

+1

+1很好的答案,但是你推薦哪種方法 - 使用'mb_convert_encoding()'或者將元標籤加入到'loadHTML()'中? – Nate

+1

@Nate:我會說這取決於。我通常不推薦'mb_convert_encoding()',但對於這種情況,我做某事。但是,這是個人偏好的細節。它依然取決於你想在自己的步驟中進行轉換,還是隻想將其轉換爲將元元素泄漏到文檔中的'DOOMDocument :: loadHTML()'。我不知道如果這個元素已經存在會發生什麼。我從來沒有測試過保存點,但通常是「正常工作」(tm)。答案中的不同方式更多地用於解釋。 – hakre

12
<?php 
    header("Content-type: text/html; charset=utf-8"); 
    $html = <<<HTML 
<!doctype html> 
<html> 
<head> 
    <meta charset="utf-8"> 
    <title>Test!</title> 
</head> 
<body> 
    <h1>☆ Hello ☆ World ☆</h1> 
</body> 
</html> 
HTML; 

    $html = mb_convert_encoding($html, 'HTML-ENTITIES', "UTF-8"); 
    $dom = new DomDocument("1.0", "utf-8"); 
    $dom->loadHTML($html); 

    header("Content-Type: text/html; charset=utf-8"); 
    echo($dom->saveHTML()); 

輸出:

<!DOCTYPE html> 
<html><head><meta charset="utf-8"><title>Test!</title></head><body> 
    <h1>&#9734; Hello &#9734; World &#9734;</h1> 
</body></html> 
+1

@powtac:這些變體實際上並不需要'header'行。所有字符都不是我們的一部分 - ascii在這裏是實體。地球上的任何瀏覽器都將始終正確顯示此內容,除非您指定了不共享us-ascii的(錯誤)編碼。但只是注意到,這也沒有錯。 – hakre

15

還有爲更快速地解決,在加載DOM文檔HTML文檔後,您只需設置(或更好說復位)原始編碼。下面是一個示例代碼:

$dom = new DOMDocument(); 
$dom->loadHTML('<?xml encoding="UTF-8">' . $html); 

foreach ($dom->childNodes as $item) 
    if ($item->nodeType == XML_PI_NODE) 
     $dom->removeChild($item); 
$dom->encoding = 'UTF-8'; // reset original encoding 
+0

這比hakre的版本添加meta標籤更好,因爲從html中添加元刪除類標籤 –

+4

嗯,這個答案就像一個déjà-vu - http://stackoverflow.com/a/10834989/367456 – hakre

+0

嗯,可能是..我有一個txt中的代碼與一堆有用的片段。我沒有聲稱這是一些原始的東西,即使這是一些非常標準的DOMDocument類的使用。 – DeZeA