File indexing completed on 2024-05-12 05:10:08
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 <config.h> 0026 0027 #include "bibtexexporter.h" 0028 #include "../collections/bibtexcollection.h" 0029 #include "../core/filehandler.h" 0030 #include "../utils/bibtexhandler.h" 0031 #include "../utils/stringset.h" 0032 #include "../fieldformat.h" 0033 #include "../tellico_debug.h" 0034 0035 #include <KLocalizedString> 0036 #include <KConfigGroup> 0037 #include <KComboBox> 0038 0039 #include <QRegularExpression> 0040 #include <QCheckBox> 0041 #include <QGroupBox> 0042 #include <QLabel> 0043 #include <QVBoxLayout> 0044 #include <QHBoxLayout> 0045 0046 using namespace Tellico; 0047 using Tellico::Export::BibtexExporter; 0048 0049 BibtexExporter::BibtexExporter(Data::CollPtr coll) : Tellico::Export::Exporter(coll), 0050 m_expandMacros(false), 0051 m_packageURL(true), 0052 m_skipEmptyKeys(false), 0053 m_widget(nullptr), 0054 m_checkExpandMacros(nullptr), 0055 m_checkPackageURL(nullptr), 0056 m_checkSkipEmpty(nullptr), 0057 m_cbBibtexStyle(nullptr) { 0058 } 0059 0060 QString BibtexExporter::formatString() const { 0061 return QStringLiteral("Bibtex"); 0062 } 0063 0064 QString BibtexExporter::fileFilter() const { 0065 return i18n("Bibtex Files") + QLatin1String(" (*.bib)") + QLatin1String(";;") + i18n("All Files") + QLatin1String(" (*)"); 0066 } 0067 0068 bool BibtexExporter::exec() { 0069 const QString text = this->text(); 0070 return text.isEmpty() ? false : FileHandler::writeTextURL(url(), text, options() & ExportUTF8, options() & Export::ExportForce); 0071 } 0072 0073 QString BibtexExporter::text() { 0074 Data::CollPtr c = collection(); 0075 if(!c || c->type() != Data::Collection::Bibtex) { 0076 return QString(); 0077 } 0078 const Data::BibtexCollection* coll = static_cast<const Data::BibtexCollection*>(c.data()); 0079 0080 // there are some special attributes 0081 // the entry-type specifies the entry type - book, inproceedings, whatever 0082 QString typeField; 0083 // the key specifies the cite-key 0084 QString keyField; 0085 // the crossref bibtex field can reference another entry 0086 QString crossRefField; 0087 bool hasCrossRefs = false; 0088 0089 const QString bibtex = QStringLiteral("bibtex"); 0090 // keep a list of all the 'ordinary' fields to iterate through later 0091 Data::FieldList fields; 0092 foreach(Data::FieldPtr it, this->fields()) { 0093 QString bibtexField = it->property(bibtex); 0094 if(bibtexField == QLatin1String("entry-type")) { 0095 typeField = it->name(); 0096 } else if(bibtexField == QLatin1String("key")) { 0097 keyField = it->name(); 0098 } else if(bibtexField == QLatin1String("crossref")) { 0099 fields.append(it); // still output crossref field 0100 crossRefField = it->name(); 0101 hasCrossRefs = true; 0102 } else if(!bibtexField.isEmpty()) { 0103 fields.append(it); 0104 } 0105 } 0106 0107 if(typeField.isEmpty() || keyField.isEmpty()) { 0108 myWarning() << "the collection must have fields defining " 0109 "the entry-type and the key of the entry"; 0110 return QString(); 0111 } 0112 if(fields.isEmpty()) { 0113 myWarning() << "no bibtex field mapping exists in the collection."; 0114 return QString(); 0115 } 0116 0117 QString text = QLatin1String("@comment{Generated by Tellico ") 0118 + QLatin1String(TELLICO_VERSION) 0119 + QLatin1String("}\n\n"); 0120 0121 const QStringList macros = coll->macroList().keys(); 0122 0123 if(!coll->preamble().isEmpty()) { 0124 text += QLatin1String("@preamble{") 0125 + BibtexHandler::exportText(coll->preamble(), macros) 0126 + QLatin1String("}\n\n"); 0127 } 0128 0129 if(!m_expandMacros) { 0130 QMap<QString, QString>::ConstIterator macroIt; 0131 for(macroIt = coll->macroList().constBegin(); macroIt != coll->macroList().constEnd(); ++macroIt) { 0132 if(!macroIt.value().isEmpty()) { 0133 text += QLatin1String("@string{") 0134 + macroIt.key() 0135 + QLatin1String("=") 0136 + BibtexHandler::exportText(macroIt.value(), macros) 0137 + QLatin1String("}\n\n"); 0138 } 0139 } 0140 } 0141 0142 // if anything is crossref'd, we have to do an initial scan through the 0143 // whole collection first 0144 StringSet crossRefKeys; 0145 if(hasCrossRefs) { 0146 foreach(Data::EntryPtr entryIt, entries()) { 0147 crossRefKeys.add(entryIt->field(crossRefField)); 0148 } 0149 } 0150 0151 StringSet usedKeys; 0152 Data::EntryList crossRefs; 0153 QString type, key, newKey; 0154 foreach(Data::EntryPtr entryIt, entries()) { 0155 type = entryIt->field(typeField); 0156 if(type.isEmpty()) { 0157 myWarning() << "the entry for '" << entryIt->title() 0158 << "' has no entry-type, skipping it!"; 0159 continue; 0160 } 0161 0162 key = entryIt->field(keyField); 0163 if(key.isEmpty()) { 0164 if(m_skipEmptyKeys) { 0165 continue; 0166 } 0167 key = BibtexHandler::bibtexKey(entryIt); 0168 } else { 0169 // check crossrefs, only counts for non-empty keys 0170 // if this entry is crossref'd, add it to the list, and skip it 0171 if(hasCrossRefs && crossRefKeys.has(key)) { 0172 crossRefs.append(entryIt); 0173 continue; 0174 } 0175 } 0176 0177 newKey = key; 0178 char c = 'a'; 0179 while(usedKeys.has(newKey)) { 0180 // duplicate found! 0181 newKey = key + QLatin1Char(c); 0182 ++c; 0183 } 0184 key = newKey; 0185 usedKeys.add(key); 0186 0187 writeEntryText(text, fields, *entryIt, type, key); 0188 } 0189 0190 // now write out crossrefs 0191 foreach(Data::EntryPtr entryIt, crossRefs) { 0192 // no need to check type 0193 0194 key = entryIt->field(keyField); 0195 newKey = key; 0196 char c = 'a'; 0197 while(usedKeys.has(newKey)) { 0198 // duplicate found! 0199 newKey = key + QLatin1Char(c); 0200 ++c; 0201 } 0202 key = newKey; 0203 usedKeys.add(key); 0204 0205 writeEntryText(text, fields, *entryIt, entryIt->field(typeField), key); 0206 } 0207 return text; 0208 } 0209 0210 QWidget* BibtexExporter::widget(QWidget* parent_) { 0211 if(m_widget && m_widget->parent() == parent_) { 0212 return m_widget; 0213 } 0214 0215 m_widget = new QWidget(parent_); 0216 QVBoxLayout* l = new QVBoxLayout(m_widget); 0217 0218 QGroupBox* gbox = new QGroupBox(i18n("Bibtex Options"), m_widget); 0219 QVBoxLayout* vlay = new QVBoxLayout(gbox); 0220 0221 m_checkExpandMacros = new QCheckBox(i18n("Expand string macros"), gbox); 0222 m_checkExpandMacros->setChecked(m_expandMacros); 0223 m_checkExpandMacros->setWhatsThis(i18n("If checked, the string macros will be expanded and no " 0224 "@string{} entries will be written.")); 0225 0226 m_checkPackageURL = new QCheckBox(i18n("Use URL package"), gbox); 0227 m_checkPackageURL->setChecked(m_packageURL); 0228 m_checkPackageURL->setWhatsThis(i18n("If checked, any URL fields will be wrapped in a " 0229 "\\url declaration.")); 0230 0231 m_checkSkipEmpty = new QCheckBox(i18n("Skip entries with empty citation keys"), gbox); 0232 m_checkSkipEmpty->setChecked(m_skipEmptyKeys); 0233 m_checkSkipEmpty->setWhatsThis(i18n("If checked, any entries without a bibtex citation key " 0234 "will be skipped.")); 0235 0236 QHBoxLayout* hlay = new QHBoxLayout(); 0237 vlay->addLayout(hlay); 0238 0239 QLabel* l1 = new QLabel(i18n("Bibtex quotation style:") + QLatin1Char(' '), gbox); // add a space for aesthetics 0240 m_cbBibtexStyle = new KComboBox(gbox); 0241 m_cbBibtexStyle->addItem(i18n("Braces")); 0242 m_cbBibtexStyle->addItem(i18n("Quotes")); 0243 QString whats = i18n("<qt>The quotation style used when exporting bibtex. All field values will " 0244 "be escaped with either braces or quotation marks.</qt>"); 0245 l1->setWhatsThis(whats); 0246 m_cbBibtexStyle->setWhatsThis(whats); 0247 if(BibtexHandler::s_quoteStyle == BibtexHandler::BRACES) { 0248 m_cbBibtexStyle->setCurrentItem(i18n("Braces")); 0249 } else { 0250 m_cbBibtexStyle->setCurrentItem(i18n("Quotes")); 0251 } 0252 0253 hlay->addWidget(l1); 0254 hlay->addWidget(m_cbBibtexStyle); 0255 0256 vlay->addWidget(m_checkExpandMacros); 0257 vlay->addWidget(m_checkPackageURL); 0258 vlay->addWidget(m_checkSkipEmpty); 0259 0260 l->addWidget(gbox); 0261 l->addStretch(1); 0262 return m_widget; 0263 } 0264 0265 void BibtexExporter::readOptions(KSharedConfigPtr config_) { 0266 KConfigGroup group(config_, QStringLiteral("ExportOptions - %1").arg(formatString())); 0267 m_expandMacros = group.readEntry("Expand Macros", m_expandMacros); 0268 m_packageURL = group.readEntry("URL Package", m_packageURL); 0269 m_skipEmptyKeys = group.readEntry("Skip Empty Keys", m_skipEmptyKeys); 0270 0271 if(group.readEntry("Use Braces", true)) { 0272 BibtexHandler::s_quoteStyle = BibtexHandler::BRACES; 0273 } else { 0274 BibtexHandler::s_quoteStyle = BibtexHandler::QUOTES; 0275 } 0276 } 0277 0278 void BibtexExporter::saveOptions(KSharedConfigPtr config_) { 0279 KConfigGroup group(config_, QStringLiteral("ExportOptions - %1").arg(formatString())); 0280 m_expandMacros = m_checkExpandMacros->isChecked(); 0281 group.writeEntry("Expand Macros", m_expandMacros); 0282 m_packageURL = m_checkPackageURL->isChecked(); 0283 group.writeEntry("URL Package", m_packageURL); 0284 m_skipEmptyKeys = m_checkSkipEmpty->isChecked(); 0285 group.writeEntry("Skip Empty Keys", m_skipEmptyKeys); 0286 0287 bool useBraces = m_cbBibtexStyle->currentText() == i18n("Braces"); 0288 group.writeEntry("Use Braces", useBraces); 0289 if(useBraces) { 0290 BibtexHandler::s_quoteStyle = BibtexHandler::BRACES; 0291 } else { 0292 BibtexHandler::s_quoteStyle = BibtexHandler::QUOTES; 0293 } 0294 } 0295 0296 void BibtexExporter::writeEntryText(QString& text_, const Tellico::Data::FieldList& fields_, const Tellico::Data::Entry& entry_, 0297 const QString& type_, const QString& key_) { 0298 static const QRegularExpression numberRx(QLatin1String("^\\d+$")); 0299 const QStringList macros = static_cast<const Data::BibtexCollection*>(collection().data())->macroList().keys(); 0300 const QString bibtex = QStringLiteral("bibtex"); 0301 const QString bibtexSep = QStringLiteral("bibtex-separator"); 0302 0303 text_ += QLatin1Char('@') + type_ + QLatin1Char('{') + key_; 0304 0305 QString value; 0306 FieldFormat::Request format = (options() & Export::ExportFormatted ? 0307 FieldFormat::ForceFormat : 0308 FieldFormat::AsIsFormat); 0309 static const QRegularExpression stripHTML(QLatin1String("<.*?>")); 0310 foreach(Data::FieldPtr fIt, fields_) { 0311 value = entry_.formattedField(fIt, format); 0312 if(value.isEmpty()) { 0313 continue; 0314 } 0315 0316 // If the entry is formatted as a name and allows multiple values 0317 // insert "and" in between them (e.g. author and editor) 0318 if(fIt->formatType() == FieldFormat::FormatName 0319 && fIt->hasFlag(Data::Field::AllowMultiple)) { 0320 value.replace(FieldFormat::delimiterString(), QLatin1String(" and ")); 0321 } else if(fIt->hasFlag(Data::Field::AllowMultiple)) { 0322 QString bibsep = fIt->property(bibtexSep); 0323 if(!bibsep.isEmpty()) { 0324 value.replace(FieldFormat::delimiterString(), bibsep); 0325 } 0326 } else if(fIt->type() == Data::Field::Para) { 0327 // strip HTML from bibtex export 0328 value.remove(stripHTML); 0329 } else if(fIt->property(bibtex) == QLatin1String("pages")) { 0330 static const QRegularExpression rx(QLatin1String("(\\d)-(\\d)")); 0331 QRegularExpressionMatch m = rx.match(value); 0332 for(int pos = m.capturedStart(); pos > -1; pos = m.capturedStart()) { 0333 value.replace(pos, 3, m.captured(1) + QLatin1String("--") + m.captured(2)); 0334 m = rx.match(value, pos+2); 0335 } 0336 } 0337 0338 if(m_packageURL && fIt->type() == Data::Field::URL) { 0339 bool b = BibtexHandler::s_quoteStyle == BibtexHandler::BRACES; 0340 value = (b ? QLatin1Char('{') : QLatin1Char('"')) 0341 + QLatin1String("\\url{") + value + QLatin1Char('}') 0342 + (b ? QLatin1Char('}') : QLatin1Char('"')); 0343 } else if(!numberRx.match(value).hasMatch()) { 0344 // numbers aren't escaped, nor will they have macros 0345 // if m_expandMacros is true, then macros is empty, so this is ok even then 0346 value = BibtexHandler::exportText(value, macros); 0347 // special case for tilde, since it's a non-breaking space in LateX 0348 // replace it EXCEPT for URL or DOI fields 0349 if(fIt->property(bibtex) != QLatin1String("doi") && fIt->type() != Data::Field::URL) { 0350 value.replace(QChar(0xA0), QLatin1Char('~')); 0351 } 0352 } 0353 text_ += QLatin1String(",\n ") 0354 + fIt->property(bibtex) 0355 + QLatin1String(" = ") 0356 + value; 0357 } 0358 text_ += QLatin1String("\n}\n\n"); 0359 }