2

我使用 stringstream 和 libcurl 下载数据。我也有解析功能。

bool parse()
{
    istringstream temp(buff.str());
    buff.str("");
    string line;
    QString line_QStr, lyrics_QStr;
    while (temp.good())
    {
        getline(temp, line);
        if (QString::fromStdString(line).contains(startMarker)) break;
    }
    if (!temp.good()) return false; // something went wrong

    while (temp.good())
    {
        getline(temp, line);
        if ((line_QStr = QString::fromStdString(line)).contains(endMarker))
        {
            lyrics_QStr += line_QStr.remove(endMarker); // remove the </div>
            break;
        }
        else
        {
            lyrics_QStr += line_QStr;
        }
    }

    if (!temp.good()) return false;

    QTextDocument lyricsHtml;
    lyricsHtml.setHtml(lyrics_QStr);
    lyrics_qstr = lyricsHtml.toPlainText();
    return true;
}

当文本是 ascii-only 时是可以的。但如果它是 unicode,那么我会在这个函数的某个地方丢失 unicode 字符。结果是这样的:

Unicode 字符搞砸了

我使用 string 和 getline 而不是 QTextStream 和 QString,因为我找不到 good() 函数的任何对应物,所以我无法进行任何体面的错误处理。

我在这个函数中做错了什么,unicode 字符丢失并显示为 2 个其他字符?我该如何解决?提前致谢!

编辑:我将解析函数更改为:

bool LyricsManiaDownloader::parse()
{
    wistringstream temp(string2wstring(buff.str()));
    buff.str("");
    wstring line;
    QString line_QStr, lyrics_QStr;
    while (temp.good())
    {
        getline(temp, line);
        if (QString::fromStdWString(line).contains(startMarker)) break;
    }
    if (!temp.good()) return false; // something went wrong

    while (temp.good())
    {
        getline(temp, line);
        if ((line_QStr = QString::fromStdWString(line)).contains(endMarker))
        {
            lyrics_QStr += line_QStr.remove(endMarker); // remove the </div>
            break;
        }
        else
        {
            lyrics_QStr += line_QStr;
        }
    }

    if (!temp.good()) return false;

    QTextDocument lyricsHtml;
    lyricsHtml.setHtml(lyrics_QStr);
    lyrics_qstr = lyricsHtml.toPlainText();
    return true;
}

string2wstring 函数是

wstring string2wstring(const string &str)
{
    wstring wstr(str.length(), L' ');
    copy(str.begin(), str.end(), wstr.begin());
    return wstr;
}

编码仍然存在一些问题。

EDIT2:我使用此功能将数据保存到字符串流中

size_t write_data_to_var(char *ptr, size_t size, size_t nmemb, void *userdata)
{
    ostringstream * stream = (ostringstream*) userdata;
    size_t count = size * nmemb;
    stream->write(ptr, count);
    return count;
}

我把std::ostringstream buff传给curl,网页数据保存在这里。然后我使用 wistringstream,将 buff.str() 转换为 wstring 并将其用作 wistringstream 的源。从 std::string 到 std::wstring 的转换是解码,不是吗?

4

1 回答 1

1

Web 服务器返回一个字节流以及一个标头,该标头指示这些字节应该被理解为什么编码。如果你调用 QString::fromStdString 而不介意那个编码,那么 Qt 将默认使用 Latin1。在您的情况下,服务器发送 UTF-8 数据并将其解析为 Latin1 会导致您作为示例给出的那种损坏的文本。

作为一种快速解决方法,您可以使用 QTextCodec::setCodecForCStrings 全局设置正确的编码。但是,这不是线程安全的。

理想情况下,您将在尝试解析 Web 服务器返回的字节流之前对其进行解码,然后使用 fromStdWString 将其转换为 QString。根据经验,您希望尽早解码文本数据。请参阅 Joel Spolsky 关于如何处理 Unicode 的著名文章:http: //www.joelonsoftware.com/articles/Unicode.html

编辑:本质上,您的代码中缺少一个步骤:获取服务器返回的字节流,并将其转换为正确的、不含歧义的文本。

您可能会发现将文本流和字节流视为完全不同的动物很有用。核心区别在于文本是明确的:它是明确定义的字符串和字符标记(变音符号),以内在的方式存在,不受实现细节的约束。然而,字节流可能意味着任何东西,具体取决于您如何解释它们。

