PHP cp1252/windows-1252 转换为 UTF-8

5
我正在尝试将数据库从Latin1转换为UTF-8。不幸的是,我无法进行巨量的单次切换,因为应用程序需要保持在线并且我们有700GB的数据库需要转换。
因此,我正在尝试利用一些mysql技巧来将表格转换为UTF-8,但不是数据。我希望能够实时读取、转换和替换数据。(即时转换)
我们的php应用程序当前使用所有默认设置,因此它使用latin1字符集连接到mysql,并且会丢弃以latin1编码的UTF-8数据。当您使用latin1查看数据时,UTF-8字符将显示为预期。当您使用UTF-8查看数据时,会出现混乱的情况。
因此,我建议强制将mysql字符集设置为UTF-8,然后必要时对数据进行即时转换。现在,由于cp1252 / windows-1252是UTF-8的子集,因此检测cp1252 / windows-1252编码并进行转换并不容易(据我所见)。
我编写了以下代码,以检测cp1252 / windows-1252编码并根据需要进行转换。它还应该检测正确编码的UTF-8字符并不执行任何操作。
$a = 'Card☃'; //cp1252 encoded
$a_test = '☃'.$a; //add known UTF8 character
$c = mb_convert_encoding($a_test, 'cp1252', 'UTF-8');
// attempt to detect known utf8 character after conversion
if (mb_strpos($c, '☃') === false) {
    // not found, original string was not cp1252 encoded, so print
    var_dump($a);
} else {
    // found, original string was cp1252 encoded, remove test character and print
    // This case runs
    $c = mb_strcut($c, 1);
    var_dump($c);
}

$a = 'COD☃'; //proper UTF8 encoded
$a_test = '☃'.$a; //add known UTF8 character
$c = mb_convert_encoding($a_test, 'cp1252', 'UTF-8');
// attempt to detect known utf8 character after conversion
if (mb_strpos($c, '☃') === false) {
    // not found, original string was not cp1252 encoded, so print
    // This case runs
    var_dump($a);
} else {
    // found, original string was cp1252 encoded, remove test character and print
    $c = mb_strcut($c, 1);
    var_dump($c);
}

运行此代码的输出结果为:
string 'Card☃' (length=7)
string 'COD☃' (length=6)

我知道在所有从数据库中提取的字符串上运行这个操作会产生性能影响,尚未测量出来,但是如果我可以在完全切换之前进行JIT转换,那么对我来说这是值得的。

有没有人对如何优化此过程有任何指导?


嘿@rnavarro,如果我的回答解决了你的问题,你能否接受它呢? - NobleUplift
1个回答

21

首先,Windows-1252 不是 UTF-8 的子集。你可以认为 ASCII 是 UTF-8 的子集,但那通常更多是一场意识形态的辩论。

其次,无法处理同时包含 CP1252 和 UTF-8 "字符" 的字符串(对于 CP1252 实际上是字节,而对于 Unicode 则是代码点)。你要么尝试将其作为 CP1252 读取,并将所有 Unicode 字符视为单个字节,或者将其作为 UTF-8 读取并裁剪掉任何无效的字节序列(或者如果 CP1252 字符与 Unicode 代码点匹配,则创建随机字符)。您并没有使用$c = mb_strcut($c, 1);删除测试字符,而是删除了由 mb_convert_encoding 创建的问号,因为它无法将该 Unicode 字符转换为 CP1252 字符。

第三点,您应该永远不要先转换字符串,然后在事后尝试确定编码。在转换第二个测试字符串之后,它变成了?COD?。没有理由检查其中是否存在 Unicode 字符,因为您已将其转换为 CP1252。其中不能有 Unicode 字符。作为程序员,您必须知道输出内容是什么。

唯一的解决方案是检查字符串是否为 CP1252,将有问题的字符转换为占位符,然后将该字符串转换为 Unicode:

