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 }