通过 Qt 中的 csv 文件进行解析

Posted

技术标签:

【中文标题】通过 Qt 中的 csv 文件进行解析【英文标题】:Parsing through a csv file in Qt 【发布时间】: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();

【问题讨论】:

【参考方案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 变量相同 - 我只是给出想法 通常对于这些简单的问题,算法(伪代码通常是一个好主意)不是主要问题,而是实现......

以上是关于通过 Qt 中的 csv 文件进行解析的主要内容,如果未能解决你的问题,请参考以下文章

Qt解析CSV文件

如何通过将csv文件与python中的其他csv文件进行比较来删除和替换csv文件中的列?

通过Qt中的程序对文本文件进行排序

fetch_csv使用

fetch_csv使用

fetch_csv使用