将SQL_Latin1_General_CP1_CI_AS编码转换为UTF-8

15

我正在使用DomDocument和PHP生成一个XML文件,需要处理亚洲字符。我使用pdo_mssql驱动程序从MSSQL2008服务器中拉取数据,并在XML属性值上应用utf8_encode()。只要没有特殊字符,一切都正常运作。

服务器是MS SQL Server 2008 SP3

数据库、表和列的排序规则都是SQL_Latin1_General_CP1_CI_AS

我正在使用PHP 5.2.17

这是我的PDO对象:

$pdo = new PDO("mssql:host=MyServer,1433;dbname=MyDatabase", user123, password123);

我的查询是一个基本的SELECT。

我知道将特殊字符存储到SQL_Latin1_General_CP1_CI_AS列中并不好,但最好能够在不更改它的情况下使其正常工作,因为其他非PHP程序已经使用该列并且可以正常工作。在SQL Server Management Studio中,我可以正确地看到亚洲字符。

考虑到上述所有细节,我应该如何处理数据?


你尝试过使用utf8_encode()吗?根据手册:将ISO-8859-1字符串编码为UTF-8 - Pierre-Olivier
当然,这就是我目前正在做的事情,但是亚洲字符显示为“?”。即使我只运行SELECT然后将数据放入文件中(无论是否进行utf8_encode),亚洲字符最终仍会在文件中显示为“?”。 - SGr
真让我惊讶的是,您居然能够在LATIN1中编码亚洲字符。LATIN1只用于编码欧洲字符... - Pierre-Olivier
7个回答

25

我找到了解决方法,希望这对某些人有所帮助。

首先,SQL_Latin1_General_CP1_CI_AS是CP-1252和UTF-8的奇怪组合。 基本字符是CP-1252,所以我只需要用UTF-8就能解决问题。亚洲和其他UTF-8字符编码为2个字节,而php pdo_mssql驱动似乎不喜欢长度可变的字符,因此它似乎会对varchar进行转换(而不是nvarchar),然后所有2字节字符都变成问号(“?”)。

我通过将其转换为二进制并使用php重新构建文本来修复它:

SELECT CAST(MY_COLUMN AS VARBINARY(MAX)) FROM MY_TABLE;
在 PHP 中:
//Binary to hexadecimal
$hex = bin2hex($bin);

//And then from hex to string
$str = "";
for ($i=0;$i<strlen($hex) -1;$i+=2)
{
    $str .= chr(hexdec($hex[$i].$hex[$i+1]));
}
//And then from UCS-2LE/SQL_Latin1_General_CP1_CI_AS (that's the column format in the DB) to UTF-8
$str = iconv('UCS-2LE', 'UTF-8', $str);

1
但是...但是...那太疯狂了。真的没有更好的方法吗?而不是使用mb_convert_encoding函数。 - icc97

2

我知道这篇文章有点旧了,但是对我来说唯一有效的方法是 iconv("CP850", "UTF-8//TRANSLIT", $var); 我也遇到了 SQL_Latin1_General_CP1_CI_AI 的问题,也许对 SQL_Latin1_General_CP1_CI_AS 也适用。


2
您可以尝试这样做:
header("Content-Type: text/html; charset=utf-8");
$dbhost   = "hostname";
$db       = "database";
$query = "SELECT *
    FROM Estado
    ORDER BY Nome";
$conn = new PDO( "sqlsrv:server=$dbhost ; Database = $db", "", "" );
$stmt = $conn->prepare( $query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED, PDO::SQLSRV_ENCODING_SYSTEM) );
$stmt->execute();
while ( $row = $stmt->fetch( PDO::FETCH_ASSOC ) )
{
// CP1252 == code page Latin1
print iconv("CP1252", "ISO-8859-1", "$row[Nome] <br>");
}

1
这个对我有用!谢谢:print iconv("CP1252", "UTF-8", "$row[Nome] <br>"); - joelpittet

1

不需要太复杂的操作。排序规则 SQL_Latin1_General_CP1_CI_AS 的字符编码是:Windows-1252

这对我来说非常完美:$str = mb_convert_encoding($str, 'UTF-8', 'Windows-1252');


1

对我来说,以上解决方案都不是直接的解决方案——尽管我使用了以上解决方案的部分内容。这对于我在越南字母表中起作用。如果您阅读此帖子,而以上解决方案都无法解决您的问题,请尝试:

    $req = "SELECT CAST(MY_COLUMN as VARBINARY(MAX)) as MY_COLUMN FROM MY_TABLE"; 
    $stmt = $conn->prepare($req);
    $stmt->execute();
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $str = pack("H*",$row['MY_COLUMN']);
        $str = mb_convert_encoding($z, 'HTML-ENTITIES','UCS-2LE');
        print_r($str);
    }

还有一个小奖励——我必须对这些数据进行json_encode,但是(呃),得到的是HTML代码而不是特殊字符。要解决这个问题,只需在使用json_encode发送字符串之前使用html_entity_decode()。


这是我在处理韩语和俄语时唯一有效的解决方案。 - Nilebac

0

感谢 @SGr 的回答。
我找到了更好的方法来做这件事:

SELECT CAST(CAST(MY_COLUMN AS VARBINARY(MAX)) AS VARCHAR(MAX)) as MY_COLUMN FROM MY_TABLE;
并且也可以尝试使用:
SELECT CAST(MY_COLUMN AS VARBINARY(MAX)) as MY_COLUMN FROM MY_TABLE;

在 PHP 中,你只需要将它转换为 UTF-8 即可:

$string = iconv('UCS-2LE', 'UTF-8', $row['MY_COLUMN']);


0

默认情况下,PDO 使用 PDO::SQLSRV_ENCODING_UTF8 用于发送/接收数据。

如果您当前的排序规则是 LATIN1,您是否尝试过指定 PDO::SQLSRV_ENCODING_SYSTEM 来让 PDO 知道您想要使用当前系统编码而不是 UTF-8

您甚至可以使用 PDO::SQLSRV_ENCODING_BINARY 来以二进制形式返回数据(在传输数据时不进行编码或转换)。这样,您就可以在自己的程序中处理字符编码。

更多文档请参见:http://ca3.php.net/manual/en/ref.pdo-sqlsrv.php


我在 SQL Server 2008 上无法使用任何 PDO::SQLSRV_* 参数。我得到了一些错误,说它是未定义的或类似的东西。 - Jo Smo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接