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 "file.h"
0021 
0022 #include <QFile>
0023 #include <QTextStream>
0024 #include <QIODevice>
0025 #include <QStringList>
0026 
0027 #include <Preferences>
0028 #include "entry.h"
0029 #include "element.h"
0030 #include "macro.h"
0031 #include "comment.h"
0032 #include "preamble.h"
0033 #include "logging_data.h"
0034 
0035 const QString File::Url = QStringLiteral("Url");
0036 const QString File::Encoding = QStringLiteral("Encoding");
0037 const QString File::StringDelimiter = QStringLiteral("StringDelimiter");
0038 const QString File::CommentContext = QStringLiteral("CommentContext");
0039 const QString File::CommentPrefix = QStringLiteral("CommentPrefix");
0040 const QString File::KeywordCasing = QStringLiteral("KeywordCasing");
0041 const QString File::ProtectCasing = QStringLiteral("ProtectCasing");
0042 const QString File::NameFormatting = QStringLiteral("NameFormatting");
0043 const QString File::ListSeparator = QStringLiteral("ListSeparator");
0044 const QString File::SortedByIdentifier = QStringLiteral("SortedByIdentifier");
0045 
0046 const quint64 valid = 0x08090a0b0c0d0e0f;
0047 const quint64 invalid = 0x0102030405060708;
0048 
0049 class File::FilePrivate
0050 {
0051 private:
0052     quint64 validInvalidField;
0053     static const quint64 initialInternalIdCounter;
0054     static quint64 internalIdCounter;
0055 
0056 public:
0057     const quint64 internalId;
0058     QHash<QString, QVariant> properties;
0059 
0060     explicit FilePrivate(File *parent)
0061             : validInvalidField(valid), internalId(++internalIdCounter)
0062     {
0063         Q_UNUSED(parent)
0064         const bool isValid = checkValidity();
0065         if (!isValid) qCDebug(LOG_KBIBTEX_DATA) << "Creating File instance" << internalId << "  Valid?" << isValid;
0066         loadConfiguration();
0067     }
0068 
0069     ~FilePrivate() {
0070         const bool isValid = checkValidity();
0071         if (!isValid) qCDebug(LOG_KBIBTEX_DATA) << "Deleting File instance" << internalId << "  Valid?" << isValid;
0072         validInvalidField = invalid;
0073     }
0074 
0075     /// Copy-assignment operator
0076     FilePrivate &operator= (const FilePrivate &other) {
0077         if (this != &other) {
0078             validInvalidField = other.validInvalidField;
0079             properties = other.properties;
0080             const bool isValid = checkValidity();
0081             if (!isValid) qCDebug(LOG_KBIBTEX_DATA) << "Assigning File instance" << other.internalId << "to" << internalId << "  Is other valid?" << other.checkValidity() << "  Self valid?" << isValid;
0082         }
0083         return *this;
0084     }
0085 
0086     /// Move-assignment operator
0087     FilePrivate &operator= (FilePrivate &&other) {
0088         if (this != &other) {
0089             validInvalidField = std::move(other.validInvalidField);
0090             properties = std::move(other.properties);
0091             const bool isValid = checkValidity();
0092             if (!isValid) qCDebug(LOG_KBIBTEX_DATA) << "Assigning File instance" << other.internalId << "to" << internalId << "  Is other valid?" << other.checkValidity() << "  Self valid?" << isValid;
0093         }
0094         return *this;
0095     }
0096 
0097     void loadConfiguration() {
0098         /// Load and set configuration as stored in settings
0099         properties.insert(File::Encoding, Preferences::instance().bibTeXEncoding());
0100         properties.insert(File::StringDelimiter, Preferences::instance().bibTeXStringDelimiter());
0101         properties.insert(File::CommentContext, static_cast<int>(Preferences::instance().bibTeXCommentContext()));
0102         properties.insert(File::CommentPrefix, Preferences::instance().bibTeXCommentPrefix());
0103         properties.insert(File::KeywordCasing, static_cast<int>(Preferences::instance().bibTeXKeywordCasing()));
0104         properties.insert(File::NameFormatting, Preferences::instance().personNameFormat());
0105         properties.insert(File::ProtectCasing, static_cast<int>(Preferences::instance().bibTeXProtectCasing() ? Qt::Checked : Qt::Unchecked));
0106         properties.insert(File::ListSeparator,  Preferences::instance().bibTeXListSeparator());
0107         properties.insert(File::SortedByIdentifier, Preferences::instance().bibTeXEntriesSortedByIdentifier());
0108     }
0109 
0110     bool checkValidity() const {
0111         if (validInvalidField != valid) {
0112             /// 'validInvalidField' must equal to the know 'valid' value
0113             qCWarning(LOG_KBIBTEX_DATA) << "Failed validity check: " << validInvalidField << "!=" << valid;
0114             return false;
0115         } else if (internalId <= initialInternalIdCounter) {
0116             /// Internal id counter starts at initialInternalIdCounter+1
0117             qCWarning(LOG_KBIBTEX_DATA) << "Failed validity check: " << internalId << "< " << (initialInternalIdCounter + 1);
0118             return false;
0119         } else if (internalId > 600000) {
0120             /// Reasonable assumption: not more that 500000 ids used
0121             qCWarning(LOG_KBIBTEX_DATA) << "Failed validity check: " << internalId << "> 600000";
0122             return false;
0123         }
0124         return true;
0125     }
0126 };
0127 
0128 const quint64 File::FilePrivate::initialInternalIdCounter = 99999;
0129 quint64 File::FilePrivate::internalIdCounter = File::FilePrivate::initialInternalIdCounter;
0130 
0131 File::File()
0132         : QList<QSharedPointer<Element> >(), d(new FilePrivate(this))
0133 {
0134     /// nothing
0135 }
0136 
0137 File::File(const File &other)
0138         : QList<QSharedPointer<Element> >(other), d(new FilePrivate(this))
0139 {
0140     d->operator =(*other.d);
0141 }
0142 
0143 File::File(File &&other)
0144         : QList<QSharedPointer<Element> >(std::move(other)), d(new FilePrivate(this))
0145 {
0146     d->operator =(std::move(*other.d));
0147 }
0148 
0149 
0150 File::~File()
0151 {
0152     Q_ASSERT_X(d->checkValidity(), "File::~File()", "This File object is not valid");
0153     delete d;
0154 }
0155 
0156 File &File::operator= (const File &other) {
0157     if (this != &other)
0158         d->operator =(*other.d);
0159     return *this;
0160 }
0161 
0162 File &File::operator= (File &&other) {
0163     if (this != &other)
0164         d->operator =(std::move(*other.d));
0165     return *this;
0166 }
0167 
0168 bool File::operator==(const File &other) const {
0169     if (size() != other.size()) return false;
0170 
0171     for (File::ConstIterator myIt = constBegin(), otherIt = other.constBegin(); myIt != constEnd() && otherIt != constEnd(); ++myIt, ++otherIt) {
0172         QSharedPointer<const Entry> myEntry = myIt->dynamicCast<const Entry>();
0173         QSharedPointer<const Entry> otherEntry = otherIt->dynamicCast<const Entry>();
0174         if ((myEntry.isNull() && !otherEntry.isNull()) || (!myEntry.isNull() && otherEntry.isNull())) return false;
0175         if (!myEntry.isNull() && !otherEntry.isNull()) {
0176             if (myEntry->operator !=(*otherEntry.data()))
0177                 return false;
0178         } else {
0179             QSharedPointer<const Macro> myMacro = myIt->dynamicCast<const Macro>();
0180             QSharedPointer<const Macro> otherMacro = otherIt->dynamicCast<const Macro>();
0181             if ((myMacro.isNull() && !otherMacro.isNull()) || (!myMacro.isNull() && otherMacro.isNull())) return false;
0182             if (!myMacro.isNull() && !otherMacro.isNull()) {
0183                 if (myMacro->operator !=(*otherMacro.data()))
0184                     return false;
0185             } else {
0186                 QSharedPointer<const Preamble> myPreamble = myIt->dynamicCast<const Preamble>();
0187                 QSharedPointer<const Preamble> otherPreamble = otherIt->dynamicCast<const Preamble>();
0188                 if ((myPreamble.isNull() && !otherPreamble.isNull()) || (!myPreamble.isNull() && otherPreamble.isNull())) return false;
0189                 if (!myPreamble.isNull() && !otherPreamble.isNull()) {
0190                     if (myPreamble->operator !=(*otherPreamble.data()))
0191                         return false;
0192                 } else {
0193                     QSharedPointer<const Comment> myComment = myIt->dynamicCast<const Comment>();
0194                     QSharedPointer<const Comment> otherComment = otherIt->dynamicCast<const Comment>();
0195                     if ((myComment.isNull() && !otherComment.isNull()) || (!myComment.isNull() && otherComment.isNull())) return false;
0196                     if (!myComment.isNull() && !otherComment.isNull()) {
0197                         if (myComment->text() != otherComment->text() || myComment->context() != otherComment->context() || (myComment->context() == Preferences::CommentContext::Prefix && myComment->prefix() != otherComment->prefix())) {
0198                             return false;
0199                         }
0200                     } else {
0201                         /// This case should never be reached
0202                         qCWarning(LOG_KBIBTEX_DATA) << "Met unhandled case while comparing two File objects";
0203                         return false;
0204                     }
0205                 }
0206             }
0207         }
0208     }
0209 
0210     return true;
0211 }
0212 
0213 bool File::operator!=(const File &other) const {
0214     return !operator ==(other);
0215 }
0216 
0217 const QSharedPointer<Element> File::containsKey(const QString &key, ElementTypes elementTypes) const
0218 {
0219     if (!d->checkValidity())
0220         qCCritical(LOG_KBIBTEX_DATA) << "const QSharedPointer<Element> File::containsKey(const QString &key, ElementTypes elementTypes) const" << "This File object is not valid";
0221     for (const auto &element : const_cast<const File &>(*this)) {
0222         const QSharedPointer<Entry> entry = elementTypes.testFlag(ElementType::Entry) ? element.dynamicCast<Entry>() : QSharedPointer<Entry>();
0223         if (!entry.isNull()) {
0224             if (entry->id() == key)
0225                 return entry;
0226         } else {
0227             const QSharedPointer<Macro> macro = elementTypes.testFlag(ElementType::Macro) ? element.dynamicCast<Macro>() : QSharedPointer<Macro>();
0228             if (!macro.isNull()) {
0229                 if (macro->key() == key)
0230                     return macro;
0231             }
0232         }
0233     }
0234 
0235     return QSharedPointer<Element>();
0236 }
0237 
0238 QStringList File::allKeys(ElementTypes elementTypes) const
0239 {
0240     if (!d->checkValidity())
0241         qCCritical(LOG_KBIBTEX_DATA) << "QStringList File::allKeys(ElementTypes elementTypes) const" << "This File object is not valid";
0242     QStringList result;
0243     result.reserve(size());
0244     for (const auto &element : const_cast<const File &>(*this)) {
0245         const QSharedPointer<Entry> entry = elementTypes.testFlag(ElementType::Entry) ? element.dynamicCast<Entry>() : QSharedPointer<Entry>();
0246         if (!entry.isNull())
0247             result.append(entry->id());
0248         else {
0249             const QSharedPointer<Macro> macro = elementTypes.testFlag(ElementType::Macro) ? element.dynamicCast<Macro>() : QSharedPointer<Macro>();
0250             if (!macro.isNull())
0251                 result.append(macro->key());
0252         }
0253     }
0254 
0255     return result;
0256 }
0257 
0258 QSet<QString> File::uniqueEntryValuesSet(const QString &fieldName) const
0259 {
0260     if (!d->checkValidity())
0261         qCCritical(LOG_KBIBTEX_DATA) << "QSet<QString> File::uniqueEntryValuesSet(const QString &fieldName) const" << "This File object is not valid";
0262     QSet<QString> valueSet;
0263     const QString lcFieldName = fieldName.toLower();
0264 
0265     for (const auto &element : const_cast<const File &>(*this)) {
0266         const QSharedPointer<Entry> entry = element.dynamicCast<Entry>();
0267         if (!entry.isNull())
0268             for (Entry::ConstIterator it = entry->constBegin(); it != entry->constEnd(); ++it)
0269                 if (it.key().toLower() == lcFieldName) {
0270                     const auto itValue = it.value();
0271                     for (const QSharedPointer<ValueItem> &valueItem : itValue) {
0272                         /// Check if ValueItem to process points to a person
0273                         const QSharedPointer<Person> person = valueItem.dynamicCast<Person>();
0274                         if (!person.isNull()) {
0275                             QSet<QString> personNameFormattingSet {Preferences::personNameFormatLastFirst, Preferences::personNameFormatFirstLast};
0276                             personNameFormattingSet.insert(Preferences::instance().personNameFormat());
0277                             /// Add person's name formatted using each of the templates assembled above
0278                             for (const QString &personNameFormatting : const_cast<const QSet<QString> &>(personNameFormattingSet))
0279                                 valueSet.insert(Person::transcribePersonName(person.data(), personNameFormatting));
0280                         } else {
0281                             /// Default case: use PlainTextValue::text to translate ValueItem
0282                             /// to a human-readable text
0283                             valueSet.insert(PlainTextValue::text(*valueItem));
0284                         }
0285                     }
0286                 }
0287     }
0288 
0289     return valueSet;
0290 }
0291 
0292 QStringList File::uniqueEntryValuesList(const QString &fieldName) const
0293 {
0294     if (!d->checkValidity())
0295         qCCritical(LOG_KBIBTEX_DATA) << "QStringList File::uniqueEntryValuesList(const QString &fieldName) const" << "This File object is not valid";
0296     QSet<QString> valueSet = uniqueEntryValuesSet(fieldName);
0297 #if QT_VERSION >= 0x050e00
0298     QStringList list(valueSet.constBegin(), valueSet.constEnd()); ///< This function was introduced in Qt 5.14
0299 #else // QT_VERSION < 0x050e00
0300     QStringList list = valueSet.toList();
0301 #endif // QT_VERSION >= 0x050e00
0302     list.sort();
0303     return list;
0304 }
0305 
0306 void File::setProperty(const QString &key, const QVariant &value)
0307 {
0308     if (!d->checkValidity())
0309         qCCritical(LOG_KBIBTEX_DATA) << "void File::setProperty(const QString &key, const QVariant &value)" << "This File object is not valid";
0310     d->properties.insert(key, value);
0311 }
0312 
0313 QVariant File::property(const QString &key) const
0314 {
0315     if (!d->checkValidity())
0316         qCCritical(LOG_KBIBTEX_DATA) << "QVariant File::property(const QString &key) const" << "This File object is not valid";
0317     return d->properties.contains(key) ? d->properties.value(key) : QVariant();
0318 }
0319 
0320 QVariant File::property(const QString &key, const QVariant &defaultValue) const
0321 {
0322     if (!d->checkValidity())
0323         qCCritical(LOG_KBIBTEX_DATA) << "QVariant File::property(const QString &key, const QVariant &defaultValue) const" << "This File object is not valid";
0324     return d->properties.value(key, defaultValue);
0325 }
0326 
0327 bool File::hasProperty(const QString &key) const
0328 {
0329     if (!d->checkValidity())
0330         qCCritical(LOG_KBIBTEX_DATA) << "bool File::hasProperty(const QString &key) const" << "This File object is not valid";
0331     return d->properties.contains(key);
0332 }
0333 
0334 void File::setPropertiesToDefault()
0335 {
0336     if (!d->checkValidity())
0337         qCCritical(LOG_KBIBTEX_DATA) << "void File::setPropertiesToDefault()" << "This File object is not valid";
0338     d->loadConfiguration();
0339 }
0340 
0341 bool File::checkValidity() const
0342 {
0343     return d->checkValidity();
0344 }
0345 
0346 const File *File::sortByIdentifier(const File *bibtexfile)
0347 {
0348     File *newFile = new File(*bibtexfile);
0349 
0350     std::sort(newFile->begin(), newFile->end(), [](const QSharedPointer<Element> &a, const QSharedPointer<Element> &b)->bool {
0351         QSharedPointer<const Entry> ae = a.dynamicCast<const Entry>();
0352         if (!ae.isNull()) {
0353             QSharedPointer<const Entry> be = b.dynamicCast<const Entry>();
0354             if (!be.isNull())
0355                 /// If two elements to be compared are actually two Entry objects,
0356                 /// compare both by their ids
0357                 return ae->id() < be->id();
0358             else {
0359                 QSharedPointer<const Macro> bm = b.dynamicCast<const Macro>();
0360                 if (!bm.isNull())
0361                     return ae->id() < bm->key();
0362             }
0363         } else {
0364             QSharedPointer<const Macro> am = a.dynamicCast<const Macro>();
0365             if (!am.isNull()) {
0366                 QSharedPointer<const Macro> bm = b.dynamicCast<const Macro>();
0367                 if (!bm.isNull())
0368                     /// If two elements to be compared are actually two Macro objects,
0369                     /// compare both by their keys
0370                     return am->key() < bm->key();
0371                 else {
0372                     QSharedPointer<const Entry> be = b.dynamicCast<const Entry>();
0373                     if (!be.isNull())
0374                         return am->key() < be->id();
0375                 }
0376             }
0377         }
0378         /// If no other comparison was available, use the Element's own comparison operator
0379         return a < b;
0380     });
0381 
0382     return newFile;
0383 }
0384 
0385 QDebug operator<<(QDebug dbg, const File *file) {
0386     dbg.nospace() << "File is " << (file->checkValidity() ? "" : "NOT ") << "valid and has " << file->count() << " members";
0387     return dbg;
0388 }