取字节 0xC2 0xA3。它们可能表示“字符 Â 后跟字符 Ł”。这是一个完全有效的解释。但它们也可能表示“字符£”。这是另一种完全有效的解释。

这些解释就是我们所说的编码。在第一种情况下,编码是 Windows-1250,而在第二种情况下,编码是 UTF-8。请允许我在此重申,这两种编码都可能是正确的。也许向您发送这些字节的人真的想说 ÂŁ。也许真的是英镑。也许它甚至完全是另外一回事,在不知道编码的情况下,你无法分辨那是什么。

这里的想法是:一个你不知道其编码的字节流基本上是无用的。

不幸的是,许多语言仍然允许您传递字节流并假装它们是文本。C++ 也不能幸免:std::string 类型,尽管名称具有误导性,但实际上是字节流。不要让名字欺骗你。

当您像文本一样传递字节时,最终负责显示该文本的子系统将对字节进行解码。(这是一条重要的经验法则:如果正在显示文本,则在某处对字节进行解码。)只有所说的子系统通常会使用默认编码(ASCII,Latin1),如果这不正确,那么这就是你最终的结果带有意想不到的字符。

而你的问题的核心正是:你正在获取Web服务器发送给你的字节流,丢弃随之而来的编码信息,并将字节盲目地传递给Qt。

当您尝试从 std::string 构建 QString 时,Qt 会尝试提供帮助并假设通常有效的通用编码。恕我直言,这不是一个好主意,因为它会导致您遇到的问题;我认为如果 QString 需要显式编码,从长远来看会更好。

所以在那之前,你必须以不同的方式解决你的问题。

值得庆幸的是,有一种已知的正确方法可以解决整个问题。

还记得我说过没有编码的字节流是没有意义的吗?好吧,Web 服务器通常向您发送一个编码,作为 Content-Type HTTP 标头的一部分。类似的东西Content-Type: text/html; charset=iso-8859-1

字符集是您的编码:在这里,它是 iso-8859-1,它是 Latin1 的另一个名称。

(注意:如果内容是 HTML,也可以在http-equivmeta header 标签中给出编码。如果该标签与 HTTP header 不一致,则假定 HTTP header 是正确的。)

您想立即使用该编码将这些字节转换为“实际”文本。

在越来越多的语言中,“实际”文本是一种特定类型,不同于字节流。然而,在 C++ 中,您只能靠自己。

管理文本的标准方法是将其从初始编码转码为 UTF-16,并将结果存储在 std::wstring 中。原因是 UTF-16 几乎可以毫无歧义地存储任何文本。(如果您改用 UTF-32,您将能够存储任何文本,包括使用罕见的旧亚洲字符的文本,但内存成本会增加一倍。)

老实说,我有点希望 libcurl 可以为你做这件事。其他语言的其他库确实返回正确解码的文本,而不是字节。但据我所知,这里没有这样的运气。

但!您不是在使用原始 C++,而是在使用 Qt,并且 Qt 带有用于正确处理文本的工具。

因此,您将尽早将字节转换为 QString,同时您手头还有编码,然后就可以了。QStrings 是正确的文本,而不是字节流;字节流的 Qt 类型是 QByteArray。

所以,告诉你什么,让我们完全放弃 wstrings,而只使用 QStrings。

为了解决您的问题 - 以及您将遇到的任何编码问题 - 您必须:

1/ 找出预期的编码;在您的情况下,您将解析 Content-Type 标头以找出编码。或者也许 libcurl 可以自己给你这些信息,我不知道。

2/ 立即使用它来解码内容。在您的情况下,使用 QTextCodec 将其解码为 QString。查看QTextCodec 文档以获取详细信息。

QTextCodec *codec = QTextCodec::codecForName( figured_out_encoding );
QString string = codec->toUnicode( byte_stream );

你完成了。string现在包含正确的、明确的文本。

这已经足够长了,所以我将停在那里而不涉及其他细节(如果服务器对 Content-Type 撒谎该怎么办,如果 Web 设计人员弄错了 http-equiv 标签怎么办)。上述方法已经解决了您将遇到的 95% 的编码问题,并且顺便说一下,让您领先于 95% 的编码人员。

希望这可以帮助!

于 2012-08-20T09:55:23.240 回答