首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用于解析CSV数据的库

用于解析CSV数据的库
EN

Code Review用户
提问于 2020-06-27 16:28:27
回答 1查看 136关注 0票数 2

请查看我的C++ CSV解析类。

我有一些具体的问题:

  1. get_next_record应该是一个静态函数吗?
  2. CsvParser意味着值将用逗号分隔,那么字段分隔符构造函数是否在顶部?
  3. record.clear()get_next_record的开头。任何其他方法来解决删除最后记录的问题。我意识到您可以返回记录,但是接下来您会遇到如何处理EOF或流错误的问题。

CsvParser.hpp

代码语言:javascript
复制
#ifndef CSV_PARSER_HPP_
#define CSV_PARSER_HPP_

#include <iostream>
#include <string>
#include <vector>

using Field = std::string;
using Record = std::vector<Field>;
using Records = std::vector<Record>;

class CsvParser {
public:
    CsvParser(char field_separator = ',');
    bool get_next_record(std::istream& istrm, Record& record) const;

private:
    char field_separator_char;
};

#endif // CSV_PARSER_HPP_

CsvParser.cpp

代码语言:javascript
复制
#include "CsvParser.hpp"

CsvParser::CsvParser(char field_separator) : field_separator_char(field_separator) {}

bool CsvParser::get_next_record(std::istream& istrm, Record& record) const {

    // Having to clear record because otherwise the program will keep pushing back
    // fields into the vector feels dirty.  How could this be improved?
    record.clear();
    bool in_quotes = false;
    Field field;
    int ch;
    while (istrm) {
        ch = istrm.get();
        if (ch == EOF || (ch == '\n' && !in_quotes)) {
            if (ch == EOF && record.empty() && field.empty()) {
                return false;
            }
            else {
                record.push_back(field);
                return true;
            }
        }
        else if (ch == field_separator_char && !in_quotes) {
            record.push_back(field);
            field.clear();
        }
        else if (ch == '"') {
            if (!in_quotes) {
                in_quotes = true;
            }
            else {
                // Could be an embedded " if next symbol not comma
                int nextch = istrm.peek();
                if (nextch != field_separator_char && nextch != '\n' && nextch != EOF) {
                    field += static_cast<char>(ch);
                }
                else {
                    in_quotes = false;
                }
            }
        }
        else if (ch == '\r') {
            if (in_quotes) {
                field += static_cast<char>(ch);
            }
        }
        else {
            field += static_cast<char>(ch);
        }
    }
    return false;
}

使用谷歌测试进行锻炼:

代码语言:javascript
复制
#include <gtest/gtest.h>

#include "CsvParser.hpp"
#include <sstream>
#include <string>

class CsvParserTest : public ::testing::Test {
public:
    CsvParser parser;
};

TEST_F(CsvParserTest, EmptyRecord) {

    const std::string csv{ "" };
    std::stringstream strm(csv);
    Record record;

    EXPECT_FALSE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 0u);
}

TEST_F(CsvParserTest, SimpleSingleRecord) {

  const std::string csv{ "AA,BB,CC" };
  std::stringstream strm(csv);
  Record record;

  EXPECT_TRUE(parser.get_next_record(strm, record));
  EXPECT_EQ(record.size(), 3u);
  EXPECT_EQ(record[0], "AA");
  EXPECT_EQ(record[1], "BB");
  EXPECT_EQ(record[2], "CC");
}

TEST_F(CsvParserTest, SimpleTwoRecord) {

    const std::string csv{ "AA,BB,CC\r\nDD,EE,FF" };
    std::stringstream strm(csv);
    Record record;

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "AA");
    EXPECT_EQ(record[1], "BB");
    EXPECT_EQ(record[2], "CC");

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "DD");
    EXPECT_EQ(record[1], "EE");
    EXPECT_EQ(record[2], "FF");
}

TEST_F(CsvParserTest, SimpleQuotedField) {

    const std::string csv{ "\"A\",BB,CCC" };
    std::stringstream strm(csv);
    Record record;

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "A");
    EXPECT_EQ(record[1], "BB");
    EXPECT_EQ(record[2], "CCC");
}

TEST_F(CsvParserTest, QuotesEmbeddedInQuotedField) {

    const std::string csv{ "\"\"A\"\",BB,CCC" };
    std::stringstream strm(csv);
    Record record;

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "\"A\"");
    EXPECT_EQ(record[1], "BB");
    EXPECT_EQ(record[2], "CCC");
}

TEST_F(CsvParserTest, LinefeedEmbeddedInQuotedField) {

    const std::string csv{ "\"\"A\n\"\",BB,CCC" };
    std::stringstream strm(csv);
    Record record;

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "\"A\n\"");
    EXPECT_EQ(record[1], "BB");
    EXPECT_EQ(record[2], "CCC");
}

TEST_F(CsvParserTest, CommaEmbeddedInQuotedField) {

    const std::string csv{ R"(""A,"",BB,CCC)" };
    std::stringstream strm(csv);
    Record record;

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], R"("A,")");
    EXPECT_EQ(record[1], "BB");
    EXPECT_EQ(record[2], "CCC");
}

TEST_F(CsvParserTest, EmptyRow) {

    const std::string csv{ ",," };
    std::stringstream strm(csv);
    Record record;

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0].size(), 0u);
    EXPECT_EQ(record[1].size(), 0u);
    EXPECT_EQ(record[2].size(), 0u);
}

