diff --git a/src/qtgui/restable.cpp b/src/qtgui/restable.cpp index 946356c2..d73eb85e 100644 --- a/src/qtgui/restable.cpp +++ b/src/qtgui/restable.cpp @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include "recoll.h" #include "refcntr.h" @@ -143,8 +145,9 @@ void ResTableDetailArea::createPopupMenu(const QPoint& pos) //// Data model methods //// -// Routines used to extract named data from an Rcl::Doc. The basic one just uses the meta map. Others -// (ie: the date ones) need to do a little processing +// Routines used to extract named data from an Rcl::Doc. The basic one +// just uses the meta map. Others (ie: the date ones) need to do a +// little processing static string gengetter(const string& fld, const Rcl::Doc& doc) { map::const_iterator it = doc.meta.find(fld); @@ -362,6 +365,38 @@ QVariant RecollModel::data(const QModelIndex& index, int role) const return QString::fromUtf8(lr.front().c_str()); } +void RecollModel::saveAsCSV(FILE *fp) +{ + if (m_source.isNull()) + return; + + int cols = columnCount(); + int rows = rowCount(); + vector tokens; + + for (int col = 0; col < cols; col++) { + QString qs = headerData(col, Qt::Horizontal,Qt::DisplayRole).toString(); + tokens.push_back((const char *)qs.toUtf8()); + } + string csv; + stringsToCSV(tokens, csv); + fprintf(fp, "%s\n", csv.c_str()); + tokens.clear(); + + for (int row = 0; row < rows; row++) { + Rcl::Doc doc; + if (!m_source->getDoc(row, doc)) { + continue; + } + for (int col = 0; col < cols; col++) { + tokens.push_back(m_getters[col](m_fields[col], doc)); + } + stringsToCSV(tokens, csv); + fprintf(fp, "%s\n", csv.c_str()); + tokens.clear(); + } +} + // This gets called when the column headers are clicked void RecollModel::sort(int column, Qt::SortOrder order) { @@ -594,6 +629,27 @@ void ResTable::resetSource() setDocSource(RefCntr()); } +void ResTable::saveAsCSV() +{ + LOGDEB(("ResTable::saveAsCSV\n")); + if (!m_model) + return; + QString s = + QFileDialog::getSaveFileName(this, //parent + tr("Save table to CSV file"), + QString::fromLocal8Bit(path_home().c_str()) + ); + const char *tofile = s.toLocal8Bit(); + FILE *fp = fopen(tofile, "w"); + if (fp == 0) { + QMessageBox::warning(0, "Recoll", + tr("Can't open/create file: ") + s); + return; + } + m_model->saveAsCSV(fp); + fclose(fp); +} + // This is called when the sort order is changed from another widget void ResTable::onSortDataChanged(DocSeqSortSpec spec) { @@ -806,6 +862,9 @@ void ResTable::createHeaderPopupMenu(const QPoint& pos) popup->addAction(tr("&Reset sort"), this, SLOT(resetSort())); popup->addSeparator(); + popup->addAction(tr("&Save as CSV"), this, SLOT(saveAsCSV())); + popup->addSeparator(); + popup->addAction(tr("&Delete column"), this, SLOT(deleteColumn())); popup->addSeparator(); diff --git a/src/qtgui/restable.h b/src/qtgui/restable.h index 47e6f7da..b1cb6140 100644 --- a/src/qtgui/restable.h +++ b/src/qtgui/restable.h @@ -42,8 +42,8 @@ public: int role = Qt::DisplayRole ) const; virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole ) const; + virtual void saveAsCSV(FILE *fp); virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); - // Specific methods virtual void readDocSource(); virtual void setDocSource(RefCntr nsource); @@ -136,6 +136,7 @@ public slots: virtual void deleteColumn(); virtual void addColumn(); virtual void resetSort(); // Revert to natural (relevance) order + virtual void saveAsCSV(); virtual void linkWasClicked(const QUrl&); virtual void makeRowVisible(int row); diff --git a/src/utils/smallut.cpp b/src/utils/smallut.cpp index f50b427d..6dd28f5d 100644 --- a/src/utils/smallut.cpp +++ b/src/utils/smallut.cpp @@ -320,6 +320,36 @@ template void stringsToString >(const list &, string &); template void stringsToString >(const vector &,string &); template void stringsToString >(const set &, string &); +template void stringsToCSV(const T &tokens, string &s, + char sep) +{ + s.erase(); + for (typename T::const_iterator it = tokens.begin(); + it != tokens.end(); it++) { + bool needquotes = false; + if (it->empty() || + it->find_first_of(string(1, sep) + "\"\n") != string::npos) + needquotes = true; + if (it != tokens.begin()) + s.append(1, sep); + if (needquotes) + s.append(1, '"'); + for (unsigned int i = 0; i < it->length(); i++) { + char car = it->at(i); + if (car == '"') { + s.append(2, '"'); + } else { + s.append(1, car); + } + } + if (needquotes) + s.append(1, '"'); + } +} +template void stringsToCSV >(const list &, string &, char); +template void stringsToCSV >(const vector &,string &, + char); + void stringToTokens(const string& str, vector& tokens, const string& delims, bool skipinit) { @@ -1045,7 +1075,7 @@ int main(int argc, char **argv) cerr << "[" << *it << "] "; cerr << endl; exit(0); -#elif 1 +#elif 0 if (argc <=0 ) { cerr << "Usage: smallut " << endl; exit(1); @@ -1126,7 +1156,15 @@ int main(int argc, char **argv) in = "a: %a title: %(title) pcpc: %% %"; pcSubst(in, out, substs); cout << "After map clear: " << in << " => " << out << endl; - +#elif 1 + list tokens; + tokens.push_back(""); + tokens.push_back("a,b"); + tokens.push_back("simple value"); + tokens.push_back("with \"quotes\""); + string out; + stringsToCSV(tokens, out); + cout << "CSV line: [" << out << "]" << endl; #endif } diff --git a/src/utils/smallut.h b/src/utils/smallut.h index 45466bd4..8fb06440 100644 --- a/src/utils/smallut.h +++ b/src/utils/smallut.h @@ -93,6 +93,13 @@ template bool stringToStrings(const string& s, T &tokens, */ template void stringsToString(const T &tokens, string &s); +/** + * Strings to CSV string. tokens containing the separator are quoted (") + * " inside tokens is escaped as "" ([word "quote"] =>["word ""quote"""] + */ +template void stringsToCSV(const T &tokens, string &s, + char sep = ','); + /** * Split input string. No handling of quoting */