Qt下csv的讀寫封裝
阿新 • • 發佈:2018-12-31
概述
csv檔案作為簡單的格式化文字檔案,隨著資料探勘和python的普及突然就又火起來了,工作中發現許多資料互動由原來的xml又改為通過csv檔案進行互動,csv檔案有個最大的缺點是單個單元格里不能出現換行,如果是單純的資料互動,csv的確是最簡單的格式化方式。
csv把每個單元資料用逗號隔開,但某些情況下需要注意的是,遇到一個單元內容有包含引號"
和逗號,
時是需要轉義的。否則會造成格式混亂,本文的目的是封裝一個簡單好用的csv流式輸入輸出,類名SACsvStream
,實現如下效果輸出csv檔案::
QFile file;
……//open file
double d = 0.111;
float f = 0.343;
int i = 1212;
QString str = "這是一個文字";
SACsvStream csv(&file);
csv << d << f << i << str << endl;
實現輸出:
0.111,0.343,1212,這是一個文字
關鍵點
csv的解析
其實匯出csv和匯出txt唯一的區別就是對csv的一些特殊情況需要轉義操作,如遇到逗號遇到引號的情況
在csv中,遇到逗號需要把整個內容用引號擴起來
遇到引號,使用兩個引號代表一個引號
如這是一個"複合文字"文字:1,2,4,5,czyt1988
"這是一個""複合文字""文字:1,2,4,5,czyt1988"
操作符<<對endl的處理
為了方便使用,過載了operator <<
操作符,這裡還需要注意一點,就是處理endl
在operator <<
操作符中,endl
其實是一個函式指標,為了能讓operator <<
操作符支援endl
需要指定一個函式指標的operator <<
操作符過載
例如:類SACsvStream 為了支援operator <<
操作符識別endl,需要定義一個函式指標:
typedef SACsvStream & (*SACsvWriterFunction)(SACsvStream &);
這個然後需要讓operator <<
操作符處理此函式指標:
SACsvStream &operator<<(SACsvStream &s, SACsvWriterFunction f)
{
return (*f)(s);
}
這樣就可以寫類似SACsvStream &endl(SACsvStream &s);
這樣的函式,實現需要的功能
SACsvStream &endl(SACsvStream &s)
{
//此處進行換行
return s;
}
這樣你不僅可以實現類似endl,你可以實現任何名稱的過載
如:
SACsvStream &wo(SACsvStream &s)
{
s << "我";
return s;
}
SACsvStream &zui(SACsvStream &s)
{
s << "最";
return s;
}
SACsvStream &shuai(SACsvStream &s)
{
s << "帥";
return s;
}
然後:
SACsvStream csv(&file);
csv << wo << zui << shuai;
輸出結果大家自己默唸
實現
標頭檔案:
#ifndef QCSVSTREAM_H
#define QCSVSTREAM_H
class QTextStream;
class QFile;
class SACsvStreamPrivate;
#include <QString>
///
/// \brief 寫csv檔案類支援
/// \author czy
/// \date 2016-08-10
///
class SACsvStream
{
public:
SACsvStream(QTextStream* txt);
SACsvStream(QFile* txt);
virtual ~SACsvStream();
//轉換為標識csv字元
static QString toCsvString(const QString& rawStr);
//把一行要用逗號分隔的字串轉換為一行標準csv字串
static QString toCsvStringLine(const QStringList& sectionLine);
//解析一行csv字元
static QStringList fromCsvLine(const QString &lineStr);
SACsvStream & operator << (const QString &str);
SACsvStream & operator << (int d);
SACsvStream & operator << (double d);
SACsvStream & operator << (float d);
//另起一行
void newLine();
//獲取輸入輸出流
QTextStream* stream() const;
//讀取並解析一行csv字串
QStringList readCsvLine();
//判斷是否到檔案末端
bool atEnd() const;
private:
static int advquoted(const QString &s, QString &fld, int i);
static int advplain(const QString &s, QString &fld, int i);
private:
QScopedPointer<SACsvStreamPrivate> d_p;
};
typedef SACsvStream & (*SACsvWriterFunction)(SACsvStream &);
inline SACsvStream &operator<<(SACsvStream &s, SACsvWriterFunction f)
{
return (*f)(s);
}
SACsvStream &endl(SACsvStream &s);
#endif // QCSVSTREAM_H
實現檔案:
#include "SACsvStream.h"
#include <QTextStream>
#include <QFile>
class SACsvStreamPrivate
{
SACsvStream* Parent;
QTextStream* m_txt;
QScopedPointer<QTextStream> m_pfile;
bool m_isStartLine;
public:
SACsvStreamPrivate(SACsvStream* p):Parent(p)
,m_txt(nullptr)
,m_pfile(nullptr)
,m_isStartLine(true)
{
}
void setTextStream(QTextStream* st)
{
m_txt = st;
}
QTextStream* stream()
{
return m_txt;
}
QTextStream& streamRef()
{
return (*m_txt);
}
bool isStartLine() const
{
return m_isStartLine;
}
void setStartLine(bool on)
{
m_isStartLine = on;
}
void setFile(QFile *txt)
{
m_pfile.reset(new QTextStream(txt));
m_txt = m_pfile.data();
}
QTextStream& formatTextStream()
{
if(!isStartLine())
{
streamRef()<<',';
}
else
{
setStartLine(false);
}
return streamRef();
}
};
SACsvStream::SACsvStream(QTextStream* txt)
:d_p(new SACsvStreamPrivate(this))
{
d_p->setTextStream(txt);
}
SACsvStream::SACsvStream(QFile *txt)
:d_p(new SACsvStreamPrivate(this))
{
d_p->setFile(txt);
}
SACsvStream::~SACsvStream()
{
}
///
/// \brief 把字串裝換為標準csv一個單元得字串,對應字串原有的逗號會進行裝換
///
/// csv的原則是:
///
/// - 如果字串有逗號,把整個字串前後用引號括起來
/// - 如果字串有引號",引號要用兩個引號表示轉義""
/// \param rawStr 原有資料
/// \return 標準的csv單元字串
///
QString SACsvStream::toCsvString(const QString &rawStr)
{
//首先查詢有沒有引號,如果有,則把引號替換為兩個引號
QString res = rawStr;
res.replace("\"","\"\"");
if(res.contains(','))
{
//如果有逗號,在前後把整個句子用引號包含
res = ('\"' + res + '\"');
}
return res;
}
///
/// \brief 把一行要用逗號分隔的字串轉換為一行標準csv字串
/// \param sectionLine 如:xxx,xxxx,xxxxx 傳入{'xxx','xxxx','xxxxx'}
/// \return 標準的csv字串不帶換行符
///
QString SACsvStream::toCsvStringLine(const QStringList §ionLine)
{
QString res;
int size = sectionLine.size();
for(int i=0;i<size;++i)
{
if(0 == i)
{
res += SACsvStream::toCsvString(sectionLine[i]);
}
else
{
res += ("," + SACsvStream::toCsvString(sectionLine[i]));
}
}
return res;
}
///
/// \brief 把一句csv格式的內容解析
/// \param lineStr
/// \return
///
QStringList SACsvStream::fromCsvLine(const QString &lineStr)
{
if(lineStr.isEmpty())
{
return QStringList();
}
QStringList res;
QString str;
int i, j=0;
int col = 0;
i = 0;
do {
if (i < lineStr.length() && lineStr[i] == '\"')
j = advquoted(lineStr, str, ++i); // skip quote
else
j = advplain(lineStr, str, i);
res.push_back (str);
++col;
i = j + 1;
} while (j < lineStr.length());
return res;
}
///
/// \brief 寫csv檔案內容,字元會自動轉義為csv檔案支援的字串,不需要轉義
///
/// 例如csv檔案:
/// \par
/// 12,txt,23,34
/// 45,num,56,56
/// 寫入的方法為:
/// \code
/// .....
/// QCsvWriter csv(&textStream);
/// csv<<"12"<<"txt"<<"23";
/// csv.endLine("34");
/// csv<<"45"<<"num"<<"56";
/// csv.endLine("56");
/// \endcode
///
/// \param str 需要寫入的csv檔案一個單元得字串
/// \return
///
SACsvStream &SACsvStream::operator <<(const QString &str)
{
d_p->formatTextStream()<<toCsvString(str);
return *this;
}
SACsvStream &SACsvStream::operator <<(int d)
{
d_p->formatTextStream()<<d;
return *this;
}
SACsvStream &SACsvStream::operator <<(double d)
{
d_p->formatTextStream()<<d;
return *this;
}
SACsvStream &SACsvStream::operator <<(float d)
{
d_p->formatTextStream()<<d;
return *this;
}
///
/// \brief 換行
///
void SACsvStream::newLine()
{
d_p->setStartLine(true);
d_p->streamRef()<<endl;
}
///
/// \brief 獲取輸入輸出流
/// \return
///
QTextStream *SACsvStream::stream() const
{
return d_p->stream();
}
///
/// \brief 讀取一行csv檔案
/// \return
///
QStringList SACsvStream::readCsvLine()
{
return fromCsvLine(stream()->readLine());
}
bool SACsvStream::atEnd() const
{
return stream()->atEnd();
}
int SACsvStream::advquoted(const QString &s, QString &fld, int i)
{
int j;
fld = "";
for (j = i; j < s.length()-1; j++)
{
if (s[j] == '\"' && s[++j] != '\"')
{
int k = s.indexOf (',', j);
if (k < 0 ) // 沒找到逗號
k = s.length();
for (k -= j; k-- > 0; )
fld += s[j++];
break;
}
fld += s[j];
}
return j;
}
int SACsvStream::advplain(const QString &s, QString &fld, int i)
{
int j;
j = s.indexOf(',', i); // 查詢,
if (j < 0) // 沒找到
j = s.length();
fld = s.mid (i,j-i);//提取內容
return j;
}
///
/// \brief endl
/// \param s
/// \return
///
SACsvStream &endl(SACsvStream &s)
{
s.newLine();
return s;
}
示例
寫入測試:
QFile file("SACsvStreamTest.csv");
if(!file.open(QIODevice::WriteOnly))
{
return;
}
double d = 0.111;
float f = 0.343;
int i = 1212;
QString str1 = QStringLiteral("這是一個文字");
QString str2 = QStringLiteral("這是一個帶,號文字");
QString str3 = QStringLiteral("這是一個帶\"文字");
QString str4 = QStringLiteral("這是一個\"複合文字\"文字:1,2,4,5,czyt1988");
SACsvStream csv(&file);
csv << d << f << i << str1 << str2 << str3 << str4 << endl;
csv << str4 << str3 << str2 << str1 << i << f << d << endl;
file.close();
結果:
0.111,0.343,1212,這是一個文字,"這是一個帶,號文字",這是一個帶""文字,"這是一個""複合文字""文字:1,2,4,5,czyt1988"
"這是一個""複合文字""文字:1,2,4,5,czyt1988",這是一個帶""文字,"這是一個帶,號文字",這是一個文字,1212,0.343,0.111
讀取示例:
QFile file("SACsvStreamTest.csv");
if(!file.open(QIODevice::ReadOnly|QIODevice::Text))
{
return -1;
}
SACsvStream csv(&file);
while(!csv.atEnd())
{
QStringList res = csv.readCsvLine();
qDebug().noquote() << res;
}
輸出:
(0.111, 0.343, 1212, 這是一個文字, 這是一個帶,號文字, 這是一個帶""文字, 這是一個"複合文字"文字:1,2,4,5,czyt1988, )
(這是一個"複合文字"文字:1,2,4,5,czyt1988, 這是一個帶""文字, 這是一個帶,號文字, 這是一個文字, 1212, 0.343, 0.111)