2 * Copyright (C) 2011, Jamie Thompson
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License as published by the Free Software Foundation; either
7 * version 3 of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public
15 * License along with this program; If not, see
16 * <http://www.gnu.org/licenses/>.
19 #include "CSVSymbianEventLogParser.h"
20 #include "EventTypes/PhoneCall.h"
27 #include <QStringList>
31 using namespace EventParsers;
36 inline bool operator()(const QPair<QChar, uint> &a, const QPair<QChar, uint> &b) const
38 return b.second < a.second;
42 const QString ExtractString(const QString &originalString)
44 QRegExp content("^[\"\']?(.*)?[\"\']?$");
45 content.indexIn(originalString.trimmed());
46 return content.cap(1);
49 iEventParser *CSVSymbianEventLogParser::IsValid(const Settings ¤tSettings, QFile &eventFile)
51 qDebug() << "Checking if a CSV call log file...";
53 QTextStream stream(&eventFile);
55 QString firstLineContent(stream.readLine());
57 if(firstLineContent.length() > 0)
59 // Count the non-alphanumeric characters used
60 QHash<QChar, uint> counts;
61 foreach(const QChar c, firstLineContent)
64 QList<QPair<QChar, uint> > orderedCounts;
65 orderedCounts.reserve(counts.size());
66 foreach(const QChar c, counts.keys())
67 if(!QChar(c).isLetterOrNumber())
68 orderedCounts.append(QPair<QChar, uint>(c, counts.value(c)));
70 qSort(orderedCounts.begin(), orderedCounts.end(), SortByValueDesc());
72 // Work around Q_FOREACH macro limitation when dealing with
73 // multi-typed templates (comma issue)
74 typedef QPair<QChar, uint> bodge;
75 foreach(bodge count, orderedCounts)
76 qDebug() << count.first << " = " << count.second;
79 // No-one would be mad enough to use quotation marks or apostrophes
80 // as their delimiter,but just in case, check the second most
81 // frequent character is present thr right number of times for
82 // the qutation marks to be present on every column heading (two
83 // per heading, less one as they're seperators)
84 if((orderedCounts.value(0).first == '"' || orderedCounts.value(0).first == '\'')
85 && ((orderedCounts.value(0).second / 2) - 1 == orderedCounts.value(1).second ))
88 delim = orderedCounts.value(1).first;
91 delim = orderedCounts.value(0).first;
93 // Check we have the essential fields we need, and grab their
95 QStringList requiredHeadings;
96 requiredHeadings << "etype" << "etime" << "remote" << "direction"
97 << "duration" << "number" << "data";
99 EventParsers::CSVSymbianEventLogParser::ColumnIndicesHash headingPositions;
100 headingPositions.reserve(requiredHeadings.count());
102 QStringList headings(QString(firstLineContent).split(delim, QString::KeepEmptyParts, Qt::CaseSensitive));
103 int numColumnsPerRecord(headings.count());
104 for(QStringList::size_type i(0); i < headings.count(); ++i)
106 QString heading(ExtractString(headings.value(i)));
107 qDebug() << headings.value(i) << " : " << heading;
109 // Check over the required headings
110 foreach(QString requiredHeading, requiredHeadings)
112 if(heading.toLower() == requiredHeading)
114 headingPositions[requiredHeading] = i;
115 requiredHeadings.removeOne(requiredHeading);
120 // If we found all of the required headings, continue
121 if(requiredHeadings.count() == 0)
122 return new EventParsers::CSVSymbianEventLogParser(currentSettings, eventFile.fileName(), delim, numColumnsPerRecord, headingPositions);
128 CSVSymbianEventLogParser::CSVSymbianEventLogParser(const Settings &settings, const QString &filename, const QChar delimiter, const int numColumnsPerRecord, const ColumnIndicesHash &headingIndices)
129 : m_Settings(settings), m_Delimiter(delimiter), m_NumColumnsPerRecord(numColumnsPerRecord), m_HeadingIndices(headingIndices)
133 EventTypes::EventFromFileList CSVSymbianEventLogParser::ParseFile(QFile &eventFile, const QList<uint> &recordsToReturn)
135 qDebug() << "CSV Parsing NYI!";
136 EventTypes::EventFromFileList fileEvents;
139 QSet<uint> recordsToReturnSet(QSet<uint>::fromList(recordsToReturn));
142 uint recordNumber(0);
145 // Read the first line
146 QTextStream stream(&eventFile);
147 QString firstLineContent(stream.readLine());
148 QStringList firstLineValues(QString(firstLineContent).split(m_Delimiter));
149 if(firstLineValues.count() != m_NumColumnsPerRecord)
150 throw new std::runtime_error(QString("Unexpected number of columns (%1, expected %2) on line %3 of %4")
151 .arg(firstLineValues.count())
152 .arg(m_NumColumnsPerRecord)
154 .arg(eventFile.fileName()).toStdString());
157 // Read the main body of the file
158 while(!stream.atEnd())
160 QStringList lineValues(QString(stream.readLine()).split(m_Delimiter));
162 // Make sure we have enough columns (i.e. handle newlines in values)
163 while(lineValues.count() < m_NumColumnsPerRecord)
165 lineValues.append(QString(stream.readLine()).split(m_Delimiter));
169 if(recordsToReturnSet.count() == 0 || recordsToReturnSet.contains(recordNumber))
172 int eType(lineValues.at(m_HeadingIndices.value("etype")).toUInt(&bOK));
173 // We're only interested in phone calls
174 if(bOK && eType == 0)
176 qDebug() << "Parsing event from line #" << lineNumber << ". Values: " << lineValues;
178 QDateTime eTime(QDateTime::fromString(lineValues.at(m_HeadingIndices.value("etime")), "dd/MM/yyyy h:mm:ss ap"));
179 int duration(lineValues.at(m_HeadingIndices.value("duration")).toInt(&bOK));
181 Settings::eDirection direction(lineValues.at(m_HeadingIndices.value("direction")) == "0"
183 : Settings::OUTGOING);
185 qDebug() << QString("Unable to parse '%1' as a duration. Skipping record.")
186 .arg(lineValues.at(m_HeadingIndices.value("duration")));
189 QString number(ExtractString(lineValues.at(m_HeadingIndices.value("number"))));
190 QString data(ExtractString(lineValues.at(m_HeadingIndices.value("data"))));
192 QSharedPointer<EventTypes::iEvent> newEvent(new EventTypes::PhoneCall(
198 fileEvents.append(EventTypes::EventFromFile(newEvent, recordNumber));
205 qDebug() << QString("File pos: %1, bAvail: %2, canReadLine: %3").arg(eventFile.pos()).arg(eventFile.bytesAvailable()).arg(eventFile.canReadLine());
206 qDebug() << fileEvents.count() << " events loaded from file";