1/*
2 SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
3 SPDX-FileCopyrightText: 2018 Dominik Haumann <dhaumann@kde.org>
4 SPDX-FileCopyrightText: 2018 Christoph Cullmann <cullmann@kde.org>
5 SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
6
7 SPDX-License-Identifier: MIT
8*/
9
10#include "definition.h"
11#include "definition_p.h"
12
13#include "context_p.h"
14#include "format.h"
15#include "format_p.h"
16#include "highlightingdata_p.hpp"
17#include "ksyntaxhighlighting_logging.h"
18#include "ksyntaxhighlighting_version.h"
19#include "repository.h"
20#include "repository_p.h"
21#include "rule_p.h"
22#include "worddelimiters_p.h"
23#include "xml_p.h"
24
25#include <QCborMap>
26#include <QCoreApplication>
27#include <QFile>
28#include <QStringTokenizer>
29#include <QXmlStreamReader>
30
31#include <algorithm>
32#include <atomic>
33
34using namespace KSyntaxHighlighting;
35
36DefinitionData::DefinitionData()
37 : wordDelimiters()
38 , wordWrapDelimiters(wordDelimiters)
39{
40}
41
42DefinitionData::~DefinitionData() = default;
43
44Definition::Definition()
45 : d(std::make_shared<DefinitionData>())
46{
47 d->q = d;
48}
49
50Definition::Definition(Definition &&other) noexcept = default;
51Definition::Definition(const Definition &) = default;
52Definition::~Definition() = default;
53Definition &Definition::operator=(Definition &&other) noexcept = default;
54Definition &Definition::operator=(const Definition &) = default;
55
56Definition::Definition(const DefinitionData &defData)
57 : d(defData.q.lock())
58{
59}
60
61bool Definition::operator==(const Definition &other) const
62{
63 return d->fileName == other.d->fileName;
64}
65
66bool Definition::operator!=(const Definition &other) const
67{
68 return d->fileName != other.d->fileName;
69}
70
71bool Definition::isValid() const
72{
73 return d->repo && !d->fileName.isEmpty() && !d->name.isEmpty();
74}
75
76QString Definition::filePath() const
77{
78 return d->fileName;
79}
80
81QString Definition::name() const
82{
83 return d->name;
84}
85
86QStringList Definition::alternativeNames() const
87{
88 return d->alternativeNames;
89}
90
91QString Definition::translatedName() const
92{
93 if (d->translatedName.isEmpty()) {
94 d->translatedName = QCoreApplication::instance()->translate(context: "Language", key: d->nameUtf8.isEmpty() ? d->name.toUtf8().constData() : d->nameUtf8.constData());
95 }
96 return d->translatedName;
97}
98
99QString Definition::section() const
100{
101 return d->section;
102}
103
104QString Definition::translatedSection() const
105{
106 if (d->translatedSection.isEmpty()) {
107 d->translatedSection = QCoreApplication::instance()->translate(context: "Language Section",
108 key: d->sectionUtf8.isEmpty() ? d->section.toUtf8().constData() : d->sectionUtf8.constData());
109 }
110 return d->translatedSection;
111}
112
113QList<QString> Definition::mimeTypes() const
114{
115 return d->mimetypes;
116}
117
118QList<QString> Definition::extensions() const
119{
120 return d->extensions;
121}
122
123int Definition::version() const
124{
125 return d->version;
126}
127
128int Definition::priority() const
129{
130 return d->priority;
131}
132
133bool Definition::isHidden() const
134{
135 return d->hidden;
136}
137
138QString Definition::style() const
139{
140 return d->style;
141}
142
143QString Definition::indenter() const
144{
145 return d->indenter;
146}
147
148QString Definition::author() const
149{
150 return d->author;
151}
152
153QString Definition::license() const
154{
155 return d->license;
156}
157
158bool Definition::isWordDelimiter(QChar c) const
159{
160 d->load();
161 return d->wordDelimiters.contains(c);
162}
163
164bool Definition::isWordWrapDelimiter(QChar c) const
165{
166 d->load();
167 return d->wordWrapDelimiters.contains(c);
168}
169
170bool Definition::foldingEnabled() const
171{
172 if (d->foldingRegionsState == DefinitionData::FoldingRegionsState::Undetermined) {
173 d->load();
174 }
175
176 return d->foldingRegionsState == DefinitionData::FoldingRegionsState::ContainsFoldingRegions || d->indentationBasedFolding;
177}
178
179bool Definition::indentationBasedFoldingEnabled() const
180{
181 d->load();
182 return d->indentationBasedFolding;
183}
184
185QStringList Definition::foldingIgnoreList() const
186{
187 d->load();
188 return d->foldingIgnoreList;
189}
190
191QStringList Definition::keywordLists() const
192{
193 d->load(onlyKeywords: DefinitionData::OnlyKeywords(true));
194 return d->keywordLists.keys();
195}
196
197QStringList Definition::keywordList(const QString &name) const
198{
199 d->load(onlyKeywords: DefinitionData::OnlyKeywords(true));
200 const auto list = d->keywordList(name);
201 return list ? list->keywords() : QStringList();
202}
203
204bool Definition::setKeywordList(const QString &name, const QStringList &content)
205{
206 d->load(onlyKeywords: DefinitionData::OnlyKeywords(true));
207 KeywordList *list = d->keywordList(name);
208 if (list) {
209 list->setKeywordList(content);
210 return true;
211 } else {
212 return false;
213 }
214}
215
216QList<Format> Definition::formats() const
217{
218 d->load();
219
220 // sort formats so that the order matches the order of the itemDatas in the xml files.
221 auto formatList = d->formats.values();
222 std::sort(first: formatList.begin(), last: formatList.end(), comp: [](const KSyntaxHighlighting::Format &lhs, const KSyntaxHighlighting::Format &rhs) {
223 return lhs.id() < rhs.id();
224 });
225
226 return formatList;
227}
228
229QList<Definition> Definition::includedDefinitions() const
230{
231 QList<Definition> definitions;
232
233 if (isValid()) {
234 d->load();
235
236 // init worklist and result used as guard with this definition
237 QVarLengthArray<const DefinitionData *, 4> queue{d.get()};
238 definitions.push_back(t: *this);
239 while (!queue.empty()) {
240 const auto *def = queue.back();
241 queue.pop_back();
242 for (const auto *defData : def->immediateIncludedDefinitions) {
243 auto pred = [defData](const Definition &def) {
244 return DefinitionData::get(def) == defData;
245 };
246 if (std::find_if(first: definitions.begin(), last: definitions.end(), pred: pred) == definitions.end()) {
247 definitions.push_back(t: Definition(*defData));
248 queue.push_back(t: defData);
249 }
250 }
251 }
252
253 // remove the 1st entry, since it is this Definition
254 definitions.front() = std::move(definitions.back());
255 definitions.pop_back();
256 }
257
258 return definitions;
259}
260
261QString Definition::singleLineCommentMarker() const
262{
263 d->load();
264 return d->singleLineCommentMarker;
265}
266
267CommentPosition Definition::singleLineCommentPosition() const
268{
269 d->load();
270 return d->singleLineCommentPosition;
271}
272
273QPair<QString, QString> Definition::multiLineCommentMarker() const
274{
275 d->load();
276 return {d->multiLineCommentStartMarker, d->multiLineCommentEndMarker};
277}
278
279QList<QPair<QChar, QString>> Definition::characterEncodings() const
280{
281 d->load();
282 return d->characterEncodings;
283}
284
285Context *DefinitionData::initialContext()
286{
287 Q_ASSERT(!contexts.empty());
288 return &contexts.front();
289}
290
291Context *DefinitionData::contextByName(QStringView wantedName)
292{
293 for (auto &context : contexts) {
294 if (context.name() == wantedName) {
295 return &context;
296 }
297 }
298 return nullptr;
299}
300
301KeywordList *DefinitionData::keywordList(const QString &wantedName)
302{
303 auto it = keywordLists.find(key: wantedName);
304 return (it == keywordLists.end()) ? nullptr : &it.value();
305}
306
307Format DefinitionData::formatByName(QStringView wantedName) const
308{
309 const auto it = formats.constFind(key: wantedName);
310 if (it != formats.constEnd()) {
311 return it.value();
312 }
313
314 return Format();
315}
316
317bool DefinitionData::isLoaded() const
318{
319 return !contexts.empty();
320}
321
322namespace
323{
324std::atomic<uint64_t> definitionId{1};
325}
326
327bool DefinitionData::load(OnlyKeywords onlyKeywords)
328{
329 if (isLoaded()) {
330 return true;
331 }
332
333 if (fileName.isEmpty()) {
334 return false;
335 }
336
337 if (bool(onlyKeywords) && keywordIsLoaded) {
338 return true;
339 }
340
341 QFile file(fileName);
342 if (!file.open(flags: QFile::ReadOnly)) {
343 return false;
344 }
345
346 QXmlStreamReader reader(&file);
347 while (!reader.atEnd()) {
348 const auto token = reader.readNext();
349 if (token != QXmlStreamReader::StartElement) {
350 continue;
351 }
352
353 if (reader.name() == QLatin1String("highlighting")) {
354 loadHighlighting(reader, onlyKeywords);
355 if (bool(onlyKeywords)) {
356 return true;
357 }
358 }
359
360 else if (reader.name() == QLatin1String("general")) {
361 loadGeneral(reader);
362 }
363 }
364
365 for (auto &kw : keywordLists) {
366 kw.setCaseSensitivity(caseSensitive);
367 }
368
369 resolveContexts();
370
371 id = definitionId.fetch_add(i: 1, m: std::memory_order_relaxed);
372
373 return true;
374}
375
376void DefinitionData::clear()
377{
378 // keep only name and repo, so we can re-lookup to make references persist over repo reloads
379 // see AbstractHighlighterPrivate::ensureDefinitionLoaded()
380 id = 0;
381 keywordLists.clear();
382 contexts.clear();
383 formats.clear();
384 contextDatas.clear();
385 immediateIncludedDefinitions.clear();
386 wordDelimiters = WordDelimiters();
387 wordWrapDelimiters = wordDelimiters;
388 keywordIsLoaded = false;
389 foldingRegionsState = FoldingRegionsState::Undetermined;
390 indentationBasedFolding = false;
391 foldingIgnoreList.clear();
392 singleLineCommentMarker.clear();
393 singleLineCommentPosition = CommentPosition::StartOfLine;
394 multiLineCommentStartMarker.clear();
395 multiLineCommentEndMarker.clear();
396 characterEncodings.clear();
397
398 fileName.clear();
399 nameUtf8.clear();
400 translatedName.clear();
401 section.clear();
402 sectionUtf8.clear();
403 translatedSection.clear();
404 style.clear();
405 indenter.clear();
406 author.clear();
407 license.clear();
408 mimetypes.clear();
409 extensions.clear();
410 caseSensitive = Qt::CaseSensitive;
411 version = 0.0f;
412 priority = 0;
413 hidden = false;
414
415 // purge our cache that is used to unify states
416 unify.clear();
417}
418
419bool DefinitionData::loadMetaData(const QString &definitionFileName)
420{
421 fileName = definitionFileName;
422
423 QFile file(definitionFileName);
424 if (!file.open(flags: QFile::ReadOnly)) {
425 return false;
426 }
427
428 QXmlStreamReader reader(&file);
429 while (!reader.atEnd()) {
430 const auto token = reader.readNext();
431 if (token != QXmlStreamReader::StartElement) {
432 continue;
433 }
434 if (reader.name() == QLatin1String("language")) {
435 return loadLanguage(reader);
436 }
437 }
438
439 return false;
440}
441
442bool DefinitionData::loadMetaData(const QString &file, const QCborMap &obj)
443{
444 name = obj.value(key: QLatin1String("name")).toString();
445 if (name.isEmpty()) {
446 return false;
447 }
448 nameUtf8 = obj.value(key: QLatin1String("name")).toByteArray();
449 section = obj.value(key: QLatin1String("section")).toString();
450 sectionUtf8 = obj.value(key: QLatin1String("section")).toByteArray();
451 version = obj.value(key: QLatin1String("version")).toInteger();
452 priority = obj.value(key: QLatin1String("priority")).toInteger();
453 style = obj.value(key: QLatin1String("style")).toString();
454 author = obj.value(key: QLatin1String("author")).toString();
455 license = obj.value(key: QLatin1String("license")).toString();
456 indenter = obj.value(key: QLatin1String("indenter")).toString();
457 hidden = obj.value(key: QLatin1String("hidden")).toBool();
458 fileName = file;
459
460 const auto names = obj.value(key: QLatin1String("alternativeNames")).toString();
461 alternativeNames = names.split(sep: QLatin1Char(';'), behavior: Qt::SkipEmptyParts);
462
463 const auto exts = obj.value(key: QLatin1String("extensions")).toString();
464 extensions = exts.split(sep: QLatin1Char(';'), behavior: Qt::SkipEmptyParts);
465
466 const auto mts = obj.value(key: QLatin1String("mimetype")).toString();
467 mimetypes = mts.split(sep: QLatin1Char(';'), behavior: Qt::SkipEmptyParts);
468
469 return true;
470}
471
472bool DefinitionData::loadLanguage(QXmlStreamReader &reader)
473{
474 Q_ASSERT(reader.name() == QLatin1String("language"));
475 Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
476
477 if (!checkKateVersion(verStr: reader.attributes().value(qualifiedName: QLatin1String("kateversion")))) {
478 return false;
479 }
480
481 name = reader.attributes().value(qualifiedName: QLatin1String("name")).toString();
482 if (name.isEmpty()) {
483 return false;
484 }
485 const auto names = reader.attributes().value(qualifiedName: QLatin1String("alternativeNames"));
486 for (const auto &n : QStringTokenizer(names, u';', Qt::SkipEmptyParts)) {
487 alternativeNames.push_back(t: n.toString());
488 }
489 section = reader.attributes().value(qualifiedName: QLatin1String("section")).toString();
490 // toFloat instead of toInt for backward compatibility with old Kate files
491 version = reader.attributes().value(qualifiedName: QLatin1String("version")).toFloat();
492 priority = reader.attributes().value(qualifiedName: QLatin1String("priority")).toInt();
493 hidden = Xml::attrToBool(str: reader.attributes().value(qualifiedName: QLatin1String("hidden")));
494 style = reader.attributes().value(qualifiedName: QLatin1String("style")).toString();
495 indenter = reader.attributes().value(qualifiedName: QLatin1String("indenter")).toString();
496 author = reader.attributes().value(qualifiedName: QLatin1String("author")).toString();
497 license = reader.attributes().value(qualifiedName: QLatin1String("license")).toString();
498 const auto exts = reader.attributes().value(qualifiedName: QLatin1String("extensions"));
499 for (const auto &ext : QStringTokenizer(exts, u';', Qt::SkipEmptyParts)) {
500 extensions.push_back(t: ext.toString());
501 }
502 const auto mts = reader.attributes().value(qualifiedName: QLatin1String("mimetype"));
503 for (const auto &mt : QStringTokenizer(mts, u';', Qt::SkipEmptyParts)) {
504 mimetypes.push_back(t: mt.toString());
505 }
506 if (reader.attributes().hasAttribute(qualifiedName: QLatin1String("casesensitive"))) {
507 caseSensitive = Xml::attrToBool(str: reader.attributes().value(qualifiedName: QLatin1String("casesensitive"))) ? Qt::CaseSensitive : Qt::CaseInsensitive;
508 }
509 return true;
510}
511
512void DefinitionData::loadHighlighting(QXmlStreamReader &reader, OnlyKeywords onlyKeywords)
513{
514 Q_ASSERT(reader.name() == QLatin1String("highlighting"));
515 Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
516
517 // skip highlighting
518 reader.readNext();
519
520 while (!reader.atEnd()) {
521 switch (reader.tokenType()) {
522 case QXmlStreamReader::StartElement:
523 if (reader.name() == QLatin1String("list")) {
524 if (!keywordIsLoaded) {
525 KeywordList keywords;
526 keywords.load(reader);
527 keywordLists.insert(key: keywords.name(), value: keywords);
528 } else {
529 reader.skipCurrentElement();
530 reader.readNext(); // Skip </list>
531 }
532 } else if (bool(onlyKeywords)) {
533 resolveIncludeKeywords();
534 return;
535 } else if (reader.name() == QLatin1String("contexts")) {
536 resolveIncludeKeywords();
537 loadContexts(reader);
538 reader.readNext();
539 } else if (reader.name() == QLatin1String("itemDatas")) {
540 loadItemData(reader);
541 } else {
542 reader.readNext();
543 }
544 break;
545 case QXmlStreamReader::EndElement:
546 return;
547 default:
548 reader.readNext();
549 break;
550 }
551 }
552}
553
554void DefinitionData::resolveIncludeKeywords()
555{
556 if (keywordIsLoaded) {
557 return;
558 }
559
560 keywordIsLoaded = true;
561
562 for (auto &kw : keywordLists) {
563 kw.resolveIncludeKeywords(def&: *this);
564 }
565}
566
567void DefinitionData::loadContexts(QXmlStreamReader &reader)
568{
569 Q_ASSERT(reader.name() == QLatin1String("contexts"));
570 Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
571
572 contextDatas.reserve(n: 32);
573
574 while (!reader.atEnd()) {
575 switch (reader.tokenType()) {
576 case QXmlStreamReader::StartElement:
577 if (reader.name() == QLatin1String("context")) {
578 contextDatas.emplace_back().load(defName: name, reader);
579 }
580 reader.readNext();
581 break;
582 case QXmlStreamReader::EndElement:
583 return;
584 default:
585 reader.readNext();
586 break;
587 }
588 }
589}
590
591void DefinitionData::resolveContexts()
592{
593 contexts.reserve(n: contextDatas.size());
594
595 /**
596 * Transform all HighlightingContextData to Context.
597 * This is necessary so that Context::resolveContexts() can find the referenced contexts.
598 */
599 for (const auto &contextData : std::as_const(t&: contextDatas)) {
600 contexts.emplace_back(args&: *this, args: contextData);
601 }
602
603 /**
604 * Resolves contexts and rules.
605 */
606 auto ctxIt = contexts.begin();
607 for (const auto &contextData : std::as_const(t&: contextDatas)) {
608 ctxIt->resolveContexts(def&: *this, data: contextData);
609 ++ctxIt;
610 }
611
612 /**
613 * To free the memory, constDatas is emptied because it is no longer used.
614 */
615 contextDatas.clear();
616 contextDatas.shrink_to_fit();
617
618 /**
619 * Resolved includeRules.
620 */
621 for (auto &context : contexts) {
622 context.resolveIncludes(def&: *this);
623 }
624
625 // when a context includes a folding region this value is Yes, otherwise it remains undetermined
626 if (foldingRegionsState == FoldingRegionsState::Undetermined) {
627 foldingRegionsState = FoldingRegionsState::NoFoldingRegions;
628 }
629}
630
631void DefinitionData::loadItemData(QXmlStreamReader &reader)
632{
633 Q_ASSERT(reader.name() == QLatin1String("itemDatas"));
634 Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
635
636 while (!reader.atEnd()) {
637 switch (reader.tokenType()) {
638 case QXmlStreamReader::StartElement:
639 if (reader.name() == QLatin1String("itemData")) {
640 Format f;
641 auto formatData = FormatPrivate::detachAndGet(format&: f);
642 formatData->definitionName = name;
643 formatData->load(reader);
644 formatData->id = RepositoryPrivate::get(repo)->nextFormatId();
645 formats.insert(key: f.name(), value: f);
646 reader.readNext();
647 }
648 reader.readNext();
649 break;
650 case QXmlStreamReader::EndElement:
651 return;
652 default:
653 reader.readNext();
654 break;
655 }
656 }
657}
658
659void DefinitionData::loadGeneral(QXmlStreamReader &reader)
660{
661 Q_ASSERT(reader.name() == QLatin1String("general"));
662 Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
663 reader.readNext();
664
665 // reference counter to count XML child elements, to not return too early
666 int elementRefCounter = 1;
667
668 while (!reader.atEnd()) {
669 switch (reader.tokenType()) {
670 case QXmlStreamReader::StartElement:
671 ++elementRefCounter;
672
673 if (reader.name() == QLatin1String("keywords")) {
674 if (reader.attributes().hasAttribute(qualifiedName: QLatin1String("casesensitive"))) {
675 caseSensitive = Xml::attrToBool(str: reader.attributes().value(qualifiedName: QLatin1String("casesensitive"))) ? Qt::CaseSensitive : Qt::CaseInsensitive;
676 }
677
678 // adapt wordDelimiters
679 wordDelimiters.append(s: reader.attributes().value(qualifiedName: QLatin1String("additionalDeliminator")));
680 wordDelimiters.remove(c: reader.attributes().value(qualifiedName: QLatin1String("weakDeliminator")));
681
682 // adapt WordWrapDelimiters
683 auto wordWrapDeliminatorAttr = reader.attributes().value(qualifiedName: QLatin1String("wordWrapDeliminator"));
684 if (wordWrapDeliminatorAttr.isEmpty()) {
685 wordWrapDelimiters = wordDelimiters;
686 } else {
687 wordWrapDelimiters.append(s: wordWrapDeliminatorAttr);
688 }
689 } else if (reader.name() == QLatin1String("folding")) {
690 if (reader.attributes().hasAttribute(qualifiedName: QLatin1String("indentationsensitive"))) {
691 indentationBasedFolding = Xml::attrToBool(str: reader.attributes().value(qualifiedName: QLatin1String("indentationsensitive")));
692 }
693 } else if (reader.name() == QLatin1String("emptyLines")) {
694 loadFoldingIgnoreList(reader);
695 } else if (reader.name() == QLatin1String("comments")) {
696 loadComments(reader);
697 } else if (reader.name() == QLatin1String("spellchecking")) {
698 loadSpellchecking(reader);
699 } else {
700 reader.skipCurrentElement();
701 }
702 reader.readNext();
703 break;
704 case QXmlStreamReader::EndElement:
705 --elementRefCounter;
706 if (elementRefCounter == 0) {
707 return;
708 }
709 reader.readNext();
710 break;
711 default:
712 reader.readNext();
713 break;
714 }
715 }
716}
717
718void DefinitionData::loadComments(QXmlStreamReader &reader)
719{
720 Q_ASSERT(reader.name() == QLatin1String("comments"));
721 Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
722 reader.readNext();
723
724 // reference counter to count XML child elements, to not return too early
725 int elementRefCounter = 1;
726
727 while (!reader.atEnd()) {
728 switch (reader.tokenType()) {
729 case QXmlStreamReader::StartElement:
730 ++elementRefCounter;
731 if (reader.name() == QLatin1String("comment")) {
732 const bool isSingleLine = reader.attributes().value(qualifiedName: QLatin1String("name")) == QLatin1String("singleLine");
733 if (isSingleLine) {
734 singleLineCommentMarker = reader.attributes().value(qualifiedName: QLatin1String("start")).toString();
735 const bool afterWhiteSpace = reader.attributes().value(qualifiedName: QLatin1String("position")) == QLatin1String("afterwhitespace");
736 singleLineCommentPosition = afterWhiteSpace ? CommentPosition::AfterWhitespace : CommentPosition::StartOfLine;
737 } else {
738 multiLineCommentStartMarker = reader.attributes().value(qualifiedName: QLatin1String("start")).toString();
739 multiLineCommentEndMarker = reader.attributes().value(qualifiedName: QLatin1String("end")).toString();
740 }
741 }
742 reader.readNext();
743 break;
744 case QXmlStreamReader::EndElement:
745 --elementRefCounter;
746 if (elementRefCounter == 0) {
747 return;
748 }
749 reader.readNext();
750 break;
751 default:
752 reader.readNext();
753 break;
754 }
755 }
756}
757
758void DefinitionData::loadFoldingIgnoreList(QXmlStreamReader &reader)
759{
760 Q_ASSERT(reader.name() == QLatin1String("emptyLines"));
761 Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
762 reader.readNext();
763
764 // reference counter to count XML child elements, to not return too early
765 int elementRefCounter = 1;
766
767 while (!reader.atEnd()) {
768 switch (reader.tokenType()) {
769 case QXmlStreamReader::StartElement:
770 ++elementRefCounter;
771 if (reader.name() == QLatin1String("emptyLine")) {
772 foldingIgnoreList << reader.attributes().value(qualifiedName: QLatin1String("regexpr")).toString();
773 }
774 reader.readNext();
775 break;
776 case QXmlStreamReader::EndElement:
777 --elementRefCounter;
778 if (elementRefCounter == 0) {
779 return;
780 }
781 reader.readNext();
782 break;
783 default:
784 reader.readNext();
785 break;
786 }
787 }
788}
789
790void DefinitionData::loadSpellchecking(QXmlStreamReader &reader)
791{
792 Q_ASSERT(reader.name() == QLatin1String("spellchecking"));
793 Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
794 reader.readNext();
795
796 // reference counter to count XML child elements, to not return too early
797 int elementRefCounter = 1;
798
799 while (!reader.atEnd()) {
800 switch (reader.tokenType()) {
801 case QXmlStreamReader::StartElement:
802 ++elementRefCounter;
803 if (reader.name() == QLatin1String("encoding")) {
804 const auto charRef = reader.attributes().value(qualifiedName: QLatin1String("char"));
805 if (!charRef.isEmpty()) {
806 const auto str = reader.attributes().value(qualifiedName: QLatin1String("string"));
807 characterEncodings.push_back(t: {charRef[0], str.toString()});
808 }
809 }
810 reader.readNext();
811 break;
812 case QXmlStreamReader::EndElement:
813 --elementRefCounter;
814 if (elementRefCounter == 0) {
815 return;
816 }
817 reader.readNext();
818 break;
819 default:
820 reader.readNext();
821 break;
822 }
823 }
824}
825
826bool DefinitionData::checkKateVersion(QStringView verStr)
827{
828 const auto idx = verStr.indexOf(c: QLatin1Char('.'));
829 if (idx <= 0) {
830 qCWarning(Log) << "Skipping" << fileName << "due to having no valid kateversion attribute:" << verStr;
831 return false;
832 }
833 const auto major = verStr.sliced(pos: 0, n: idx).toInt();
834 const auto minor = verStr.sliced(pos: idx + 1).toInt();
835
836 if (major > KSYNTAXHIGHLIGHTING_VERSION_MAJOR || (major == KSYNTAXHIGHLIGHTING_VERSION_MAJOR && minor > KSYNTAXHIGHLIGHTING_VERSION_MINOR)) {
837 qCWarning(Log) << "Skipping" << fileName << "due to being too new, version:" << verStr;
838 return false;
839 }
840
841 return true;
842}
843
844quint16 DefinitionData::foldingRegionId(const QString &foldName)
845{
846 foldingRegionsState = FoldingRegionsState::ContainsFoldingRegions;
847 return RepositoryPrivate::get(repo)->foldingRegionId(defName: name, foldName);
848}
849
850DefinitionData::ResolvedContext DefinitionData::resolveIncludedContext(QStringView defName, QStringView contextName)
851{
852 if (defName.isEmpty()) {
853 return {.def: this, .context: contextByName(wantedName: contextName)};
854 }
855
856 auto d = repo->definitionForName(defName: defName.toString());
857 if (d.isValid()) {
858 auto *resolvedDef = get(def: d);
859 if (resolvedDef != this) {
860 if (std::find(first: immediateIncludedDefinitions.begin(), last: immediateIncludedDefinitions.end(), val: resolvedDef) == immediateIncludedDefinitions.end()) {
861 immediateIncludedDefinitions.push_back(t: resolvedDef);
862 resolvedDef->load();
863 if (resolvedDef->foldingRegionsState == FoldingRegionsState::ContainsFoldingRegions) {
864 foldingRegionsState = FoldingRegionsState::ContainsFoldingRegions;
865 }
866 }
867 }
868 if (contextName.isEmpty()) {
869 return {.def: resolvedDef, .context: resolvedDef->initialContext()};
870 } else {
871 return {.def: resolvedDef, .context: resolvedDef->contextByName(wantedName: contextName)};
872 }
873 }
874
875 return {.def: nullptr, .context: nullptr};
876}
877
878#include "moc_definition.cpp"
879

source code of syntax-highlighting/src/lib/definition.cpp