如何使用C++跳过csv中的标题行

How to skip header row in csv using C++

本文关键字:标题 csv 何使用 C++ 跳过      更新时间:2023-10-16

在我的场景中,我需要使用CSV创建一个参数文件。每一行都意味着一个配置数据,该行的第一个字段被视为标头,用作标识符。下面这样的CSV格式对我来说很容易解析:

1,field1,field2,field3,field4 // 1 indicated the TARGET that the other fields will be writted to.
1,field1,field2,field3,field4
2,field1,field2,field3,field4
2,field1,field2,field3,field4........

但它对用户并不友好。因此,我定义了一个csv文件,如下所示:

HeaderLine_Begin,1
field1,field2,field3,field4
field1,field2,field3,field4
HeaderLine_Begin,2
field1,field2,field3,field4
field1,field2,field3,field4

意味着,每一行都是数据,数据将由HeaderLine_Begin写入目标。我只是将ID与实际数据分开。然后,我创建了一个这样的结构:

    enum myenum
    {
      ON,OFF,NOCHANGE
    };
    struct Setting
    {
      int TargetID;
      string field1;
      string field2;
      myenum field3;
      myenum field4;    
    };

我知道如何写一些代码来逐行读取csv,就像下面的一样

filename +=".csv";
std::ifstream file(filename.c_str());
std::string line;
while ( file.good() )
{       
    getline ( file, line, 'n' ); // read a line until last 
    if(line.compare(0,1,"#") == 0) // ignore the comment line
        continue;
    ParseLine();// DONE.Parse the line if it's header row OR data row           
}
file.close(); // close file

我想做的是创建一个类似否决器设置的列表来保存数据。流程应该是,找到第一个headerID1,然后找到下一行。如果下一条线路是数据线路,则将其视为数据线路属于headerID1。如果下一个线路是另一个headerID,则再次循环。

问题是,在我找到headerRow之后,没有这样的std::getnextline(int-lineIndex)可以让我获取行。

您的输入循环应该更像:

int id = -1;
while (getline(file, line))
{
     if (line.empty() || line[0] == '#')
         continue;
     if (starts_with_and_remove(line, "HeaderLine_Begin,"))
         id = boost::lexical_cast<int>(line); // or id = atoi(line.c_str())
     else
     {
         assert(id != -1);
         ...parse CSV, knowing "id" is in effect...
     }
}

带有:

bool stats_with_and_remove(std::string& lhs, const std::string& rhs)
{
    if (lhs.compare(0, rhs.size(), lhs) == 0)  // rhs.size() > lhs.size() IS safe
    {
        lhs.erase(0, rhs.size());
        return true;
    }
    return false;
}

最简单的解决方案是使用正则表达式:

std::string line;
int currentId = 0;
while ( std::getline( source, line ) ) {
    trimCommentsAndWhiteSpace( line );
    static std::regex const header( "HeaderLine_Begin,(\d+)" );
    std::smatch match;
    if ( line.empty() ) {
        //  ignore
    } else if ( std::regex_match( line, match, header ) ) {
        std::istringstream s( match[ 1 ] );
        s >> currentId;
    } else {
        //  ...
    }
}

我经常使用这种策略来解析.ini文件同样的问题:节头与其他事情。

trimCommentsAndWhiteSpace可以简单到:

void
trimCommentsAndWhiteSpace( std::string& line )
{
    if ( !line.empty() && line[0] == '#' ) {
        line = "";
    }
}

很容易将其扩展为处理行末注释然而,这通常是一项好政策(在类似this)来修剪前导和尾随空格---尾随尤其是,因为人类读者在看文件。

当然,也可以使用正则表达式要树化为注释的行("\s*#.*");这很管用符合您当前的定义,但没有真正扩展适用于行尾注释,尤其是如果您希望允许字段中带引号的字符串中的#

最后一条评论是:你的循环不正确。你不测试getline在使用其结果之前成功,并且即使没有其他内容,file.good()也可能返回true阅读(file.good()就是其中之一历史原因;在任何情况下,使用)