/*csv格式是以逗号分隔列的。
* 每行内容是以换行符作为一行的。
* 如果内容中包含换行符,则整个内容要以双引号括起来。
* 如果内容中包含逗号,则整个内容要以双引号括起来。
* 如果内容中又有双引号,则会是2个双引号连在一起表示一个双引号。
* 每个字段的左右2边的空白字符可以忽略。
* 还有其他复杂的情况,暂时不考虑
*/
/*针对以上csv的复杂多变的格式,我们这里只考虑以下几种情况吧:
* 1. 逗号分隔列。
* 2. 某个列的内容可能多行显示。
* 3. 某个列可能存在逗号、双引号、换行。
*/
QList<QStringList> parseCSVFile(const QString &strFile)
{
QList<QStringList> allRecordSets;
QFile file(strFile);
if (!file.open(QFile::ReadOnly)) return allRecordSets;
QTextStream ins(&file);
bool bInQuote = false;
const QChar chQuote('\"');
const QChar chEnter('\n');
const QChar chComma(',');
int iFileLineCount = 0;
QStringList currentRecordset;
QString strFieldData;
while (!ins.atEnd())
{
++iFileLineCount;
const QString strLine = ins.readLine()+'\n'; //QTextStream::readLine()去掉了换行符
for (int i=0; i<strLine.size(); ++i)
{
const QChar c = strLine.at(i);
if (chComma == c)
{
if (bInQuote)
{
strFieldData.append(c);
}
else
{
strFieldData = strFieldData.trimmed(); //去掉左右两边的空白字符
currentRecordset.append(strFieldData);
strFieldData = "";
}
}
else if (chEnter == c)
{
if (bInQuote)
{
strFieldData.append(c);
}
else
{
strFieldData = strFieldData.trimmed(); //去掉左右两边的空白字符
currentRecordset.append(strFieldData);
allRecordSets.append(currentRecordset);
strFieldData = "";
currentRecordset.clear();
}
}
else if (chQuote == c)
{
//由于strLine的最后一个字符一定是'\n',所以strLine.at(i+1)肯定不会越界,所以无需判断越界。
if (bInQuote)
{//当前字符是双引号,且最开始已经有双引号,则当前字符可能是字段内容,也可能是字段结束位置
QChar next = strLine.at(i+1);
if (chQuote == next)
{//当前字符的下一个字符是双引号,说明是字段内容的一部分
strFieldData.append(c);
++i;
}
else if (chEnter == next)
{//当前字符的下一个字符是换行,说明当前的一条记录解析完毕
bInQuote = false;
}
else if (chComma == next)
{//当前字符的下一个字符是逗号,说明当前字段解析完毕
bInQuote = false;
}
else if (next.isSpace())
{//双引号结束的情况下,如果当前字符的下一个字符是空白字符,则忽略
for(++i;i<strLine.size();)
{
next = strLine.at(i);
if (next.isSpace() && next!=chEnter)
{//是空白字符但不是换行
++i;
}
else
{
--i; //便于下一个解析,所以指针回退。
break;
}
}
bInQuote = false;
}
else
{//说明格式有问题
qDebug().noquote().nospace()<<QString("the csv file(%1)(line=%2) has format error").arg(strFile).arg(iFileLineCount);
strFieldData.append(c);
}
}
else
{//表示字段的开始
bInQuote = true;
}
}
else
{
strFieldData.append(c);
}
}
}
return allRecordSets;
}