File indexing completed on 2024-05-19 05:05:20
0001 /*************************************************************************** 0002 * SPDX-License-Identifier: GPL-2.0-or-later 0003 * * 0004 * SPDX-FileCopyrightText: 2004-2023 Thomas Fischer <fischer@unix-ag.uni-kl.de> 0005 * * 0006 * This program is free software; you can redistribute it and/or modify * 0007 * it under the terms of the GNU General Public License as published by * 0008 * the Free Software Foundation; either version 2 of the License, or * 0009 * (at your option) any later version. * 0010 * * 0011 * This program is distributed in the hope that it will be useful, * 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0014 * GNU General Public License for more details. * 0015 * * 0016 * You should have received a copy of the GNU General Public License * 0017 * along with this program; if not, see <https://www.gnu.org/licenses/>. * 0018 ***************************************************************************/ 0019 0020 #include "entry.h" 0021 0022 #include <typeinfo> 0023 0024 #include <QRegularExpression> 0025 0026 #include "file.h" 0027 #include "logging_data.h" 0028 0029 // FIXME: Check if using those constants in the program is really necessary 0030 // or can be replace by config files 0031 const QString Entry::ftAbstract = QStringLiteral("abstract"); 0032 const QString Entry::ftAddress = QStringLiteral("address"); 0033 const QString Entry::ftAuthor = QStringLiteral("author"); 0034 const QString Entry::ftBookTitle = QStringLiteral("booktitle"); 0035 const QString Entry::ftChapter = QStringLiteral("chapter"); 0036 const QString Entry::ftColor = QStringLiteral("x-color"); 0037 const QString Entry::ftComment = QStringLiteral("comment"); 0038 const QString Entry::ftCrossRef = QStringLiteral("crossref"); 0039 const QString Entry::ftDOI = QStringLiteral("doi"); 0040 const QString Entry::ftEdition = QStringLiteral("edition"); 0041 const QString Entry::ftEditor = QStringLiteral("editor"); 0042 const QString Entry::ftFile = QStringLiteral("file"); 0043 const QString Entry::ftISSN = QStringLiteral("issn"); 0044 const QString Entry::ftISBN = QStringLiteral("isbn"); 0045 const QString Entry::ftJournal = QStringLiteral("journal"); 0046 const QString Entry::ftKeywords = QStringLiteral("keywords"); 0047 const QString Entry::ftLocalFile = QStringLiteral("localfile"); 0048 const QString Entry::ftLocation = QStringLiteral("location"); 0049 const QString Entry::ftMonth = QStringLiteral("month"); 0050 const QString Entry::ftNote = QStringLiteral("note"); 0051 const QString Entry::ftNumber = QStringLiteral("number"); 0052 const QString Entry::ftPages = QStringLiteral("pages"); 0053 const QString Entry::ftPublisher = QStringLiteral("publisher"); 0054 const QString Entry::ftSchool = QStringLiteral("school"); 0055 const QString Entry::ftSeries = QStringLiteral("series"); 0056 const QString Entry::ftStarRating = QStringLiteral("x-stars"); 0057 const QString Entry::ftTitle = QStringLiteral("title"); 0058 const QString Entry::ftUrl = QStringLiteral("url"); 0059 const QString Entry::ftUrlDate = QStringLiteral("urldate"); 0060 const QString Entry::ftVolume = QStringLiteral("volume"); 0061 const QString Entry::ftYear = QStringLiteral("year"); 0062 0063 const QString Entry::ftXData = QStringLiteral("xdata"); 0064 0065 const QString Entry::etArticle = QStringLiteral("article"); 0066 const QString Entry::etBook = QStringLiteral("book"); 0067 const QString Entry::etInBook = QStringLiteral("inbook"); 0068 const QString Entry::etInProceedings = QStringLiteral("inproceedings"); 0069 const QString Entry::etProceedings = QStringLiteral("proceedings"); 0070 const QString Entry::etMisc = QStringLiteral("misc"); 0071 const QString Entry::etPhDThesis = QStringLiteral("phdthesis"); 0072 const QString Entry::etMastersThesis = QStringLiteral("mastersthesis"); 0073 const QString Entry::etTechReport = QStringLiteral("techreport"); 0074 const QString Entry::etUnpublished = QStringLiteral("unpublished"); 0075 0076 quint64 Entry::internalUniqueIdCounter = 0; 0077 0078 /** 0079 * Private class to store internal variables that should not be visible 0080 * in the interface as defined in the header file. 0081 */ 0082 class Entry::EntryPrivate 0083 { 0084 public: 0085 QString type; 0086 QString id; 0087 }; 0088 0089 Entry::Entry(const QString &type, const QString &id) 0090 : Element(), QMap<QString, Value>(), internalUniqueId(++internalUniqueIdCounter), d(new Entry::EntryPrivate) 0091 { 0092 d->type = type; 0093 d->id = id; 0094 } 0095 0096 Entry::Entry(const Entry &other) 0097 : Element(), QMap<QString, Value>(), internalUniqueId(++internalUniqueIdCounter), d(new Entry::EntryPrivate) 0098 { 0099 operator=(other); 0100 } 0101 0102 Entry::~Entry() 0103 { 0104 clear(); 0105 delete d; 0106 } 0107 0108 quint64 Entry::uniqueId() const 0109 { 0110 return internalUniqueId; 0111 } 0112 0113 bool Entry::operator==(const Entry &other) const 0114 { 0115 /// Quick and easy tests first: id, type, and numer of fields 0116 if (id() != other.id() || type().compare(other.type(), Qt::CaseInsensitive) != 0 || count() != other.count()) 0117 return false; 0118 0119 /// Compare each field with other's corresponding field 0120 for (Entry::ConstIterator it = constBegin(); it != constEnd(); ++it) { 0121 if (!other.contains(it.key())) return false; 0122 const Value &thisValue = it.value(); 0123 const Value &otherValue = other.value(it.key()); 0124 if (thisValue != otherValue) return false; 0125 } 0126 0127 /// All fields of the other entry must occurr as well in this entry 0128 /// (no need to check equivalence again) 0129 for (Entry::ConstIterator it = other.constBegin(); it != other.constEnd(); ++it) 0130 if (!contains(it.key())) return false; 0131 0132 return true; 0133 } 0134 0135 bool Entry::operator!=(const Entry &other) const 0136 { 0137 return !operator ==(other); 0138 } 0139 0140 Entry &Entry::operator= (const Entry &other) 0141 { 0142 if (this != &other) { 0143 d->type = other.type(); 0144 d->id = other.id(); 0145 clear(); 0146 for (Entry::ConstIterator it = other.constBegin(); it != other.constEnd(); ++it) 0147 insert(it.key(), it.value()); 0148 } 0149 return *this; 0150 } 0151 0152 void Entry::setType(const QString &type) 0153 { 0154 d->type = type; 0155 } 0156 0157 QString Entry::type() const 0158 { 0159 return d->type; 0160 } 0161 0162 void Entry::setId(const QString &id) 0163 { 0164 d->id = id; 0165 } 0166 0167 QString Entry::id() const 0168 { 0169 return d->id; 0170 } 0171 0172 const Value Entry::value(const QString &key) const 0173 { 0174 const QString lcKey = key.toLower(); 0175 for (Entry::ConstIterator it = constBegin(); it != constEnd(); ++it) 0176 if (it.key().toLower() == lcKey) 0177 return QMap<QString, Value>::value(it.key()); 0178 0179 return Value(); 0180 } 0181 0182 int Entry::remove(const QString &key) 0183 { 0184 const QString lcKey = key.toLower(); 0185 for (Entry::Iterator it = begin(); it != end(); ++it) 0186 if (it.key().toLower() == lcKey) { 0187 QMap<QString, Value>::erase(it); 0188 return 1; 0189 } 0190 0191 return 0; 0192 } 0193 0194 bool Entry::contains(const QString &key) const 0195 { 0196 const QString lcKey = key.toLower(); 0197 for (Entry::ConstIterator it = constBegin(); it != constEnd(); ++it) 0198 if (it.key().toLower() == lcKey) 0199 return true; 0200 0201 return false; 0202 } 0203 0204 QSharedPointer<Entry> Entry::resolveCrossref(const File *bibTeXfile) const 0205 { 0206 QSharedPointer<Entry> result(new Entry(*this)); 0207 0208 if (bibTeXfile == nullptr) 0209 return result; 0210 0211 static const QStringList crossRefFields = {ftCrossRef, ftXData}; 0212 for (const QString &crossRefField : crossRefFields) { 0213 const QString crossRefValue = PlainTextValue::text(result->value(crossRefField)); 0214 if (crossRefValue.isEmpty()) 0215 continue; 0216 0217 const QSharedPointer<const Entry> crossRefEntry = bibTeXfile->containsKey(crossRefField, File::ElementType::Entry).dynamicCast<Entry>(); 0218 if (!crossRefEntry.isNull()) { 0219 /// Copy all fields from crossref'ed entry to new entry which do not (yet) exist in the new entry 0220 for (Entry::ConstIterator it = crossRefEntry->constBegin(); it != crossRefEntry->constEnd(); ++it) 0221 if (!result->contains(it.key())) 0222 result->insert(it.key(), Value(it.value())); 0223 0224 if (crossRefEntry->type().compare(etProceedings, Qt::CaseInsensitive) && result->type().compare(etInProceedings, Qt::CaseInsensitive) && crossRefEntry->contains(ftTitle) && !result->contains(ftBookTitle)) { 0225 /// In case current entry is of type 'inproceedings' but lacks a 'book title' 0226 /// and the crossref'ed entry is of type 'proceedings' and has a 'title', then 0227 /// copy this 'title into as the 'book title' of the current entry. 0228 /// Note: the correct way should be that the crossref'ed entry has a 'book title' 0229 /// field, but that case was handled above when copying non-existing fields, 0230 /// so this if-block is only a fall-back case. 0231 result->insert(ftBookTitle, Value(crossRefEntry->operator [](ftTitle))); 0232 } 0233 0234 /// Remove crossref field (no longer of use as all data got copied) 0235 result->remove(crossRefField); 0236 } 0237 } 0238 0239 return result; 0240 } 0241 0242 QStringList Entry::authorsLastName(const Entry &entry) 0243 { 0244 Value value; 0245 if (entry.contains(Entry::ftAuthor)) 0246 value = entry.value(Entry::ftAuthor); 0247 if (value.isEmpty() && entry.contains(Entry::ftEditor)) 0248 value = entry.value(Entry::ftEditor); 0249 if (value.isEmpty()) 0250 return QStringList(); 0251 0252 QStringList result; 0253 int maxAuthors = 16; ///< limit the number of authors considered 0254 result.reserve(maxAuthors); 0255 for (const QSharedPointer<ValueItem> &item : const_cast<const Value &>(value)) { 0256 QSharedPointer<const Person> person = item.dynamicCast<const Person>(); 0257 if (!person.isNull()) { 0258 const QString lastName = person->lastName(); 0259 if (!lastName.isEmpty()) 0260 result << lastName; 0261 } 0262 if (--maxAuthors <= 0) break; ///< limit the number of authors considered 0263 } 0264 0265 return result; 0266 } 0267 0268 QStringList Entry::authorsLastName() const 0269 { 0270 return authorsLastName(*this); 0271 } 0272 0273 bool Entry::isEntry(const Element &other) { 0274 return typeid(other) == typeid(Entry); 0275 } 0276 0277 QDebug operator<<(QDebug dbg, const Entry *entry) { 0278 dbg.nospace() << "Entry " << entry->id() << " (uniqueId=" << entry->uniqueId() << "), has " << entry->count() << " key-value pairs"; 0279 return dbg; 0280 }