在字段中用逗号解析csv

时间:2015-04-04 13:00:22

标签: c++ parsing csv

我尝试使用带有以下数据的csv创建对象

Alonso,Fernando,21,31,29,2,Racing
Dhoni,Mahendra Singh,22,30,4,26,Cricket
Wade,Dwyane,23,29.9,18.9,11,Basketball
Anthony,Carmelo,24,29.4,21.4,8,Basketball
Klitschko,Wladimir,25,28,24,4,Boxing
Manning,Peyton,26,27.1,15.1,12,Football
Stoudemire,Amar'e,27,26.7,21.7,5,Basketball
"Earnhardt, Jr.",Dale,28,25.9,14.9,11,Racing
Howard,Dwight,29,25.5,20.5,5,Basketball
Lee,Cliff,30,25.3,25.1,0.2,Baseball
Mauer,Joe,31,24.8,23,1.8,Baseball
Cabrera,Miguel,32,24.6,22.6,2,Baseball
Greinke,Zack,33,24.5,24.4,50,Baseball
Sharapova,Maria,34,24.4,2.4,22,Tennis
Jeter,Derek,35,24.3,15.3,9,Baseball

我使用以下代码解析它:

void AthleteDatabase::createDatabase(void)
{
    ifstream inFile(INPUT_FILE.c_str());
    string inputString;

    if(!inFile)
    {
        cout << "Error opening file for input: " << INPUT_FILE << endl;
    }
    else
    {
        getline(inFile, inputString);
        while(inFile)
        {
            istringstream s(inputString);
            string field;
            string athleteArray[7];
            int counter = 0;
            while(getline(s, field, ','))
            {
                athleteArray[counter] = field;
                counter++;
            }

            string lastName = athleteArray[0];
            string firstName = athleteArray[1];
            int rank = atoi(athleteArray[2].c_str());
            float totalEarnings = strtof(athleteArray[3].c_str(), NULL);
            float salary = strtof(athleteArray[4].c_str(), NULL);
            float endorsements = strtof(athleteArray[5].c_str(), NULL);
            string sport = athleteArray[6];

            Athlete anAthlete(lastName, firstName, rank,
                              totalEarnings, salary, endorsements, sport);
            athleteDatabaseBST.add(anAthlete);
            display(anAthlete);
            getline(inFile, inputString);
        }
        inFile.close();
    }
}

我的代码中断了:

"Earnhardt, Jr.",Dale,28,25.9,14.9,11,Racing
显然是因为引号。有没有更好的方法来处理这个?我仍然是C ++的新手,所以非常感谢任何帮助。

4 个答案:

答案 0 :(得分:3)

我建议您使用正确的CSV解析器。您可以在this earlier questionsearch for one on Google的答案中找到一些内容。

如果你坚持自己动手,那么最简单的方法是将其设计为一个有限状态机,一次处理输入的一个字符。使用单字符预测,您基本上需要两种状态:&#34;读取正常输入&#34;和&#34;读取带引号的字符串&#34;。如果您不想使用预测,则可以使用其他几种状态执行此操作,例如:像这样:

  • 初始状态:如果下一个字符是引号,请切换到州引用字段;其他行为好像处于州未引用字段

  • 未加引号的字段:如果下一个字符是EOF,则结束解析;否则,如果是换行符,请开始新行并切换到初始状态;否则,如果是分隔符(逗号),则在同一行中启动一个新字段并切换到初始状态;否则将该字符附加到当前字段并保持状态不带引号的字段。 (可选地,如果字符是引号,则表示解析错误。)

  • 引用字段:如果下一个字符是EOF,则信号解析错误;否则,如果是报价,请切换到州结束报价;否则将该字符附加到当前字段并保持状态引用字段

  • 结束引用:如果下一个字符是引号,请将其附加到当前字段并返回状态引用字段;否则,如果是逗号或换行符或EOF,则表现为状态未加引号的字段;否则信号解析错误。

(这适用于&#34;传统&#34; CSV,如RFC 4180中所述,其中引用字段中的引号通过加倍来转义。添加对反斜杠转义的支持,在某些情况下使用CSV格式的相当常见的变体,留作练习。它需要一个或两个以上的状态,这取决于你是否要支持带引号或不带引号的字符串中的反斜杠,以及是否要支持传统和反斜杠转义在同一时间。)

在高级脚本语言中,这种逐个字符的迭代效率非常低,但是由于你正在编写C ++,所以它需要快速实现的是一些不太好的I / O缓冲和一个合理有效的字符串附加操作。

答案 1 :(得分:1)

你必须逐个字符地解析每一行,使用bool标志和积累下一个字段内容的std::string;而不是像往常一样向下一个逗​​号前进。

最初,bool标志为false,您逐个字符地遍历整行。引号字符翻转bool标志。只有当bool标志为false时,逗号字符 才会获取std::string的累计内容,并将其保存为该行的下一个字段,并将std::string清空为空,为下一个领域做好准备。否则,该字符将附加到缓冲区。

这是算法的基本概要,有一些细微的细节,你应该能够自己充实。还有其他几种方法可以做到这一点,效率稍高,但对于像你这样的初学者来说,这种方法最容易实现。

答案 2 :(得分:1)

简单回答:使用不同的分隔符。如果您使用'|'之类的东西,则更容易解析:

Stoudemire,Amar'e|27|26.7|21.7|5|Basketball
Earnhardt, Jr.|Dale|28|25.9|14.9|11|Racing

任何其他可能需要解析文件的应用程序的优势也可以干净利落地完成。

如果要求使用逗号,则必须根据其第一个字符条件有条件地获取字段:

std::istream& nextField(std::istringstream& s, std::string& field)
{
    char c;
    if (s >> c) {
        if (c == '"') {
            // using " as the delimeter
            getline(s, field, '"');
            return s >> c; // for the subsequent comma
                           // could potentially assert for error-checking
        }
        else if (c == ',') {
            // handle empty field case
            field = "";
        }
        else {
            // normal case, but prepend c
            getline(s, field, ',');
            field = c + field;
        }
    }

    return s;
}

用作getline

的替代品
while (nextField(s, field)) {
    athleteVec.push_back(field); // prefer vector to array
}

如果我们有一个未终结的带引号的字符串,可以通过继续使用getline来简化那个逻辑:

std::istream& nextField(std::istringstream& s, std::string& field)
{
    if (std::getline(s, field, ',')) {
        while (s && field[0] == '"' && field[field.size() - 1] != '"') {
            std::string next;
            std::getline(s, next, ',');
            field += ',' + next;
        }

        if (field[0] == '"' && field[field.size() - 1] == '"') {
            field = field.substr(1, field.size() - 2);
        }
    }

    return s;
}

答案 3 :(得分:0)

我同意伊万里的回答,为什么重新发明轮子?话虽这么说,你考虑过正则表达式吗?我相信this answer可以用来完成你想要的,然后是一些。