PHP,MySQL和XML =乱码HTML输出

时间:2011-01-13 17:46:24

标签: php xml utf-8 character-encoding

我在MySQL中有一个类型为text的字段,使用以下排序规则:utf8_general_ci

使用使用DOMDocument构建的变量填充此XML字段:

function ed_audit_node($dom, $field, $new, $old){

    //create audit_detail node
    $ad = $dom->createElement('audit_detail');

    $fn = $dom->createElement('fieldname');
    $fn->appendChild($dom->createTextNode($field));
    $ad->appendChild($fn);

    $ov = $dom->createElement('old_value');
    $ov->appendChild($dom->createTextNode($old));
    $ad->appendChild($ov);

    $nv = $dom->createElement('new_value');
    $nv->appendChild($dom->createTextNode($new));
    $ad->appendChild($nv);

    //append to document
    return $ad;
}

以下是我保存到数据库的方式($ xml来自$ dom-> saveXML()):

function ed_audit_insert($ed, $xml){
    global $visitor;

    $sql = <<<EOF
    INSERT INTO ed.audit
    (employee_id, audit_date, audit_action, audit_data, user_id) 
    VALUES (
        {$ed[emp][employee_id]}, 
        now(), 
        '{$ed[audit_action]}', 
        '{$xml}', 
        {$visitor[user_id]}
    );      
EOF;
    $req = mysql_query($sql,$ed['db']) or die(db_query_error($sql,mysql_error(),__FUNCTION__));
//snip  
}

请参阅一个较旧的,并行的,稍微相关的关于我如何创建此XML的线程: Another PHP XML parsing error: "Input is not proper UTF-8, indicate encoding!"

什么有用: - 查询数据库,选择字段并使用jQuery(.ajax())输出并填充textarea。 Firebug和textarea匹配数据库中的内容(与Toad确认)。

什么行不通: - 将数据库中的文本输出到HTML页面。此HTML页面具有内容类型ISO-8859-1,我无法更改。

以下是将其输出到屏幕的代码:

$xmlData = simplexml_load_string($d['audit_data']);

foreach ($xmlData->audit_detail as $a){
    echo "<p> straight from db = ".$a->new_value."</p>";
    echo "<p> utf8_decode() = ".utf8_decode($a->new_value)."</p>";
} 

我还为Firefox使用了charset changer扩展:尝试了ISO-8859-1,UTF-8和1252但没有成功。

如果是UTF-8,我不应该看到里面有问号的钻石(因为它的内容类型= ISO-8859-1)?如果它不是UTF-8,它是什么?

编辑#1

以下是我所做的其他测试的快照:

$xmlData = simplexml_load_string($d['audit_data']);
foreach ($xmlData->audit_detail as $a){
    echo "<p>encoding is, straight from db, using mb_detect_encoding: ".mb_detect_encoding($a->new_value)."</p>";
    echo "<p>encoding is, with utf8_decode, using mb_detect_encoding: ".mb_detect_encoding(utf8_decode($a->new_value))."</p>";
    echo "<hr/>";
    echo "<p> straight from db = <pre>".$a->new_value."</pre></p>";
    echo "<p> utf8_decode() = <pre>".utf8_decode($a->new_value)."</pre></p>";
    echo "<hr/>";
    $iso88591_2 = iconv('UTF-8', 'ISO-8859-1', $a->new_value);
    $iso88591_3 = mb_convert_encoding($a->new_value, 'ISO-8859-1', 'UTF-8');
    echo "<p> iconv() = ".$iso88591_2."</p>";
    echo "<p> mb_convert_encoding() = ".$iso88591_3."</p>";
}

编辑#2

我添加了FF专有标签xmp。

代码:

$xmlData = simplexml_load_string($d['audit_data']);

foreach ($xmlData->audit_detail as $a){
    echo "<p>encoding is, straight from db, using mb_detect_encoding: ".mb_detect_encoding($a->new_value)."</p>";
    echo "<p>encoding is, with utf8_decode, using mb_detect_encoding: ".mb_detect_encoding(utf8_decode($a->new_value))."</p>";
    echo "<hr/>";
    echo "<p> straight from db = <pre>".$a->new_value."</pre></p>";
    echo "<p> utf8_decode() = <pre>".utf8_decode($a->new_value)."</pre></p>";
    echo "<hr/>";
    $iso88591_2 = iconv('UTF-8', 'ISO-8859-1', $a->new_value);
    $iso88591_3 = mb_convert_encoding($a->new_value, 'ISO-8859-1', 'UTF-8');
    echo "<p> iconv() = ".$iso88591_2."</p>";
    echo "<p> mb_convert_encoding() = ".$iso88591_3."</p>";
    echo "<hr/>";
    echo "<p>straight from db, using &lt;xmp&gt;  = <xmp>".$a->new_value."</xmp></p>";
    echo "<p>utf8_decode(), using &lt;xmp&gt; = <xmp>".utf8_decode($a->new_value)."</xmp></p>";

}

