1

几天来我一直在尝试解决这个问题,但我无法得到它。基本上我的代码应该读取由 wmic 生成的 .csv 文件并将其保存到结构中。我可以读取数据并且它正在被存储,但是数据在每个字符之后都有一个额外的空间。我曾尝试切换到函数的 Unicode 版本并使用宽字符串,但它们只会更加混乱数据(他们将“n”变成了“ÿ”)。

这是我认为是问题的代码:

system("wmic product get name,version,installdate,vendor /format:csv > product.txt");

std::ifstream infoFile("./program.txt"); // The file wmic wrote in csv format.

if(infoFile.is_open())
{
    std::string line;
    int lineNum = 0;

    while(getline(infoFile, line))
    {
        lineNum++;
        std::cout << "\nLine #" << lineNum << ":" << std::endl;

        Program temp;
        std::istringstream lineStream(line);
        std::string cell;
        int counter = 0;
        int cellNum = 0;

        while(getline(linestream, cell, ','))
        {
            cellNum++;
            std::cout << "\nCell #" << cellNum << ":" << cell << std::endl;

            switch(counter)
            {
            case 0:
                break;
            case 1:
                temp.installDate = cell;
                break;
            case 2:
                temp.name = cell;
                break;
            case 3:
                temp.vendor = cell;
                break;
            case 4:
                temp.version = cell;
                break;
            default:
                std::cout << "GetProductInfo(): Invalid switch value: " << counter << std::endl;
                break;
            }
            counter++;
        }

        information->push_back(temp); // Vector to save all of the programs.
    }

    infoFile.close();
}
else
{
    std::cout << "GetProductInfo(): Failed to open the input file." << std::endl;
    return 1;
}

return 0;
}

编辑: 好的,我正在尝试编写 BOM (FF FE 0D 00 0A),因为它以前没有被编写过。我正在用十六进制值编写一个 char 数组,但是添加了一个额外的 0x0D(FF FE 0D 00 0D 0A)。它还使用额外的空格保存内部变量。这可能不是问题,因为我可以修改我的代码来解决它,但这不是最佳的。有任何想法吗?

Edit2: 所以我想我不需要 BOM。我现在的主要问题是读取 UTF-16LE 文件并将数据保存到没有额外空格的结构中。我需要一些帮助以正确的方式进行操作,因为我想弄清楚将来如何防止这种情况发生。谢谢大家的帮助,这个bug很关键。

4

3 回答 3

5

这闻起来很像文本编码问题,所以我继续尝试运行您提供的命令,果然,输出文件是用 UCS16LE 编码的。(这是 16 位字符,little-endian。)尝试在十六进制编辑器中打开文件以查看它的实际外观。

尝试使用宽字符串时,您走在了正确的道路上,但处理 Unicode 可能会很棘手。接下来的几段将为您提供一些有关如何以困难的方式处理此问题的提示,但如果您需要快速简便的解决方案,请跳到最后。

有两件事要小心。首先,确保您也在使用宽流,例如 wcout。值得将每个字符转换为 int 以仔细检查输出格式是否存在问题。

二是wcout、wstring等格式不规范。在某些编译器上,它是每个字符 2 个字节,而在其他编译器上是 4 个。您通常可以在编译器设置中更改它。C++11 还提供了 std::u16string 和 std::u32string,它们的大小更加明确。

不幸的是,使用 C++ 库读取 Unicode 文本可能相当麻烦,因为即使您有正确的字符串大小,您也需要处理 BOM 和字节序格式,更不用说规范化了。

有一些库可以帮助解决这个问题,但最简单的解决方案可能是在记事本中打开 txt 文件,选择另存为,然后选择您更熟悉的编码,例如 ANSI。

编辑:如果您对快速而肮脏的解决方案不满意,并且不想使用更好的 Unicode 库,则可以使用标准库执行此操作,但前提是您使用的是支持 C+ 的编译器+11,例如 Visual Studio 2012。

C++11 添加了一些codecvt方面来处理不同 Unicode 文件类型之间的转换。这应该符合您的目的,但是这部分库的底层设计是在过去或过去设计的,并且可能相当难以理解。抓住你的裤子。

在您打开 的行下方ifstream,添加以下代码:

infoFile.imbue(std::locale(infoFile.getloc(), new std::codecvt_utf16<char, 0x10FFFF, std::consume_header>));

