0

在我的场景中,我需要使用 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

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

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

4

2 回答 2

1

您的输入循环应该更像:

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;
}
于 2013-07-12T07:40:41.133 回答
0

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

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 = "";
    }
}

但是,将其扩展为处理行尾注释也相当容易,并且修剪前导和尾随空格通常是一个很好的策略(在这种情况下)——尤其是尾随,因为人类读者不会看到它在查看文件时。

或者,当然,您可以将正则表达式用于要作为注释树的行 ("\s*#.*"); 这适用于您当前的定义,但对于行尾注释并不能很好地扩展,特别是如果您希望 #在字段中允许引用字符串。

最后一条评论:您的循环不正确。在使用它的结果之前你不会测试它是否getline成功,并且 file.good()即使没有更多可阅读的内容也可能返回 true。(file.good()这是由于历史原因而存在的东西之一;没有任何情况下使用它是有意义的。)

于 2013-07-12T08:05:27.040 回答