【问题标题】:Parsing through a csv file in Qt通过 Qt 中的 csv 文件进行解析
【发布时间】:2014-12-05 14:55:54
【问题描述】:

是否有人熟悉如何解析 csv 文件并将其放入字符串列表中。现在我将整个 csv 文件放入字符串列表中。我想弄清楚是否有办法只获得第一列。

#include "searchwindow.h"
#include <QtGui/QApplication>

#include <QApplication>
#include <QStringList>
#include <QLineEdit>
#include <QCompleter>
#include <QHBoxLayout>
#include <QWidget>
#include <QLabel>

#include <qfile.h>
#include <QTextStream>


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QWidget *widget = new QWidget();
    QHBoxLayout *layout = new QHBoxLayout();

    QStringList wordList;

    QFile f("FlightParam.csv");
    if (f.open(QIODevice::ReadOnly))
    {
        //file opened successfully
        QString data;
        data = f.readAll();
        wordList = data.split(',');

        f.close();
    }

    QLabel *label = new QLabel("Select");
    QLineEdit *lineEdit = new QLineEdit;
    label->setBuddy(lineEdit);

    QCompleter *completer = new QCompleter(wordList);
    completer->setCaseSensitivity(Qt::CaseInsensitive); //Make caseInsensitive selection

    lineEdit->setCompleter(completer);

    layout->addWidget(label);
    layout->addWidget(lineEdit);

    widget->setLayout(layout);
    widget->showMaximized();

    return a.exec();
}

