【问题标题】:Parsing CSV with empty values using C++ iostreams使用 C++ iostreams 解析具有空值的 CSV
【发布时间】:2015-12-01 23:24:35
【问题描述】:

我无法正确解析 CSV 文件。数据行中的某些值可以为空白,当我在任何值行中有空白条目时,my code 无法正常工作。如果没有空白条目,程序将返回以下结果:

Symbol: GOOG
Name: Googl Inc.
Price: $570.25
High Today: $570.25
Low Today: $560.35

Symbol: APPL
Name: Apple Inc.
Price: $123.25
High Today: $124.25
Low Today: $125.35

如果我使用以下 CSV 字符串运行相同的程序,程序会因断言错误而停止。这是由于解析器跳过了 adjacent ,, delimiters,因此数据行中的列数与标题中的列数不匹配。

std::stringstream ifs(
    "Symbol,Name,Price,High Today,Low Today\n"
    "GOOG,Googl Inc.,$570.25 ,$570.25 ,$560.35\n"
    "APPL,Apple Inc.,$123.25 ,,$125.35\n");

这是我的代码:

#include <iostream>
#include <vector>
#include <sstream>
#include <fstream>
#include <algorithm>
#include <cassert>
#include <locale>

// This ctype facet classifies commas and endlines as whitespace
struct csv_whitespace : std::ctype<char> {
    static const mask* make_table() {
        // make a copy of the "C" locale table
        static std::vector<mask> v(classic_table(), classic_table() + table_size);
        v[','] |= space;        // comma will be classified as whitespace
        v[' '] &= ~space;       // space will not be classified as whitespace
        return &v[0];
    }
    csv_whitespace(std::size_t refs = 0)
        : ctype(make_table(), false, refs)
    {}
};

static int row_end = std::ios_base::xalloc();

std::istream& record(std::istream& is) {
    while (std::isspace(is.peek(), is.getloc())) {
        int c(is.peek());
        is.ignore();
        if (c == '\n') {
            is.iword(row_end) = 1;
            is.setstate(std::ios_base::failbit);
        }
    }
    return is;
}

template<class Iter1, class Iter2, class Function>
void for_each_binary_range(Iter1 first1, Iter1 last1,
    Iter2 first2, Iter2 last2, Function f)
{
    assert(std::distance(first1, last1) <=
        std::distance(first2, last2));
    while (first1 != last1) {
        f(*first1++, *first2++);
    }
}

int main(int argc, char *argv[])
{
    std::stringstream ifs(
        "Symbol,Name,Price,High Today,Low Today\n"
        "GOOG,Googl Inc.,$570.25 ,$570.25 ,$560.35\n"
        "APPL,Apple Inc.,$123.25 ,$124.25 ,$125.35\n");
    //std::ifstream ifs("c:\\temp\\csvfile.csv", std::ios::in);
    std::vector<std::string> keys, values;
    ifs.imbue(std::locale(ifs.getloc(), new csv_whitespace));
    bool bHeaderProcessed = false;
    for (std::string item;;) {
        if (ifs >> record >> item) {
            if (!bHeaderProcessed) {
                keys.push_back(item);
            } else {
                values.push_back(item);
            }
        } else if (ifs.eof()) {
            // catch case where last line does not have trailing \n
            if (!values.empty()) {
                for_each_binary_range(std::begin(keys), std::end(keys),
                    std::begin(values), std::end(values),
                    [&](std::string const& key, std::string const& value) {
                    std::cout << key << ": " << value << std::endl;
                    std::cout << std::endl;
                });
                values.clear();
            }
            break;
        } else if (ifs.iword(row_end)) {
            // reset eol flag & clear stream state
            ifs.iword(row_end) = 0;
            // clear the fail-bit so we can stream more values
            ifs.clear();
            bHeaderProcessed = true;
            if (!values.empty()) {
                for_each_binary_range(std::begin(keys), std::end(keys),
                    std::begin(values), std::end(values),
                    [&](std::string const& key, std::string const& value) {
                        std::cout << key << ": " << value << std::endl;
                    });
                values.clear();
                std::cout << std::endl;
            }
        } else {
            break;
        }
    }
    return -1;
}

我基于我的原始代码记录良好here。不幸的是,问题的答案(带有现场演示here)似乎无法处理多行的情况,我无法处理令牌为空的情况。

我的版本将每一行打印为一系列名称/值,它还处理多行或一行未以新行结尾的情况。

上面的链接答案中很好地描述了逻辑

有人可以指出如何处理我在 csv 的数据行中有相邻分隔符的情况。

【问题讨论】:

  • 我们需要查看您的代码来帮助您。经过一整天的回答问题,我们的精神力量现在有点弱。 ;)
  • @johnco3 @ user4581301 不,这就是你所说的读取失败。我不习惯加粗的蓝色链接,我想我掩盖了它们。对于我的这个错误,我深表歉意。
  • 别担心,@soulsabr。稍后我将把我评论的重要内容移植到新评论并删除旧评论,这样我们就可以从头开始。
  • 伙计们,我通过将代码嵌入到 clafity 中来解决了这个问题 - 道歉和好点,感谢建设性的 cmets
  • 这一点已经变得毫无意义。谢谢你,@johnco3

标签: c++ csv iostream


【解决方案1】:

您的问题在于您期望数据的解析方式与数据的实际解析方式。双“,”被完全忽略,而不是推到值向量上。这意味着您的 values 数组的大小将是一个或多个。断言失败,因为键的大小被断言为

std::stringstream ifs(
    "Symbol,Name,,High Today,Low Today\n"
    "GOOG,Googl Inc.,$570.25 ,$570.25 ,$560.35\n"
    "APPL,Apple Inc.,$123.25 ,$124.25 ,$125.35\n");

尝试使用上述方法并观察输出。

一个简单但不优雅的解决方案是在每个“,”之间插入一个空格,这样程序就会选择它。有很多更好的解决方案,但这应该会让你继续前进。

编辑:感谢您的理解。

【讨论】:

  • 实际上,您的答案几乎可以解释我在问题中第二段的最后一句话。我知道空格破解,但是如果最后一个值为空,那么肮脏的解决方法也需要在行尾有一个空格 - 这在编辑器中并不明显。
  • 公平地说,我回答后你确实改变了问题。
  • 其实那条评论一直都有,我改了问题添加了源代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-25
  • 2011-10-12
  • 1970-01-01
  • 2015-03-05
相关资源
最近更新 更多