TEST_F(CsvParserTest, QuotedFollowedByTwoEmptyFields) {

    const std::string csv{ "\"A\n\n\nB\",," };
    std::stringstream strm(csv);
    Record record;

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "A\n\n\nB");
    EXPECT_EQ(record[1].size(), 0u);
    EXPECT_EQ(record[2].size(), 0u);
}

TEST_F(CsvParserTest, EmptyThenQuotedThenEmptyField) {

    const std::string csv{ ",\"A\n\n\nB\"," };
    std::stringstream strm(csv);
    Record record;

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0].size(), 0u);
    EXPECT_EQ(record[1], "A\n\n\nB");
    EXPECT_EQ(record[2].size(), 0u);
}

TEST_F(CsvParserTest, EmptyEmptyThenQuoted) {

    const std::string csv{ ",,\"A\n\n\nB\"" };
    std::stringstream strm(csv);
    Record record;

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0].size(), 0u);
    EXPECT_EQ(record[1].size(), 0u);
    EXPECT_EQ(record[2], "A\n\n\nB");
}

TEST_F(CsvParserTest, CRLFEndOfLIne) {

    const std::string csv{ "A,B,C\r\nD,E,F" };
    std::stringstream strm(csv);
    Record record;

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "A");
    EXPECT_EQ(record[1], "B");
    EXPECT_EQ(record[2], "C");

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "D");
    EXPECT_EQ(record[1], "E");
    EXPECT_EQ(record[2], "F");
}

TEST_F(CsvParserTest, EmbeddedCRLF) {

    const std::string csv{ "A,\"B\r\nC\",D\r\nE,F,G" };
    std::stringstream strm(csv);
    Record record;

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "A");
    EXPECT_EQ(record[1], "B\r\nC");
    EXPECT_EQ(record[2], "D");

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "E");
    EXPECT_EQ(record[1], "F");
    EXPECT_EQ(record[2], "G");
}

TEST_F(CsvParserTest, Complex) {

    const std::string csv = "AAA,BB,CCC\nDDD,EE,FFF\n\"A A\",\"B\nB\",CC\n\"A,B,C\",\"D         E\",F\n\"Billy \"Da Man\" Hooker\",,\n,,\n,,\"Yo bitches!\"\n,,\"Holler if you luv dem \"hat\" bitches\"\n,\"These are my long\nnotes on a load\nof stuff\n fancy some commas:,,,,,,,,,,,,,,,,,,,,,,,,,,,\",";

    std::stringstream strm(csv);
    Record record;

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "AAA");
    EXPECT_EQ(record[1], "BB");
    EXPECT_EQ(record[2], "CCC");

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "DDD");
    EXPECT_EQ(record[1], "EE");
    EXPECT_EQ(record[2], "FFF");

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "A A");
    EXPECT_EQ(record[1], "B\nB");
    EXPECT_EQ(record[2], "CC");

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "A,B,C");
    EXPECT_EQ(record[1], "D         E");
    EXPECT_EQ(record[2], "F");

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "Billy \"Da Man\" Hooker");
    EXPECT_EQ(record[1], "");
    EXPECT_EQ(record[2], "");

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "");
    EXPECT_EQ(record[1], "");
    EXPECT_EQ(record[2], "");

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "");
    EXPECT_EQ(record[1], "");
    EXPECT_EQ(record[2], "Yo bitches!");

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "");
    EXPECT_EQ(record[1], "");
    EXPECT_EQ(record[2], "Holler if you luv dem \"hat\" bitches");

    EXPECT_TRUE(parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "");
    EXPECT_EQ(record[1], "These are my long\nnotes on a load\nof stuff\n fancy some commas:,,,,,,,,,,,,,,,,,,,,,,,,,,,");
    EXPECT_EQ(record[2], "");
}

TEST_F(CsvParserTest, TabSeparated) {

    const std::string csv{ "AA\tBB\tCC\nDD\tEE\tFF" };
    std::stringstream strm(csv);
    Record record;
    CsvParser tab_parser('\t');

    EXPECT_TRUE(tab_parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "AA");
    EXPECT_EQ(record[1], "BB");
    EXPECT_EQ(record[2], "CC");

    EXPECT_TRUE(tab_parser.get_next_record(strm, record));
    EXPECT_EQ(record.size(), 3u);
    EXPECT_EQ(record[0], "DD");
    EXPECT_EQ(record[1], "EE");
    EXPECT_EQ(record[2], "FF");
}
EN

回答 1

Code Review用户

回答已采纳

发布于 2020-06-27 18:06:10

代码级别太低,缺乏功能。

使用std::getline并从流中提取整条线将更加惯用(而且可能会根据具体情况而更有效)。然后,您可以通过字符串的,函数一个接一个地找到分隔符find_first_of,并将行分隔成字符串数组。

当csv文件中有大行时,可能缺少这种方法,但是获取字符串向量的整个设计是这种情况下更大的受害者。考虑使用容器缓冲区和字符串视图向量。

缺乏功能:有时您了解csv文件的格式,并希望执行内部转换,而不是获取字符串的向量。如果您能够创建几个满足常见情况的方法,您需要支持它,那么它将把您的csv库转换成一个可用的库。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/244620

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档