我从各种RSS订阅中阅读大量文本,并将它们插入到我的数据库中。

当然,在提要中使用了几种不同的字符编码,例如UTF-8和ISO 8859-1。

不幸的是,文本的编码有时会有问题。例子:

“Fußball”中的“ß”在我的数据库中应该是这样的:“Ÿ”。如果是“Ÿ”,则显示正确。 有时,“Fußball”中的“ß”在我的数据库中看起来像这样:“ß”。当然,这样就会显示错误。 在其他情况下,“ß”被保存为“ß”-因此没有任何变化。然后它也会被错误地显示。

我怎么做才能避免情况2和3?

我如何使所有的编码相同,最好是UTF-8?什么时候我必须使用utf8_encode(),什么时候我必须使用utf8_decode()(很清楚的效果是什么,但什么时候我必须使用函数?),什么时候我必须对输入什么都不做?

如何让所有编码都相同呢?也许使用函数mb_detect_encoding()?我能写一个函数吗?所以我的问题是:

如何找出文本使用的编码? 我如何将其转换为UTF-8 -无论旧的编码是什么?

这样的函数可行吗?

function correct_encoding($text) {
    $current_encoding = mb_detect_encoding($text, 'auto');
    $text = iconv($current_encoding, 'UTF-8', $text);
    return $text;
}

我已经测试过了,但是不行。有什么问题吗?


当前回答

此版本适用于德语,但您可以修改$CHARSETS和$TESTCHARS。

class CharsetDetector
{
    private static $CHARSETS = array(
        "ISO_8859-1",
        "ISO_8859-15",
        "CP850"
    );

    private static $TESTCHARS = array(
        "€",
        "ä",
        "Ä",
        "ö",
        "Ö",
        "ü",
        "Ü",
        "ß"
    );

    public static function convert($string)
    {
        return self::__iconv($string, self::getCharset($string));
    }

    public static function getCharset($string)
    {
        $normalized = self::__normalize($string);
        if(!strlen($normalized))
            return "UTF-8";
        $best = "UTF-8";
        $charcountbest = 0;
        foreach (self::$CHARSETS as $charset)
        {
            $str = self::__iconv($normalized, $charset);
            $charcount = 0;
            $stop = mb_strlen($str, "UTF-8");

            for($idx = 0; $idx < $stop; $idx++)
            {
                $char = mb_substr($str, $idx, 1, "UTF-8");
                foreach (self::$TESTCHARS as $testchar)
                {
                    if($char == $testchar)
                    {
                        $charcount++;
                        break;
                    }
                }
            }

            if($charcount > $charcountbest)
            {
                $charcountbest = $charcount;
                $best = $charset;
            }
            //echo $text . "<br />";
        }
        return $best;
    }

    private static function __normalize($str)
    {
        $len = strlen($str);
        $ret = "";
        for($i = 0; $i < $len; $i++)
        {
            $c = ord($str[$i]);
            if ($c > 128) {
                if (($c > 247))
                    $ret .= $str[$i];
                elseif
                    ($c > 239) $bytes = 4;
                elseif
                    ($c > 223) $bytes = 3;
                elseif
                    ($c > 191) $bytes = 2;
                else
                    $ret .= $str[$i];

                if (($i + $bytes) > $len)
                    $ret .= $str[$i];
                $ret2 = $str[$i];
                while ($bytes > 1)
                {
                    $i++;
                    $b = ord($str[$i]);
                    if ($b < 128 || $b > 191)
                    {
                        $ret .= $ret2;
                        $ret2 = "";
                        $i += $bytes-1;
                        $bytes = 1;
                        break;
                    }
                    else
                        $ret2 .= $str[$i];
                    $bytes--;
                }
            }
        }
        return $ret;
    }

    private static function __iconv($string, $charset)
    {
        return iconv ($charset, "UTF-8", $string);
    }
}

其他回答

当你试着掌握多种语言时,比如日语和韩语,你可能会遇到麻烦。

带有'auto'参数的Mb_convert_encoding不能很好地工作。设置mb_detect_order('ASCII,UTF-8,JIS,EUC- jp,SJIS,EUC- kr,UHC')没有帮助,因为它会错误地检测EUC-*。

我的结论是,只要输入字符串来自HTML,它就应该在元元素中使用“字符集”。我使用Simple HTML DOM Parser,因为它支持无效的HTML。

下面的代码片段从网页中提取title元素。如果您想转换整个页面,那么您可能需要删除一些行。

<?php
require_once 'simple_html_dom.php';

echo convert_title_to_utf8(file_get_contents($argv[1])), PHP_EOL;

function convert_title_to_utf8($contents)
{
    $dom = str_get_html($contents);
    $title = $dom->find('title', 0);
    if (empty($title)) {
        return null;
    }
    $title = $title->plaintext;
    $metas = $dom->find('meta');
    $charset = 'auto';
    foreach ($metas as $meta) {
        if (!empty($meta->charset)) { // HTML5
            $charset = $meta->charset;
        } else if (preg_match('@charset=(.+)@', $meta->content, $match)) {
            $charset = $match[1];
        }
    }
    if (!in_array(strtolower($charset), array_map('strtolower', mb_list_encodings()))) {
        $charset = 'auto';
    }
    return mb_convert_encoding($title, 'UTF-8', $charset);
}

从头文件中获取编码并将其转换为UTF-8。

$post_url = 'http://website.domain';