【问题讨论】:

    标签: c++ qt csv qt5 qfile


    【解决方案1】:

    你去吧:

    FlightParam.csv

    1,2,3,
    4,5,6,
    7,8,9,
    

    main.cpp

    #include <QFile>
    #include <QStringList>
    #include <QDebug>
    
    int main()
    {
        QFile file("FlightParam.csv");
        if (!file.open(QIODevice::ReadOnly)) {
            qDebug() << file.errorString();
            return 1;
        }
    
        QStringList wordList;
        while (!file.atEnd()) {
            QByteArray line = file.readLine();
            wordList.append(line.split(',').first());
        }
    
        qDebug() << wordList;
    
        return 0;
    }
    

    main.pro

    TEMPLATE = app
    TARGET = main
    QT = core
    SOURCES += main.cpp
    

    构建并运行

    qmake && make && ./main
    

    输出

    ("1", "4", "7")
    

    【讨论】:

    • 这行得通。唯一的问题是,在我的文件中,第一列是编号的。我尝试用 .second 替换 wordList.append(line.split(',').first() 并且它不起作用。qt 是否有另一个内置函数可以让您获得第二列?
    • @user3878223: at(1).
    • 这不适用于包含包含逗号的值的行。例如,在‘1, "2, 3", 4’中,第二列是‘2,3’。
    • @Gallaecio:确实。很遗憾,这里的其他几个不错的答案似乎付出了更多努力来正确解析输入,但还没有获得更多投票来超越这个快速-n-dirty 解决方案。
    • ` wordList.append(line.split(',').first());` 将在第一次出现 "a",2,3 时崩溃。
    【解决方案2】:

    您正在寻找的是QTextStream 类。它提供了各种文件读写接口。

    一个简单的例子:

    QStringList firstColumn;
    QFile f1("h:/1.txt");
    f1.open(QIODevice::ReadOnly);
    QTextStream s1(&f1);
    while (!s1.atEnd()){
      QString s=s1.readLine(); // reads line from file
      firstColumn.append(s.split(",").first()); // appends first column to list, ',' is separator
    }
    f1.close();
    

    或者是的,你可以做这样的事情,结果是一样的:

    wordList = f.readAll().split(QRegExp("[\r\n]"),QString::SkipEmptyParts); //reading file and splitting it by lines
    for (int i=0;i<wordList.count();i++) 
       wordList[i]=wordlist[i].split(",").first(); // replacing whole row with only first value
    f.close();    
    

    【讨论】:

    • 我认为QTextStream 是不必要的复杂化,QRegExp 版本相当丑陋。 :-)
    【解决方案3】:

    这是我经常使用的代码。我是作者,请按原样考虑,公共领域。它具有与CodeLurker's code 相似的功能集和概念,只是状态机的表示方式不同,代码更短一些。

    bool readCSVRow (QTextStream &in, QStringList *row) {
    
        static const int delta[][5] = {
            //  ,    "   \n    ?  eof
            {   1,   2,  -1,   0,  -1  }, // 0: parsing (store char)
            {   1,   2,  -1,   0,  -1  }, // 1: parsing (store column)
            {   3,   4,   3,   3,  -2  }, // 2: quote entered (no-op)
            {   3,   4,   3,   3,  -2  }, // 3: parsing inside quotes (store char)
            {   1,   3,  -1,   0,  -1  }, // 4: quote exited (no-op)
            // -1: end of row, store column, success
            // -2: eof inside quotes
        };
    
        row->clear();
    
        if (in.atEnd())
            return false;
    
        int state = 0, t;
        char ch;
        QString cell;
    
        while (state >= 0) {
    
            if (in.atEnd())
                t = 4;
            else {
                in >> ch;
                if (ch == ',') t = 0;
                else if (ch == '\"') t = 1;
                else if (ch == '\n') t = 2;
                else t = 3;
            }
    
            state = delta[state][t];
    
            switch (state) {
            case 0:
            case 3:
                cell += ch;
                break;
            case -1:
            case 1:
                row->append(cell);
                cell = "";
                break;
            }
    
        }
    
        if (state == -2)
            throw runtime_error("End-of-file found while inside quotes.");
    
        return true;
    
    }
    
    • 参数:in,一个QTextStream
    • 参数:row,将接收行的QStringList
    • 返回:true 如果读取了一行,false 如果 EOF。
    • 如果发生错误,则抛出:std::runtime_error

    它解析 Excel 样式的 CSV,适当地处理引号和双引号,并允许字段中的换行符。只要使用QFile::Text 打开文件,就可以正确处理 Windows 和 Unix 行尾。我不认为 Qt 支持老式 Mac 行尾,而且它不支持二进制模式未翻译的行尾,但在大多数情况下,这在这些天应该不是问题。

    其他说明:

    • 与 CodeLurker 的实现不同,如果 EOF 在引号内被击中,这会故意失败。如果您将状态表中的 -2 更改为 -1,那将是宽容的。
    • x"y"z 解析为xyz,不确定字符串中引号的规则是什么。我不知道这是否正确。
    • 性能和内存特性与 CodeLurker 相同(即非常好)。
    • 不支持 unicode (converts to ISO-5589-1),但更改为 QChar 应该很简单。

    例子:

    QFile csv(filename);
    csv.open(QFile::ReadOnly | QFile::Text);
    
    QTextStream in(&csv);
    QStringList row;
    while (readCSVRow(in, &row))
        qDebug() << row;
    

    【讨论】:

    • 现在有一个懂状态机的人!
    • @MichalLeon 感谢您的编辑。这不是完全正确(特别是当它应该将它包含在字段中时它会忽略引号内的 \r ),但我现在没有时间调整它。
    • @Jason C,你是绝对正确的,但我不认为控制字符“属于”CSV。没有具体的禁令,但我从未见过包含任何 127。
    • @MichałLeon 如果您在例如Excel 中的单个单元格或其他内容。
    • @Jason C“Excel 中的单个单元格或其他内容”破坏我的编辑。不要犹豫拒绝我的编辑。顺便说一句,“单元格内的新行”在 linux 上工作正常,即使它是 LF (0x0A)
    【解决方案4】:

    人们可能更喜欢这样做:

    QStringList MainWindow::parseCSV(const QString &string)
    {
        enum State {Normal, Quote} state = Normal;
        QStringList fields;
        QString value;
    
        for (int i = 0; i < string.size(); i++)
        {
            const QChar current = string.at(i);
    
            // Normal state
            if (state == Normal)
            {
                // Comma
                if (current == ',')
                {
                    // Save field
                    fields.append(value.trimmed());
                    value.clear();
                }
    
                // Double-quote
                else if (current == '"')
                {
                    state = Quote;
                    value += current;
                }
    
                // Other character
                else
                    value += current;
            }
    
            // In-quote state
            else if (state == Quote)
            {
                // Another double-quote
                if (current == '"')
                {
                    if (i < string.size())
                    {
                        // A double double-quote?
                        if (i+1 < string.size() && string.at(i+1) == '"')
                        {
                            value += '"';
    
                            // Skip a second quote character in a row
                            i++;
                        }
                        else
                        {
                            state = Normal;
                            value += '"';
                        }
                    }
                }
    
                // Other character
                else
                    value += current;
            }
        }
    
        if (!value.isEmpty())
            fields.append(value.trimmed());
    
        // Quotes are left in until here; so when fields are trimmed, only whitespace outside of
        // quotes is removed.  The outermost quotes are removed here.
        for (int i=0; i<fields.size(); ++i)
            if (fields[i].length()>=1 && fields[i].left(1)=='"')
            {
                fields[i]=fields[i].mid(1);
                if (fields[i].length()>=1 && fields[i].right(1)=='"')
                    fields[i]=fields[i].left(fields[i].length()-1);
            }
    
        return fields;
    }
    
    • 功能强大:处理带逗号、双双引号(表示双引号字符)和右侧空格的引用材料
    • 灵活:如果忘记最后一个字符串的最后一个引号也不会失败,并且可以处理更复杂的 CSV 文件;让您一次处理一行,而无需先读取内存中的整个文件
    • 简单:只需将此状态机放入您的代码中,右键单击 QtCreator 中的函数名称并选择 Refactor |添加私有声明,然后就可以了。
    • 高性能:准确处理 CSV 行的速度比对每个字符执行 RegEx 预测要快
    • 方便:不需要外部库
    • 易读:代码直观,万一你需要修改它。

    编辑:我终于可以用它来修剪字段前后的空格。引号内不修剪空格或逗号。否则,从字段的开头和结尾修剪所有空格。在对此感到困惑一段时间后,我想到了可以在场上留下引号的想法。因此可以修剪所有字段。这样,仅删除引号或文本前后的空格。然后添加了最后一步,去除以引号开头和结尾的字段的引号。

    这是一个或多或少具有挑战性的测试用例:

    QStringList sl=
    {
        "\"one\"",
        "  \" two \"\"\"  , \" and a half  ",
        "three  ",
        "\t  four"
    };
    
    for (int i=0; i < sl.size(); ++i)
        qDebug() << parseCSV(sl[i]);
    

    这个对应文件

    "one"
     " two """  , " and a half  
    three  
    <TAB>  four
    

    其中代表制表符;并且每一行依次输入到 parseCSV() 中。不要这样写 .csv 文件!

    它的输出是(其中 qDebug() 用\" 表示字符串中的引号并将内容放在引号和括号中):

    ("one")
    (" two \"", " and a half")
    ("three")
    ("four")
    

    您可以观察到引号和额外的空格保留在项目“二”的引号内。在“and a half”的格式错误的情况下,引号之前的空格和最后一个单词之后的空格被删除;但其他人不是。此例程中缺少终端空格可能表示缺少终端引号。字段中没有开始或结束的引号仅被视为字符串的一部分。如果没有开始,则不会从字段末尾删除引号。要在此处检测错误,只需检查以引号开头但不以引号结尾的字段;和/或在最后一个循环中包含引号但不以一个开头和结尾的一个。

    我知道,这超出了您的测试用例所需;但是对于 ? 的一个可靠的一般答案 - 也许对于其他找到它的人来说。

    改编自: https://github.com/hnaohiro/qt-csv/blob/master/csv.cpp

    【讨论】:

    • 先生,我爱你。请嫁给我。这段代码很棒。赞成。
    【解决方案5】:

    尝试使用qtcsv 库来读取和写入 csv 文件。示例:

    #include <QList>
    #include <QStringList>
    #include <QDir>
    #include <QDebug>
    
    #include "qtcsv/stringdata.h"
    #include "qtcsv/reader.h"
    #include "qtcsv/writer.h"
    
    int main()
    {
        // prepare data that you want to save to csv-file
        QStringList strList;
        strList << "one" << "two" << "three";
    
        QtCSV::StringData strData;
        strData.addRow(strList);
        strData.addEmptyRow();
        strData << strList << "this is the last row";
    
        // write to file
        QString filePath = QDir::currentPath() + "/test.csv";
        QtCSV::Writer::write(filePath, strData);
    
        // read data from file
        QList<QStringList> readData = QtCSV::Reader::readToList(filePath);
        for ( int i = 0; i < readData.size(); ++i )
        {
            qDebug() << readData.at(i).join(",");
        }
    
        return 0;
    }
    

    我试图让它变得小巧易用。有关库文档和其他代码示例,请参见 Readme 文件。

    【讨论】:

      【解决方案6】:
      lines = data.split('\n');
      

      然后

      for line in lines
         column1.add(line.split(',')[0])
      

      我不确定添加函数是否存在以添加到数组中 - 让我们调用第 1 列

      【讨论】:

      • “行”是什么值类型。我假设一个字符串列表。对吗?
      • 是的。它与您的 wordlist 变量相同 - 我只是给出想法
      • 通常对于这些简单的问题,算法(伪代码通常是一个好主意)不是主要问题,而是实现......
      猜你喜欢
      • 1970-01-01
      • 2021-03-04
      • 2015-05-08
      • 1970-01-01
      • 2018-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多