File indexing completed on 2024-05-12 04:02:18

0001 /*
0002     SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
0003     SPDX-FileCopyrightText: 2018 Dominik Haumann <dhaumann@kde.org>
0004     SPDX-FileCopyrightText: 2018 Christoph Cullmann <cullmann@kde.org>
0005     SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
0006 
0007     SPDX-License-Identifier: MIT
0008 */
0009 
0010 #include "definition.h"
0011 #include "definition_p.h"
0012 #include "definitionref_p.h"
0013 
0014 #include "context_p.h"
0015 #include "format.h"
0016 #include "format_p.h"
0017 #include "highlightingdata_p.hpp"
0018 #include "ksyntaxhighlighting_logging.h"
0019 #include "ksyntaxhighlighting_version.h"
0020 #include "repository.h"
0021 #include "repository_p.h"
0022 #include "rule_p.h"
0023 #include "worddelimiters_p.h"
0024 #include "xml_p.h"
0025 
0026 #include <QCborMap>
0027 #include <QCoreApplication>
0028 #include <QFile>
0029 #include <QXmlStreamReader>
0030 
0031 #include <algorithm>
0032 #include <atomic>
0033 
0034 using namespace KSyntaxHighlighting;
0035 
0036 DefinitionData::DefinitionData()
0037     : wordDelimiters()
0038     , wordWrapDelimiters(wordDelimiters)
0039 {
0040 }
0041 
0042 DefinitionData::~DefinitionData() = default;
0043 
0044 Definition::Definition()
0045     : d(std::make_shared<DefinitionData>())
0046 {
0047     d->q = *this;
0048 }
0049 
0050 Definition::Definition(Definition &&other) noexcept = default;
0051 Definition::Definition(const Definition &) = default;
0052 Definition::~Definition() = default;
0053 Definition &Definition::operator=(Definition &&other) noexcept = default;
0054 Definition &Definition::operator=(const Definition &) = default;
0055 
0056 Definition::Definition(std::shared_ptr<DefinitionData> &&dd)
0057     : d(std::move(dd))
0058 {
0059     if (!d) {
0060         Definition().d.swap(d);
0061     }
0062 }
0063 
0064 bool Definition::operator==(const Definition &other) const
0065 {
0066     return d->fileName == other.d->fileName;
0067 }
0068 
0069 bool Definition::operator!=(const Definition &other) const
0070 {
0071     return d->fileName != other.d->fileName;
0072 }
0073 
0074 bool Definition::isValid() const
0075 {
0076     return d->repo && !d->fileName.isEmpty() && !d->name.isEmpty();
0077 }
0078 
0079 QString Definition::filePath() const
0080 {
0081     return d->fileName;
0082 }
0083 
0084 QString Definition::name() const
0085 {
0086     return d->name;
0087 }
0088 
0089 QString Definition::translatedName() const
0090 {
0091     if (d->translatedName.isEmpty()) {
0092         d->translatedName = QCoreApplication::instance()->translate("Language", d->nameUtf8.isEmpty() ? d->name.toUtf8().constData() : d->nameUtf8.constData());
0093     }
0094     return d->translatedName;
0095 }
0096 
0097 QString Definition::section() const
0098 {
0099     return d->section;
0100 }
0101 
0102 QString Definition::translatedSection() const
0103 {
0104     if (d->translatedSection.isEmpty()) {
0105         d->translatedSection = QCoreApplication::instance()->translate("Language Section",
0106                                                                        d->sectionUtf8.isEmpty() ? d->section.toUtf8().constData() : d->sectionUtf8.constData());
0107     }
0108     return d->translatedSection;
0109 }
0110 
0111 QList<QString> Definition::mimeTypes() const
0112 {
0113     return d->mimetypes;
0114 }
0115 
0116 QList<QString> Definition::extensions() const
0117 {
0118     return d->extensions;
0119 }
0120 
0121 int Definition::version() const
0122 {
0123     return d->version;
0124 }
0125 
0126 int Definition::priority() const
0127 {
0128     return d->priority;
0129 }
0130 
0131 bool Definition::isHidden() const
0132 {
0133     return d->hidden;
0134 }
0135 
0136 QString Definition::style() const
0137 {
0138     return d->style;
0139 }
0140 
0141 QString Definition::indenter() const
0142 {
0143     return d->indenter;
0144 }
0145 
0146 QString Definition::author() const
0147 {
0148     return d->author;
0149 }
0150 
0151 QString Definition::license() const
0152 {
0153     return d->license;
0154 }
0155 
0156 bool Definition::isWordDelimiter(QChar c) const
0157 {
0158     d->load();
0159     return d->wordDelimiters.contains(c);
0160 }
0161 
0162 bool Definition::isWordWrapDelimiter(QChar c) const
0163 {
0164     d->load();
0165     return d->wordWrapDelimiters.contains(c);
0166 }
0167 
0168 bool Definition::foldingEnabled() const
0169 {
0170     d->load();
0171     if (d->hasFoldingRegions || indentationBasedFoldingEnabled()) {
0172         return true;
0173     }
0174 
0175     // check included definitions
0176     const auto defs = includedDefinitions();
0177     for (const auto &def : defs) {
0178         if (def.foldingEnabled()) {
0179             d->hasFoldingRegions = true;
0180             break;
0181         }
0182     }
0183 
0184     return d->hasFoldingRegions;
0185 }
0186 
0187 bool Definition::indentationBasedFoldingEnabled() const
0188 {
0189     d->load();
0190     return d->indentationBasedFolding;
0191 }
0192 
0193 QStringList Definition::foldingIgnoreList() const
0194 {
0195     d->load();
0196     return d->foldingIgnoreList;
0197 }
0198 
0199 QStringList Definition::keywordLists() const
0200 {
0201     d->load(DefinitionData::OnlyKeywords(true));
0202     return d->keywordLists.keys();
0203 }
0204 
0205 QStringList Definition::keywordList(const QString &name) const
0206 {
0207     d->load(DefinitionData::OnlyKeywords(true));
0208     const auto list = d->keywordList(name);
0209     return list ? list->keywords() : QStringList();
0210 }
0211 
0212 bool Definition::setKeywordList(const QString &name, const QStringList &content)
0213 {
0214     d->load(DefinitionData::OnlyKeywords(true));
0215     KeywordList *list = d->keywordList(name);
0216     if (list) {
0217         list->setKeywordList(content);
0218         return true;
0219     } else {
0220         return false;
0221     }
0222 }
0223 
0224 QList<Format> Definition::formats() const
0225 {
0226     d->load();
0227 
0228     // sort formats so that the order matches the order of the itemDatas in the xml files.
0229     auto formatList = d->formats.values();
0230     std::sort(formatList.begin(), formatList.end(), [](const KSyntaxHighlighting::Format &lhs, const KSyntaxHighlighting::Format &rhs) {
0231         return lhs.id() < rhs.id();
0232     });
0233 
0234     return formatList;
0235 }
0236 
0237 QList<Definition> Definition::includedDefinitions() const
0238 {
0239     d->load();
0240 
0241     // init worklist and result used as guard with this definition
0242     QList<const DefinitionData *> queue{d.get()};
0243     QList<Definition> definitions{*this};
0244     while (!queue.empty()) {
0245         const auto *def = queue.back();
0246         queue.pop_back();
0247         for (const auto &defRef : std::as_const(def->immediateIncludedDefinitions)) {
0248             const auto definition = defRef.definition();
0249             if (!definitions.contains(definition)) {
0250                 definitions.push_back(definition);
0251                 queue.push_back(definition.d.get());
0252             }
0253         }
0254     }
0255 
0256     // remove the 1st entry, since it is this Definition
0257     definitions.front() = std::move(definitions.back());
0258     definitions.pop_back();
0259 
0260     return definitions;
0261 }
0262 
0263 QString Definition::singleLineCommentMarker() const
0264 {
0265     d->load();
0266     return d->singleLineCommentMarker;
0267 }
0268 
0269 CommentPosition Definition::singleLineCommentPosition() const
0270 {
0271     d->load();
0272     return d->singleLineCommentPosition;
0273 }
0274 
0275 QPair<QString, QString> Definition::multiLineCommentMarker() const
0276 {
0277     d->load();
0278     return {d->multiLineCommentStartMarker, d->multiLineCommentEndMarker};
0279 }
0280 
0281 QList<QPair<QChar, QString>> Definition::characterEncodings() const
0282 {
0283     d->load();
0284     return d->characterEncodings;
0285 }
0286 
0287 Context *DefinitionData::initialContext()
0288 {
0289     Q_ASSERT(!contexts.empty());
0290     return &contexts.front();
0291 }
0292 
0293 Context *DefinitionData::contextByName(QStringView wantedName)
0294 {
0295     for (auto &context : contexts) {
0296         if (context.name() == wantedName) {
0297             return &context;
0298         }
0299     }
0300     return nullptr;
0301 }
0302 
0303 KeywordList *DefinitionData::keywordList(const QString &wantedName)
0304 {
0305     auto it = keywordLists.find(wantedName);
0306     return (it == keywordLists.end()) ? nullptr : &it.value();
0307 }
0308 
0309 Format DefinitionData::formatByName(const QString &wantedName) const
0310 {
0311     const auto it = formats.constFind(wantedName);
0312     if (it != formats.constEnd()) {
0313         return it.value();
0314     }
0315 
0316     return Format();
0317 }
0318 
0319 bool DefinitionData::isLoaded() const
0320 {
0321     return !contexts.empty();
0322 }
0323 
0324 namespace
0325 {
0326 std::atomic<uint64_t> definitionId{1};
0327 }
0328 
0329 bool DefinitionData::load(OnlyKeywords onlyKeywords)
0330 {
0331     if (fileName.isEmpty()) {
0332         return false;
0333     }
0334 
0335     if (isLoaded()) {
0336         return true;
0337     }
0338 
0339     if (bool(onlyKeywords) && keywordIsLoaded) {
0340         return true;
0341     }
0342 
0343     QFile file(fileName);
0344     if (!file.open(QFile::ReadOnly)) {
0345         return false;
0346     }
0347 
0348     QXmlStreamReader reader(&file);
0349     while (!reader.atEnd()) {
0350         const auto token = reader.readNext();
0351         if (token != QXmlStreamReader::StartElement) {
0352             continue;
0353         }
0354 
0355         if (reader.name() == QLatin1String("highlighting")) {
0356             loadHighlighting(reader, onlyKeywords);
0357             if (bool(onlyKeywords)) {
0358                 return true;
0359             }
0360         }
0361 
0362         else if (reader.name() == QLatin1String("general")) {
0363             loadGeneral(reader);
0364         }
0365     }
0366 
0367     for (auto it = keywordLists.begin(); it != keywordLists.end(); ++it) {
0368         it->setCaseSensitivity(caseSensitive);
0369     }
0370 
0371     resolveContexts();
0372 
0373     id = definitionId.fetch_add(1, std::memory_order_relaxed);
0374 
0375     return true;
0376 }
0377 
0378 void DefinitionData::clear()
0379 {
0380     // keep only name and repo, so we can re-lookup to make references persist over repo reloads
0381     id = 0;
0382     keywordLists.clear();
0383     contexts.clear();
0384     formats.clear();
0385     contextDatas.clear();
0386     immediateIncludedDefinitions.clear();
0387     wordDelimiters = WordDelimiters();
0388     wordWrapDelimiters = wordDelimiters;
0389     keywordIsLoaded = false;
0390     hasFoldingRegions = false;
0391     indentationBasedFolding = false;
0392     foldingIgnoreList.clear();
0393     singleLineCommentMarker.clear();
0394     singleLineCommentPosition = CommentPosition::StartOfLine;
0395     multiLineCommentStartMarker.clear();
0396     multiLineCommentEndMarker.clear();
0397     characterEncodings.clear();
0398 
0399     fileName.clear();
0400     nameUtf8.clear();
0401     translatedName.clear();
0402     section.clear();
0403     sectionUtf8.clear();
0404     translatedSection.clear();
0405     style.clear();
0406     indenter.clear();
0407     author.clear();
0408     license.clear();
0409     mimetypes.clear();
0410     extensions.clear();
0411     caseSensitive = Qt::CaseSensitive;
0412     version = 0.0f;
0413     priority = 0;
0414     hidden = false;
0415 
0416     // purge our cache that is used to unify states
0417     unify.clear();
0418 }
0419 
0420 bool DefinitionData::loadMetaData(const QString &definitionFileName)
0421 {
0422     fileName = definitionFileName;
0423 
0424     QFile file(definitionFileName);
0425     if (!file.open(QFile::ReadOnly)) {
0426         return false;
0427     }
0428 
0429     QXmlStreamReader reader(&file);
0430     while (!reader.atEnd()) {
0431         const auto token = reader.readNext();
0432         if (token != QXmlStreamReader::StartElement) {
0433             continue;
0434         }
0435         if (reader.name() == QLatin1String("language")) {
0436             return loadLanguage(reader);
0437         }
0438     }
0439 
0440     return false;
0441 }
0442 
0443 bool DefinitionData::loadMetaData(const QString &file, const QCborMap &obj)
0444 {
0445     name = obj.value(QLatin1String("name")).toString();
0446     nameUtf8 = obj.value(QLatin1String("name")).toByteArray();
0447     section = obj.value(QLatin1String("section")).toString();
0448     sectionUtf8 = obj.value(QLatin1String("section")).toByteArray();
0449     version = obj.value(QLatin1String("version")).toInteger();
0450     priority = obj.value(QLatin1String("priority")).toInteger();
0451     style = obj.value(QLatin1String("style")).toString();
0452     author = obj.value(QLatin1String("author")).toString();
0453     license = obj.value(QLatin1String("license")).toString();
0454     indenter = obj.value(QLatin1String("indenter")).toString();
0455     hidden = obj.value(QLatin1String("hidden")).toBool();
0456     fileName = file;
0457 
0458     const auto exts = obj.value(QLatin1String("extensions")).toString();
0459     extensions = exts.split(QLatin1Char(';'), Qt::SkipEmptyParts);
0460 
0461     const auto mts = obj.value(QLatin1String("mimetype")).toString();
0462     mimetypes = mts.split(QLatin1Char(';'), Qt::SkipEmptyParts);
0463 
0464     return true;
0465 }
0466 
0467 bool DefinitionData::loadLanguage(QXmlStreamReader &reader)
0468 {
0469     Q_ASSERT(reader.name() == QLatin1String("language"));
0470     Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
0471 
0472     if (!checkKateVersion(reader.attributes().value(QLatin1String("kateversion")))) {
0473         return false;
0474     }
0475 
0476     name = reader.attributes().value(QLatin1String("name")).toString();
0477     section = reader.attributes().value(QLatin1String("section")).toString();
0478     // toFloat instead of toInt for backward compatibility with old Kate files
0479     version = reader.attributes().value(QLatin1String("version")).toFloat();
0480     priority = reader.attributes().value(QLatin1String("priority")).toInt();
0481     hidden = Xml::attrToBool(reader.attributes().value(QLatin1String("hidden")));
0482     style = reader.attributes().value(QLatin1String("style")).toString();
0483     indenter = reader.attributes().value(QLatin1String("indenter")).toString();
0484     author = reader.attributes().value(QLatin1String("author")).toString();
0485     license = reader.attributes().value(QLatin1String("license")).toString();
0486     const auto exts = reader.attributes().value(QLatin1String("extensions"));
0487     for (const auto &ext : exts.split(QLatin1Char(';'), Qt::SkipEmptyParts)) {
0488         extensions.push_back(ext.toString());
0489     }
0490     const auto mts = reader.attributes().value(QLatin1String("mimetype"));
0491     for (const auto &mt : mts.split(QLatin1Char(';'), Qt::SkipEmptyParts)) {
0492         mimetypes.push_back(mt.toString());
0493     }
0494     if (reader.attributes().hasAttribute(QLatin1String("casesensitive"))) {
0495         caseSensitive = Xml::attrToBool(reader.attributes().value(QLatin1String("casesensitive"))) ? Qt::CaseSensitive : Qt::CaseInsensitive;
0496     }
0497     return true;
0498 }
0499 
0500 void DefinitionData::loadHighlighting(QXmlStreamReader &reader, OnlyKeywords onlyKeywords)
0501 {
0502     Q_ASSERT(reader.name() == QLatin1String("highlighting"));
0503     Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
0504 
0505     // skip highlighting
0506     reader.readNext();
0507 
0508     while (!reader.atEnd()) {
0509         switch (reader.tokenType()) {
0510         case QXmlStreamReader::StartElement:
0511             if (reader.name() == QLatin1String("list")) {
0512                 if (!keywordIsLoaded) {
0513                     KeywordList keywords;
0514                     keywords.load(reader);
0515                     keywordLists.insert(keywords.name(), keywords);
0516                 } else {
0517                     reader.skipCurrentElement();
0518                     reader.readNext(); // Skip </list>
0519                 }
0520             } else if (bool(onlyKeywords)) {
0521                 resolveIncludeKeywords();
0522                 return;
0523             } else if (reader.name() == QLatin1String("contexts")) {
0524                 resolveIncludeKeywords();
0525                 loadContexts(reader);
0526                 reader.readNext();
0527             } else if (reader.name() == QLatin1String("itemDatas")) {
0528                 loadItemData(reader);
0529             } else {
0530                 reader.readNext();
0531             }
0532             break;
0533         case QXmlStreamReader::EndElement:
0534             return;
0535         default:
0536             reader.readNext();
0537             break;
0538         }
0539     }
0540 }
0541 
0542 void DefinitionData::resolveIncludeKeywords()
0543 {
0544     if (keywordIsLoaded) {
0545         return;
0546     }
0547 
0548     keywordIsLoaded = true;
0549 
0550     for (auto it = keywordLists.begin(); it != keywordLists.end(); ++it) {
0551         it->resolveIncludeKeywords(*this);
0552     }
0553 }
0554 
0555 void DefinitionData::loadContexts(QXmlStreamReader &reader)
0556 {
0557     Q_ASSERT(reader.name() == QLatin1String("contexts"));
0558     Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
0559 
0560     contextDatas.reserve(32);
0561 
0562     while (!reader.atEnd()) {
0563         switch (reader.tokenType()) {
0564         case QXmlStreamReader::StartElement:
0565             if (reader.name() == QLatin1String("context")) {
0566                 contextDatas.push_back(HighlightingContextData());
0567                 contextDatas.back().load(name, reader);
0568             }
0569             reader.readNext();
0570             break;
0571         case QXmlStreamReader::EndElement:
0572             return;
0573         default:
0574             reader.readNext();
0575             break;
0576         }
0577     }
0578 }
0579 
0580 void DefinitionData::resolveContexts()
0581 {
0582     contexts.reserve(contextDatas.size());
0583 
0584     /**
0585      * Transform all HighlightingContextData to Context.
0586      * This is necessary so that Context::resolveContexts() can find the referenced contexts.
0587      */
0588     for (const auto &contextData : std::as_const(contextDatas)) {
0589         contexts.emplace_back(*this, contextData);
0590     }
0591 
0592     /**
0593      * Resolves contexts and rules.
0594      */
0595     auto ctxIt = contexts.begin();
0596     for (const auto &contextData : std::as_const(contextDatas)) {
0597         ctxIt->resolveContexts(*this, contextData);
0598         ++ctxIt;
0599     }
0600 
0601     /**
0602      * To free the memory, constDatas is emptied because it is no longer used.
0603      */
0604     contextDatas.clear();
0605     contextDatas.shrink_to_fit();
0606 
0607     /**
0608      * Resolved includeRules.
0609      */
0610     for (auto &context : contexts) {
0611         context.resolveIncludes(*this);
0612     }
0613 }
0614 
0615 void DefinitionData::loadItemData(QXmlStreamReader &reader)
0616 {
0617     Q_ASSERT(reader.name() == QLatin1String("itemDatas"));
0618     Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
0619 
0620     while (!reader.atEnd()) {
0621         switch (reader.tokenType()) {
0622         case QXmlStreamReader::StartElement:
0623             if (reader.name() == QLatin1String("itemData")) {
0624                 Format f;
0625                 auto formatData = FormatPrivate::detachAndGet(f);
0626                 formatData->definitionName = name;
0627                 formatData->load(reader);
0628                 formatData->id = RepositoryPrivate::get(repo)->nextFormatId();
0629                 formats.insert(f.name(), f);
0630                 reader.readNext();
0631             }
0632             reader.readNext();
0633             break;
0634         case QXmlStreamReader::EndElement:
0635             return;
0636         default:
0637             reader.readNext();
0638             break;
0639         }
0640     }
0641 }
0642 
0643 void DefinitionData::loadGeneral(QXmlStreamReader &reader)
0644 {
0645     Q_ASSERT(reader.name() == QLatin1String("general"));
0646     Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
0647     reader.readNext();
0648 
0649     // reference counter to count XML child elements, to not return too early
0650     int elementRefCounter = 1;
0651 
0652     while (!reader.atEnd()) {
0653         switch (reader.tokenType()) {
0654         case QXmlStreamReader::StartElement:
0655             ++elementRefCounter;
0656 
0657             if (reader.name() == QLatin1String("keywords")) {
0658                 if (reader.attributes().hasAttribute(QLatin1String("casesensitive"))) {
0659                     caseSensitive = Xml::attrToBool(reader.attributes().value(QLatin1String("casesensitive"))) ? Qt::CaseSensitive : Qt::CaseInsensitive;
0660                 }
0661 
0662                 // adapt wordDelimiters
0663                 wordDelimiters.append(reader.attributes().value(QLatin1String("additionalDeliminator")));
0664                 wordDelimiters.remove(reader.attributes().value(QLatin1String("weakDeliminator")));
0665 
0666                 // adapt WordWrapDelimiters
0667                 auto wordWrapDeliminatorAttr = reader.attributes().value(QLatin1String("wordWrapDeliminator"));
0668                 if (wordWrapDeliminatorAttr.isEmpty()) {
0669                     wordWrapDelimiters = wordDelimiters;
0670                 } else {
0671                     wordWrapDelimiters.append(wordWrapDeliminatorAttr);
0672                 }
0673             } else if (reader.name() == QLatin1String("folding")) {
0674                 if (reader.attributes().hasAttribute(QLatin1String("indentationsensitive"))) {
0675                     indentationBasedFolding = Xml::attrToBool(reader.attributes().value(QLatin1String("indentationsensitive")));
0676                 }
0677             } else if (reader.name() == QLatin1String("emptyLines")) {
0678                 loadFoldingIgnoreList(reader);
0679             } else if (reader.name() == QLatin1String("comments")) {
0680                 loadComments(reader);
0681             } else if (reader.name() == QLatin1String("spellchecking")) {
0682                 loadSpellchecking(reader);
0683             } else {
0684                 reader.skipCurrentElement();
0685             }
0686             reader.readNext();
0687             break;
0688         case QXmlStreamReader::EndElement:
0689             --elementRefCounter;
0690             if (elementRefCounter == 0) {
0691                 return;
0692             }
0693             reader.readNext();
0694             break;
0695         default:
0696             reader.readNext();
0697             break;
0698         }
0699     }
0700 }
0701 
0702 void DefinitionData::loadComments(QXmlStreamReader &reader)
0703 {
0704     Q_ASSERT(reader.name() == QLatin1String("comments"));
0705     Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
0706     reader.readNext();
0707 
0708     // reference counter to count XML child elements, to not return too early
0709     int elementRefCounter = 1;
0710 
0711     while (!reader.atEnd()) {
0712         switch (reader.tokenType()) {
0713         case QXmlStreamReader::StartElement:
0714             ++elementRefCounter;
0715             if (reader.name() == QLatin1String("comment")) {
0716                 const bool isSingleLine = reader.attributes().value(QLatin1String("name")) == QLatin1String("singleLine");
0717                 if (isSingleLine) {
0718                     singleLineCommentMarker = reader.attributes().value(QLatin1String("start")).toString();
0719                     const bool afterWhiteSpace = reader.attributes().value(QLatin1String("position")) == QLatin1String("afterwhitespace");
0720                     singleLineCommentPosition = afterWhiteSpace ? CommentPosition::AfterWhitespace : CommentPosition::StartOfLine;
0721                 } else {
0722                     multiLineCommentStartMarker = reader.attributes().value(QLatin1String("start")).toString();
0723                     multiLineCommentEndMarker = reader.attributes().value(QLatin1String("end")).toString();
0724                 }
0725             }
0726             reader.readNext();
0727             break;
0728         case QXmlStreamReader::EndElement:
0729             --elementRefCounter;
0730             if (elementRefCounter == 0) {
0731                 return;
0732             }
0733             reader.readNext();
0734             break;
0735         default:
0736             reader.readNext();
0737             break;
0738         }
0739     }
0740 }
0741 
0742 void DefinitionData::loadFoldingIgnoreList(QXmlStreamReader &reader)
0743 {
0744     Q_ASSERT(reader.name() == QLatin1String("emptyLines"));
0745     Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
0746     reader.readNext();
0747 
0748     // reference counter to count XML child elements, to not return too early
0749     int elementRefCounter = 1;
0750 
0751     while (!reader.atEnd()) {
0752         switch (reader.tokenType()) {
0753         case QXmlStreamReader::StartElement:
0754             ++elementRefCounter;
0755             if (reader.name() == QLatin1String("emptyLine")) {
0756                 foldingIgnoreList << reader.attributes().value(QLatin1String("regexpr")).toString();
0757             }
0758             reader.readNext();
0759             break;
0760         case QXmlStreamReader::EndElement:
0761             --elementRefCounter;
0762             if (elementRefCounter == 0) {
0763                 return;
0764             }
0765             reader.readNext();
0766             break;
0767         default:
0768             reader.readNext();
0769             break;
0770         }
0771     }
0772 }
0773 
0774 void DefinitionData::loadSpellchecking(QXmlStreamReader &reader)
0775 {
0776     Q_ASSERT(reader.name() == QLatin1String("spellchecking"));
0777     Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
0778     reader.readNext();
0779 
0780     // reference counter to count XML child elements, to not return too early
0781     int elementRefCounter = 1;
0782 
0783     while (!reader.atEnd()) {
0784         switch (reader.tokenType()) {
0785         case QXmlStreamReader::StartElement:
0786             ++elementRefCounter;
0787             if (reader.name() == QLatin1String("encoding")) {
0788                 const auto charRef = reader.attributes().value(QLatin1String("char"));
0789                 if (!charRef.isEmpty()) {
0790                     const auto str = reader.attributes().value(QLatin1String("string"));
0791                     characterEncodings.push_back({charRef[0], str.toString()});
0792                 }
0793             }
0794             reader.readNext();
0795             break;
0796         case QXmlStreamReader::EndElement:
0797             --elementRefCounter;
0798             if (elementRefCounter == 0) {
0799                 return;
0800             }
0801             reader.readNext();
0802             break;
0803         default:
0804             reader.readNext();
0805             break;
0806         }
0807     }
0808 }
0809 
0810 bool DefinitionData::checkKateVersion(QStringView verStr)
0811 {
0812     const auto idx = verStr.indexOf(QLatin1Char('.'));
0813     if (idx <= 0) {
0814         qCWarning(Log) << "Skipping" << fileName << "due to having no valid kateversion attribute:" << verStr;
0815         return false;
0816     }
0817     const auto major = verStr.left(idx).toInt();
0818     const auto minor = verStr.mid(idx + 1).toInt();
0819 
0820     if (major > KSYNTAXHIGHLIGHTING_VERSION_MAJOR || (major == KSYNTAXHIGHLIGHTING_VERSION_MAJOR && minor > KSYNTAXHIGHLIGHTING_VERSION_MINOR)) {
0821         qCWarning(Log) << "Skipping" << fileName << "due to being too new, version:" << verStr;
0822         return false;
0823     }
0824 
0825     return true;
0826 }
0827 
0828 quint16 DefinitionData::foldingRegionId(const QString &foldName)
0829 {
0830     hasFoldingRegions = true;
0831     return RepositoryPrivate::get(repo)->foldingRegionId(name, foldName);
0832 }
0833 
0834 void DefinitionData::addImmediateIncludedDefinition(const Definition &def)
0835 {
0836     if (get(def) != this) {
0837         DefinitionRef defRef(def);
0838         if (!immediateIncludedDefinitions.contains(defRef)) {
0839             immediateIncludedDefinitions.push_back(std::move(defRef));
0840         }
0841     }
0842 }
0843 
0844 DefinitionRef::DefinitionRef() = default;
0845 
0846 DefinitionRef::DefinitionRef(const Definition &def) noexcept
0847     : d(def.d)
0848 {
0849 }
0850 
0851 DefinitionRef &DefinitionRef::operator=(const Definition &def) noexcept
0852 {
0853     d = def.d;
0854     return *this;
0855 }
0856 
0857 Definition DefinitionRef::definition() const
0858 {
0859     return Definition(d.lock());
0860 }
0861 
0862 bool DefinitionRef::operator==(const DefinitionRef &other) const
0863 {
0864     return !d.owner_before(other.d) && !other.d.owner_before(d);
0865 }
0866 
0867 bool DefinitionRef::operator==(const Definition &other) const
0868 {
0869     return !d.owner_before(other.d) && !other.d.owner_before(d);
0870 }
0871 
0872 #include "moc_definition.cpp"