/// Get headers ///////////////////////////////////////////////
function get_headers_curl($url)
{
    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL,            $url);
    curl_setopt($ch, CURLOPT_HEADER,         true);
    curl_setopt($ch, CURLOPT_NOBODY,         true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT,        15);

    $r = curl_exec($ch);
    return $r;
}

$the_header = get_headers_curl($post_url);

/// Check for redirect ////////////////////////////////////////
if (preg_match("/Location:/i", $the_header)) {
    $arr = explode('Location:', $the_header);
    $location = $arr[1];

    $location = explode(chr(10), $location);
    $location = $location[0];

    $the_header = get_headers_curl(trim($location));
}

/// Get charset ///////////////////////////////////////////////
if (preg_match("/charset=/i", $the_header)) {
    $arr = explode('charset=', $the_header);
    $charset = $arr[1];

    $charset = explode(chr(10), $charset);
    $charset = $charset[0];
}

///////////////////////////////////////////////////////////////////
// echo $charset;

if($charset && $charset != 'UTF-8') {
    $html = iconv($charset, "UTF-8", $html);
}

我一直在寻找编码的解决方案,这个页面可能是多年搜索的结论!我测试了你提到的一些建议,以下是我的笔记:

这是我的测试字符串:

这是一个“wròng wrìtten”字符串bùt I nèed到pù‘sòme’的特殊 Chàrs看thèm, convertèd看fùnctìon!!&就是这样!

我执行INSERT操作将该字符串保存在数据库中的字段中,该字段设置为utf8_general_ci

我的页面的字符集是UTF-8。

如果我像这样做一个INSERT,在我的数据库中,我有一些字符可能来自火星…

所以我需要把它们转换成一些“理智的”UTF-8。我尝试utf8_encode(),但仍然外星人字符入侵我的数据库…

所以我尝试使用函数forceeutf8张贴在数字8,但在数据库中保存的字符串看起来像这样:

这是一个“wròng wrà tten”的字符串bùt I nèed to pù'sòme'特殊 Chà rs看thèm, convertèd由fùnctà on!!&就是这样!

所以在这个页面上收集更多的信息,并将它们与其他页面上的其他信息合并,我用这个解决方案解决了我的问题:

$finallyIDidIt = mb_convert_encoding(
  $string,
  mysql_client_encoding($resourceID),
  mb_detect_encoding($string)
);

现在在我的数据库中,我有了编码正确的字符串。

注意:

唯一需要注意的是mysql_client_encoding函数! 您需要连接到数据库,因为这个函数需要一个资源ID作为参数。

但是,我只是在插入之前重新编码,所以对我来说这不是问题。

不带auto的Try

那就是:

mb_detect_encoding($text)

而不是:

mb_detect_encoding($text, 'auto')

更多信息可以在这里找到:mb_detect_encoding

首先必须检测使用了什么编码。在解析RSS提要时(可能通过HTTP),应该从Content-Type HTTP报头字段的字符集参数中读取编码。如果不存在,则从XML处理指令的encoding属性中读取编码。如果也没有,请使用规范中定义的UTF-8。


以下是我可能会做的:

I’d use cURL to send and fetch the response. That allows you to set specific header fields and fetch the response header as well. After fetching the response, you have to parse the HTTP response and split it into header and body. The header should then contain the Content-Type header field that contains the MIME type and (hopefully) the charset parameter with the encoding/charset too. If not, we’ll analyse the XML PI for the presence of the encoding attribute and get the encoding from there. If that’s also missing, the XML specs define to use UTF-8 as encoding.

$url = 'http://www.lr-online.de/storage/rss/rss/sport.xml';

$accept = array(
    'type' => array('application/rss+xml', 'application/xml', 'application/rdf+xml', 'text/xml'),
    'charset' => array_diff(mb_list_encodings(), array('pass', 'auto', 'wchar', 'byte2be', 'byte2le', 'byte4be', 'byte4le', 'BASE64', 'UUENCODE', 'HTML-ENTITIES', 'Quoted-Printable', '7bit', '8bit'))
);
$header = array(
    'Accept: '.implode(', ', $accept['type']),
    'Accept-Charset: '.implode(', ', $accept['charset']),
);
$encoding = null;
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
$response = curl_exec($curl);
if (!$response) {
    // error fetching the response
} else {
    $offset = strpos($response, "\r\n\r\n");
    $header = substr($response, 0, $offset);
    if (!$header || !preg_match('/^Content-Type:\s+([^;]+)(?:;\s*charset=(.*))?/im', $header, $match)) {
        // error parsing the response
    } else {
        if (!in_array(strtolower($match[1]), array_map('strtolower', $accept['type']))) {
            // type not accepted
        }
        $encoding = trim($match[2], '"\'');
    }
    if (!$encoding) {
        $body = substr($response, $offset + 4);
        if (preg_match('/^<\?xml\s+version=(?:"[^"]*"|\'[^\']*\')\s+encoding=("[^"]*"|\'[^\']*\')/s', $body, $match)) {
            $encoding = trim($match[1], '"\'');
        }
    }
    if (!$encoding) {
        $encoding = 'utf-8';
    } else {
        if (!in_array($encoding, array_map('strtolower', $accept['charset']))) {
            // encoding not accepted
        }
        if ($encoding != 'utf-8') {
            $body = mb_convert_encoding($body, 'utf-8', $encoding);
        }
    }
    $simpleXML = simplexml_load_string($body, null, LIBXML_NOERROR);
    if (!$simpleXML) {
        // parse error
    } else {
        echo $simpleXML->asXML();
    }
}