function convert_cp1252_to_utf8($input, $default = '', $replace = array()) {
    if ($input === null || $input == '') {
        return $default;
    }

    // https://en.wikipedia.org/wiki/UTF-8
    // https://en.wikipedia.org/wiki/ISO/IEC_8859-1
    // https://en.wikipedia.org/wiki/Windows-1252
    // http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT
    $encoding = mb_detect_encoding($input, array('Windows-1252', 'ISO-8859-1'), true);
    if ($encoding == 'ISO-8859-1' || $encoding == 'Windows-1252') {
        /*
         * Use the search/replace arrays if a character needs to be replaced with
         * something other than its Unicode equivalent.
         */ 

        /*$replace = array(
            128 => "€",      // http://www.fileformat.info/info/unicode/char/20AC/index.htm EURO SIGN
            129 => "",              // UNDEFINED
            130 => "‚",      // http://www.fileformat.info/info/unicode/char/201A/index.htm SINGLE LOW-9 QUOTATION MARK
            131 => "ƒ",      // http://www.fileformat.info/info/unicode/char/0192/index.htm LATIN SMALL LETTER F WITH HOOK
            132 => "„",      // http://www.fileformat.info/info/unicode/char/201e/index.htm DOUBLE LOW-9 QUOTATION MARK
            133 => "…",      // http://www.fileformat.info/info/unicode/char/2026/index.htm HORIZONTAL ELLIPSIS
            134 => "†",      // http://www.fileformat.info/info/unicode/char/2020/index.htm DAGGER
            135 => "‡",      // http://www.fileformat.info/info/unicode/char/2021/index.htm DOUBLE DAGGER
            136 => "ˆ",      // http://www.fileformat.info/info/unicode/char/02c6/index.htm MODIFIER LETTER CIRCUMFLEX ACCENT
            137 => "‰",      // http://www.fileformat.info/info/unicode/char/2030/index.htm PER MILLE SIGN
            138 => "Š",      // http://www.fileformat.info/info/unicode/char/0160/index.htm LATIN CAPITAL LETTER S WITH CARON
            139 => "‹",      // http://www.fileformat.info/info/unicode/char/2039/index.htm SINGLE LEFT-POINTING ANGLE QUOTATION MARK
            140 => "Œ",      // http://www.fileformat.info/info/unicode/char/0152/index.htm LATIN CAPITAL LIGATURE OE
            141 => "",              // UNDEFINED
            142 => "Ž",      // http://www.fileformat.info/info/unicode/char/017d/index.htm LATIN CAPITAL LETTER Z WITH CARON 
            143 => "",              // UNDEFINED
            144 => "",              // UNDEFINED
            145 => "‘",      // http://www.fileformat.info/info/unicode/char/2018/index.htm LEFT SINGLE QUOTATION MARK 
            146 => "’",      // http://www.fileformat.info/info/unicode/char/2019/index.htm RIGHT SINGLE QUOTATION MARK
            147 => "“",      // http://www.fileformat.info/info/unicode/char/201c/index.htm LEFT DOUBLE QUOTATION MARK
            148 => "”",      // http://www.fileformat.info/info/unicode/char/201d/index.htm RIGHT DOUBLE QUOTATION MARK
            149 => "•",      // http://www.fileformat.info/info/unicode/char/2022/index.htm BULLET
            150 => "–",      // http://www.fileformat.info/info/unicode/char/2013/index.htm EN DASH
            151 => "—",      // http://www.fileformat.info/info/unicode/char/2014/index.htm EM DASH
            152 => "˜",      // http://www.fileformat.info/info/unicode/char/02DC/index.htm SMALL TILDE
            153 => "™",      // http://www.fileformat.info/info/unicode/char/2122/index.htm TRADE MARK SIGN
            154 => "š",      // http://www.fileformat.info/info/unicode/char/0161/index.htm LATIN SMALL LETTER S WITH CARON
            155 => "›",      // http://www.fileformat.info/info/unicode/char/203A/index.htm SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
            156 => "œ",      // http://www.fileformat.info/info/unicode/char/0153/index.htm LATIN SMALL LIGATURE OE
            157 => "",              // UNDEFINED
            158 => "ž",      // http://www.fileformat.info/info/unicode/char/017E/index.htm LATIN SMALL LETTER Z WITH CARON
            159 => "Ÿ",      // http://www.fileformat.info/info/unicode/char/0178/index.htm LATIN CAPITAL LETTER Y WITH DIAERESIS
        );*/

        if (count($replace) != 0) {
            $find = array();
            foreach (array_keys($replace) as $key) {
                $find[] = chr($key);
            }
            $input = str_replace($find, array_values($replace), $input);
        }
        /*
         * Because ISO-8859-1 and CP1252 are identical except for 0x80 through 0x9F
         * and control characters, always convert from Windows-1252 to UTF-8.
         */
        $input = iconv('Windows-1252', 'UTF-8//IGNORE', $input);
        if (count($replace) != 0) {
            $input = html_entity_decode($input);
        }
    }
    return $input;
}

关键在于需要同时检查ISO-8859-1CP1252,因为它们非常相似。我通过与此函数玩耍了几个小时后才艰难地发现这一点,最终由这个答案拯救。如果您觉得这个函数有用,可以+1该答案。

基本上,此函数使用表示Unicode字符的HTML实体替换所有不良的CP1252字节。然后,我们将字符串从ISO-8859-1/CP1252转换为UTF-8,由于我们的新Unicode字符都是简单的ASCII字符,因此它们不会出现损坏。最后,我们对HTML实体进行解码,最终获得一个100%的Unicode字符串。


谢谢您提供的解决方案,救了我的一天! - jMike

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