以下是该页面中的一些元标记:

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="dc.language" scheme="ISO639-2/T" content="eng" />

IMO,最后一个元标记没有影响。

编辑#3

源代码:

<p>encoding is, straight from db, using mb_detect_encoding: UTF-8</p><p>encoding is, with utf8_decode, using mb_detect_encoding: ASCII</p><hr/><p> straight from db = <pre>Ro马eç ³é ¥n franê¡©s</pre></p><p> utf8_decode() = <pre>Ro?e??n fran?s</pre></p><hr/><p> iconv() = Ro</p><p> mb_convert_encoding() = Ro?e??n fran?s</p><hr/><p>straight from db, using &lt;xmp&gt;  = <xmp>Ro马eç ³é ¥n franê¡©s</xmp></p><p>utf8_decode(), using &lt;xmp&gt; = <xmp>Ro?e??n fran?s</xmp></p>

编辑#4

这是进入db的SQL语句:

INSERT INTO ed.audit
    (employee_id, audit_date, audit_action, audit_data, user_id) 
    VALUES (
        75, 
        now(), 
        'u', 
        '<?xml version="1.0"?>
<audit><audit_detail><fieldname>role_fra</fieldname><old_value>aRo&#x9A6C;e&#x7833;&#x9825;n fran&#xA869;s</old_value><new_value>bRo&#x9A6C;e&#x7833;&#x9825;n fran&#xA869;s</new_value></audit_detail></audit>
', 
        333
    );

!请注意,此XML中的文本不一定与上面提供的屏幕截图相匹配。

编辑#5

这是我的新函数,它将CDATA标记包装在old_value和new_value节点的值周围:

function ed_audit_node($dom, $field, $new, $old){

    //create audit_detail node
    $ad = $dom->createElement('audit_detail');

    $fn = $dom->createElement('fieldname');
    $fn->appendChild($dom->createTextNode($field));
    $ad->appendChild($fn);

    $ov = $dom->createElement('old_value');

    $ov->appendChild($dom->createCDATASection($old));
    $ad->appendChild($ov);

    $nv = $dom->createElement('new_value');
    $nv->appendChild($dom->createCDATASection($new));
    $ad->appendChild($nv);

    //append to document
    return $ad;
}

我还将编码添加到XML文档中:

$dom = new DomDocument('1.0', 'UTF-8');

这是我的新simpleXML调用:

$xmlData = simplexml_load_string($d['audit_data'], "SimpleXMLElement", LIBXML_NOENT | LIBXML_NOCDATA);

我也在Toad中看到了CDATA标签。但是,我仍然收到错误:

Warning: simplexml_load_string() [function.simplexml-load-string]: Entity: line 2: parser error : Input is not proper UTF-8, indicate encoding ! Bytes: 0xE9 0xE9 0x6C 0x65 in <snip>

编辑#6

我刚注意到jQuery调用在CDATA中返回正确的重音字符。

1 个答案:

答案 0 :(得分:1)

从技术上讲,您的字符串是UTF8,但HTML编码的字符(由浏览器呈现时)不是UTF8。所以&#xa869;是一个有效的UTF8字符串,但从Web浏览器呈现到屏幕上的字符不是有效的UTF8。

我还会将你的回声包装到屏幕上(例子中的最后两行),如下所示:

echo "<p>straight from db = <xmp>".$a->new_value."</xmp></p>";
echo "<p>utf8_decode() = <xmp>".utf8_decode($a->new_value)."</xmp></p>";

这将清楚地显示我上面提出的观点。

修改

问题实际上是针对PHP的simplexml_load_string()中无法控制的未记录的“功能”。它会自动将所有字符从其XML实体表单中转换为实际的字符形式。避免这种情况的唯一方法是使用simplexml_load_string(),如下所示:

 $data = simplexml_load_string(
      '<?xml version="1.0" encoding="utf-8"?> 
           <audit>
                <audit_detail>
                     <fieldname>role_fra</fieldname>
                     <old_value><![CDATA[aRo&#x9A6C;e&#x7833;&#x9825;n fran&#xA869;s]]></old_value>
                     <new_value><![CDATA[bRo&#x9A6C;e&#x7833;&#x9825;n fran&#xA869;s]]></new_value>
                </audit_detail>
           </audit>', 
      "SimpleXMLElement", 
      LIBXML_NOENT | LIBXML_NOCDATA
 );
 print "<PRE>";
 print_r($data);
 exit;

您必须将元素包装在<![CDATA[]]>标记中,然后将LIBXML_NOCDATA选项传递给xml解析器。这将强制<![CDATA[]]>标记中的内容转换为String类型,PHP可以正确处理SimpleXMLObject之外的内容。