File indexing completed on 2024-05-12 05:10:09

0001 /***************************************************************************
0002     Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "csvexporter.h"
0026 #include "../collection.h"
0027 #include "../core/filehandler.h"
0028 
0029 #include <KLocalizedString>
0030 #include <KConfigGroup>
0031 
0032 #include <QLineEdit>
0033 #include <QGroupBox>
0034 #include <QCheckBox>
0035 #include <QRadioButton>
0036 #include <QGridLayout>
0037 #include <QVBoxLayout>
0038 #include <QLabel>
0039 
0040 using namespace Tellico;
0041 using Tellico::Export::CSVExporter;
0042 
0043 CSVExporter::CSVExporter(Data::CollPtr coll_) : Tellico::Export::Exporter(coll_),
0044     m_includeTitles(true),
0045     m_delimiter(QStringLiteral(",")),
0046     m_colDelimiter(QStringLiteral(":")),
0047     m_rowDelimiter(QStringLiteral("|")),
0048     m_widget(nullptr),
0049     m_checkIncludeTitles(nullptr),
0050     m_radioComma(nullptr),
0051     m_radioSemicolon(nullptr),
0052     m_radioTab(nullptr),
0053     m_radioOther(nullptr),
0054     m_editOther(nullptr),
0055     m_colDelimiterEdit(nullptr),
0056     m_rowDelimiterEdit(nullptr) {
0057 }
0058 
0059 QString CSVExporter::formatString() const {
0060   return QStringLiteral("CSV");
0061 }
0062 
0063 QString CSVExporter::fileFilter() const {
0064   return i18n("CSV Files") + QLatin1String(" (*.csv)") + QLatin1String(";;") + i18n("All Files") + QLatin1String(" (*)");
0065 }
0066 
0067 QString& CSVExporter::escapeText(QString& text_) const {
0068   bool quotes = false;
0069   if(text_.contains(QLatin1Char('"'))) {
0070     quotes = true;
0071     // quotation marks will be escaped by using a double pair
0072     text_.replace(QLatin1Char('"'), QLatin1String("\"\""));
0073   }
0074   // if the text contains quotes or the delimiter, it needs to be surrounded by quotes
0075   if(quotes || text_.contains(m_delimiter) || text_.contains(QLatin1Char('\n'))) {
0076     text_.prepend(QLatin1Char('"'));
0077     text_.append(QLatin1Char('"'));
0078   }
0079   return text_;
0080 }
0081 
0082 bool CSVExporter::exec() {
0083   if(!collection()) {
0084     return false;
0085   }
0086 
0087   return FileHandler::writeTextURL(url(), text(), options() & ExportUTF8, options() & Export::ExportForce);
0088 }
0089 
0090 QString CSVExporter::text() const {
0091   QString text;
0092 
0093   if(m_includeTitles) {
0094     foreach(Data::FieldPtr fIt, fields()) {
0095       QString title = fIt->title();
0096       // because of Microsoft Excel bug, https://support.microsoft.com/kb/323626
0097       if(text.isEmpty() && title == QLatin1String("ID")) {
0098         title = QStringLiteral("Id");
0099       }
0100       text += escapeText(title) + m_delimiter;
0101     }
0102     // remove last delimiter
0103     text.truncate(text.length() - m_delimiter.length());
0104     text += QLatin1Char('\n');
0105   }
0106 
0107   FieldFormat::Request format = (options() & Export::ExportFormatted ?
0108                                                 FieldFormat::ForceFormat :
0109                                                 FieldFormat::AsIsFormat);
0110 
0111   const bool replaceColDelimiter = (m_colDelimiter != FieldFormat::columnDelimiterString());
0112   const bool replaceRowDelimiter = (m_rowDelimiter != FieldFormat::rowDelimiterString());
0113 
0114   foreach(Data::EntryPtr entryIt, entries()) {
0115     QStringList values;
0116     foreach(Data::FieldPtr fIt, fields()) {
0117       QString value = entryIt->formattedField(fIt, format);
0118       if(replaceColDelimiter) {
0119         value.replace(FieldFormat::columnDelimiterString(), m_colDelimiter);
0120       }
0121       if(replaceRowDelimiter) {
0122         value.replace(FieldFormat::rowDelimiterString(), m_rowDelimiter);
0123       }
0124       values += escapeText(value);
0125     }
0126     text += values.join(m_delimiter) + QLatin1Char('\n');
0127   }
0128   return text;
0129 }
0130 
0131 QWidget* CSVExporter::widget(QWidget* parent_) {
0132   if(m_widget && m_widget->parent() == parent_) {
0133     return m_widget;
0134   }
0135 
0136   m_widget = new QWidget(parent_);
0137   QVBoxLayout* l = new QVBoxLayout(m_widget);
0138 
0139   QGroupBox* gbox = new QGroupBox(i18n("CSV Options"), m_widget);
0140   QVBoxLayout* vlay = new QVBoxLayout(gbox);
0141 
0142   m_checkIncludeTitles = new QCheckBox(i18n("Include field titles as column headers"), gbox);
0143   m_checkIncludeTitles->setChecked(m_includeTitles);
0144   m_checkIncludeTitles->setWhatsThis(i18n("If checked, a header row will be added with the "
0145                                           "field titles."));
0146 
0147   QGroupBox* delimiterGroup = new QGroupBox(i18n("Delimiter"), gbox);
0148   QGridLayout* m_delimiterGroupLayout = new QGridLayout(delimiterGroup);
0149   m_delimiterGroupLayout->setAlignment(Qt::AlignTop);
0150   delimiterGroup->setWhatsThis(i18n("In addition to a comma, other characters may be used as "
0151                                     "a delimiter, separating each value in the file."));
0152 
0153   m_radioComma = new QRadioButton(delimiterGroup);
0154   m_radioComma->setText(i18n("Comma"));
0155   m_radioComma->setChecked(true);
0156   m_radioComma->setWhatsThis(i18n("Use a comma as the delimiter."));
0157   m_delimiterGroupLayout->addWidget(m_radioComma, 0, 0);
0158 
0159   m_radioSemicolon = new QRadioButton( delimiterGroup);
0160   m_radioSemicolon->setText(i18n("Semicolon"));
0161   m_radioSemicolon->setWhatsThis(i18n("Use a semi-colon as the delimiter."));
0162   m_delimiterGroupLayout->addWidget(m_radioSemicolon, 0, 1);
0163 
0164   m_radioTab = new QRadioButton(delimiterGroup);
0165   m_radioTab->setText(i18n("Tab"));
0166   m_radioTab->setWhatsThis(i18n("Use a tab as the delimiter."));
0167   m_delimiterGroupLayout->addWidget(m_radioTab, 1, 0);
0168 
0169   m_radioOther = new QRadioButton(delimiterGroup);
0170   m_radioOther->setText(i18n("Other"));
0171   m_radioOther->setWhatsThis(i18n("Use a custom string as the delimiter."));
0172   m_delimiterGroupLayout->addWidget(m_radioOther, 1, 1);
0173 
0174   m_editOther = new QLineEdit(delimiterGroup);
0175   m_editOther->setEnabled(m_radioOther->isChecked());
0176   m_editOther->setWhatsThis(i18n("A custom string, such as a colon, may be used as a delimiter."));
0177   m_delimiterGroupLayout->addWidget(m_editOther, 1, 2);
0178   QObject::connect(m_radioOther, &QAbstractButton::toggled,
0179                    m_editOther, &QWidget::setEnabled);
0180 
0181   if(m_delimiter == QLatin1String(",")) {
0182     m_radioComma->setChecked(true);
0183   } else if(m_delimiter == QLatin1String(";")) {
0184     m_radioSemicolon->setChecked(true);
0185   } else if(m_delimiter == QLatin1String("\t")) {
0186     m_radioTab->setChecked(true);
0187   } else if(!m_delimiter.isEmpty()) {
0188     m_radioOther->setChecked(true);
0189     m_editOther->setEnabled(true);
0190     m_editOther->setText(m_delimiter);
0191   }
0192 
0193   QLabel* label = new QLabel(i18n("Table column delimiter:"), gbox);
0194   m_colDelimiterEdit = new QLineEdit(gbox);
0195   m_colDelimiterEdit->setText(m_colDelimiter);
0196   m_delimiterGroupLayout->addWidget(label, 2, 0, 1, 2);
0197   m_delimiterGroupLayout->addWidget(m_colDelimiterEdit, 2, 2);
0198   QString w = i18n("The column delimiter separates values in each column of a <i>Table</i> field.");
0199   label->setWhatsThis(w);
0200   m_colDelimiterEdit->setWhatsThis(w);
0201 
0202   label = new QLabel(i18n("Table row delimiter:"), gbox);
0203   m_rowDelimiterEdit = new QLineEdit(gbox);
0204   m_rowDelimiterEdit->setText(m_rowDelimiter);
0205   m_delimiterGroupLayout->addWidget(label, 3, 0, 1, 2);
0206   m_delimiterGroupLayout->addWidget(m_rowDelimiterEdit, 3, 2);
0207   w = i18n("The row delimiter separates values in each row of a <i>Table</i> field.");
0208   label->setWhatsThis(w);
0209   m_rowDelimiterEdit->setWhatsThis(w);
0210 
0211   vlay->addWidget(m_checkIncludeTitles);
0212   vlay->addWidget(delimiterGroup);
0213 
0214   l->addWidget(gbox);
0215   l->addStretch(1);
0216   return m_widget;
0217 }
0218 
0219 void CSVExporter::readOptions(KSharedConfigPtr config_) {
0220   KConfigGroup group(config_, QStringLiteral("ExportOptions - %1").arg(formatString()));
0221   m_includeTitles = group.readEntry("Include Titles", m_includeTitles);
0222   m_delimiter = group.readEntry("Delimiter", m_delimiter);
0223   m_rowDelimiter = group.readEntry("RowDelimiter", m_rowDelimiter);
0224   m_colDelimiter = group.readEntry("ColumnDelimiter", m_colDelimiter);
0225 }
0226 
0227 void CSVExporter::saveOptions(KSharedConfigPtr config_) {
0228   m_includeTitles = m_checkIncludeTitles->isChecked();
0229   if(m_radioComma->isChecked()) {
0230     m_delimiter = QLatin1Char(',');
0231   } else if(m_radioSemicolon->isChecked()) {
0232     m_delimiter = QLatin1Char(';');
0233   } else if(m_radioTab->isChecked()) {
0234     m_delimiter = QLatin1Char('\t');
0235   } else {
0236     m_delimiter = m_editOther->text();
0237   }
0238 
0239   QString s = m_colDelimiterEdit->text();
0240   if(!s.isEmpty()) {
0241     m_colDelimiter = s;
0242   }
0243 
0244   s = m_rowDelimiterEdit->text();
0245   if(!s.isEmpty()) {
0246     m_rowDelimiter = s;
0247   }
0248 
0249   KConfigGroup group(config_, QStringLiteral("ExportOptions - %1").arg(formatString()));
0250   group.writeEntry("Include Titles", m_includeTitles);
0251   group.writeEntry("Delimiter", m_delimiter);
0252   group.writeEntry("RowDelimiter", m_rowDelimiter);
0253   group.writeEntry("ColumnDelimiter", m_colDelimiter);
0254 }