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 }