我知道这看起来有点吓人。它所做的是从现有语言环境的副本中创建“语言环境”,然后向处理格式转换的语言环境添加“方面”。

“语言环境”处理一大堆东西,主要与本地化有关(例如如何标点货币,例如“100.00”与“100,00”)。语言环境中的每个规则都称为一个方面。在 C++ 标准库中,文件编码被视为这些方面之一。

(背景:回想起来,将文件编码与本地化混合起来可能不是一个非常明智的主意,但是在设计库的这一部分时,文件编码通常由程序的语言决定,所以这就是我们陷入了这种情况。)

因此,locale上面的构造函数locale将文件流创建的默认值的副本作为其第一个参数,第二个参数是要使用的新方面。

codecvt_utf16是与 utf-16 相互转换的一个方面。第一个参数是“宽”类型,也就是程序使用的类型,而不是字节流中使用的类型。我char在这里指定了,它适用于 Visual Studio,但根据标准它实际上并不有效。我稍后再谈。

第二个参数是你希望在不抛出错误的情况下接受的最大 Unicode 值,在可预见的未来,0x10FFFF 代表最大的 Unicode 字符。

最后一个参数是改变构面行为的位掩码。我认为std::consume_header这对您特别有用,因为wmic输出 BOM(至少在我的机器上)。这将消耗该 BOM,并根据获得的内容选择是将其视为小端流还是大端流。

您还会注意到我正在使用 堆栈上创建构面new,但我没有delete在任何地方调用。这不是在现代 C++ 中设计库的一种非常安全的方法,但就像我说的,语言环境是库中相当古老的部分。

请放心,您不需要delete这个方面。这实际上并没有很好地记录(因为在实践中很少使用语言环境),但是默认构造的方面将由delete它所附加的语言环境自动生成。

现在,还记得我说过char用作宽类型无效吗?该标准规定您必须使用whcar_t,char16_tchar32_t,如果您想支持非 ASCII 字符,您肯定会想要这样做。使其有效的最简单方法是使用、wchar_t更改ifstream、、和到、、、和,然后确保您的字符串/字符常量前面有一个,如下所示:stringcoutistringstreamwifstreamwstringwcoutwistringstreamL

std::wcout << L"\nLine #" << lineNum << L":" << line << std::endl;

这些是使用宽字符串所需的所有更改。但是,还要注意 Windows 控制台无法处理非 ANSI 字符,因此如果您尝试输出这样的字符(当我运行代码时,我遇到了一个 ™ 字符),wcout 流将失效并停止输出任何内容。如果您要输出到文件,这应该不是问题。

您可能会说我对标准库的这一部分并不特别兴奋。在实践中,大多数想要使用 Unicode 的人会使用不同的库(就像我在评论中提到的那样),或者使用他们自己的编码器/解码器。

于 2013-06-01T06:23:46.620 回答
0

如果您的数据没有您需要的任何空格,您可以使用我的示例:

std::string s = "test, delim, ";
std::string delims = ", ";

size_t pos = 0;
std::string token;

while((pos=s.find(delimiter))!=std::string::npos)) 
{ token = s.substr(0,pos);
  std::cout<<token<<std::endl;
  s.erase(0, pos + delimiter.length());
}
std::cout<<s<<std::endl //last word

或者,您可以strtokcstring库中使用。你也可以检查我的问题,它几乎是一样的:strtok() analog in C++

于 2013-05-31T19:44:38.203 回答
0

如果数据在每个字符之后都有一个额外的空格,我想这意味着它在常规空格之后也有一个额外的空格。

因此,您可以安全地擦除之前没有其他空格的每个空格(实际上是每个字符)。这假设您在原始数据中没有连续两个空格,但如果有,您只需要一个额外的标志来处理它。

所以你的代码可能会变成这样:

while(getline(infoFile, line))
{
    int lsize = line.size(), at = 1;
    for(int i = 1; i < lsize; ++i)
        if(line[i-1] == ' ') line[at++] = line[i];
        // if there is no space behind it, skip it, it is a broken space itself!
    line.resize(at);

    lineNum++;
    // std::cout << "\nLine #"...

我意识到这并不完全理想,因为您实际上并没有阻止核心问题的发生,但考虑到您已经尝试了好几天,这至少可以通过在问题发生后修复它来有效地缓解问题。

检查现场演示

于 2013-06-01T03:23:10.103 回答