KSyntaxHighlighting

katehighlightingindexer.cpp
1 /*
2  Copyright (C) 2014 Christoph Cullmann <[email protected]>
3 
4  Permission is hereby granted, free of charge, to any person obtaining
5  a copy of this software and associated documentation files (the
6  "Software"), to deal in the Software without restriction, including
7  without limitation the rights to use, copy, modify, merge, publish,
8  distribute, sublicense, and/or sell copies of the Software, and to
9  permit persons to whom the Software is furnished to do so, subject to
10  the following conditions:
11 
12  The above copyright notice and this permission notice shall be included
13  in all copies or substantial portions of the Software.
14 
15  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23 
24 #include <QCoreApplication>
25 #include <QDebug>
26 #include <QFile>
27 #include <QFileInfo>
28 #include <QCborValue>
29 #include <QRegularExpression>
30 #include <QVariant>
31 #include <QXmlStreamReader>
32 
33 #ifdef QT_XMLPATTERNS_LIB
34 #include <QXmlSchema>
35 #include <QXmlSchemaValidator>
36 #endif
37 
38 namespace
39 {
40 QStringList readListing(const QString &fileName)
41 {
42  QFile file(fileName);
43  if (!file.open(QIODevice::ReadOnly)) {
44  return QStringList();
45  }
46 
47  QXmlStreamReader xml(&file);
48  QStringList listing;
49  while (!xml.atEnd()) {
50  xml.readNext();
51 
52  // add only .xml files, no .json or stuff
53  if (xml.isCharacters() && xml.text().toString().contains(QLatin1String(".xml"))) {
54  listing.append(xml.text().toString());
55  }
56  }
57 
58  if (xml.hasError()) {
59  qWarning() << "XML error while reading" << fileName << " - " << qPrintable(xml.errorString()) << "@ offset" << xml.characterOffset();
60  listing.clear();
61  }
62 
63  return listing;
64 }
65 
71 bool checkExtensions(const QString &extensions)
72 {
73  // get list of extensions
74 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
75  const QStringList extensionParts = extensions.split(QLatin1Char(';'), QString::SkipEmptyParts);
76 #else
77  const QStringList extensionParts = extensions.split(QLatin1Char(';'), Qt::SkipEmptyParts);
78 #endif
79 
80  // ok if empty
81  if (extensionParts.isEmpty()) {
82  return true;
83  }
84 
85  // check that only valid wildcard things are inside the parts
86  for (const auto &extension : extensionParts) {
87  for (const auto c : extension) {
88  // eat normal things
89  if (c.isDigit() || c.isLetter()) {
90  continue;
91  }
92 
93  // allow some special characters
94  if (c == QLatin1Char('.') || c == QLatin1Char('-') || c == QLatin1Char('_') || c == QLatin1Char('+')) {
95  continue;
96  }
97 
98  // only allowed wildcard things: '?' and '*'
99  if (c == QLatin1Char('?') || c == QLatin1Char('*')) {
100  continue;
101  }
102 
103  qWarning() << "invalid character" << c << " seen in extensions wildcard";
104  return false;
105  }
106  }
107 
108  // all checks passed
109  return true;
110 }
111 
117 bool checkRegularExpression(const QString &hlFilename, QXmlStreamReader &xml)
118 {
119  if (xml.name() == QLatin1String("RegExpr") || xml.name() == QLatin1String("emptyLine")) {
120  // get right attribute
121  const QString string(xml.attributes().value((xml.name() == QLatin1String("RegExpr")) ? QLatin1String("String") : QLatin1String("regexpr")).toString());
122 
123  // validate regexp
124  const QRegularExpression regexp(string);
125  if (!regexp.isValid()) {
126  qWarning() << hlFilename << "line" << xml.lineNumber() << "broken regex:" << string << "problem:" << regexp.errorString() << "at offset" << regexp.patternErrorOffset();
127  return false;
128  } else if (string.isEmpty()) {
129  qWarning() << hlFilename << "line" << xml.lineNumber() << "empty regex not allowed.";
130  return false;
131  }
132 
133  // catch possible case typos: [A-z] or [a-Z]
134  const int azOffset = std::max(string.indexOf(QStringLiteral("A-z")), string.indexOf(QStringLiteral("a-Z")));
135  if (azOffset >= 0) {
136  qWarning() << hlFilename << "line" << xml.lineNumber() << "broken regex:" << string << "problem: [a-Z] or [A-z] at offset" << azOffset;
137  return false;
138  }
139 
140  // dynamic == true and no place holder?
141  if (xml.name() == QLatin1String("RegExpr") && xml.attributes().value(QStringLiteral("dynamic")) == QStringLiteral("true")) {
142  static const QRegularExpression placeHolder(QStringLiteral("%\\d+"));
143  if (!string.contains(placeHolder)) {
144  qWarning() << hlFilename << "line" << xml.lineNumber() << "broken regex:" << string << "problem: dynamic=true but no %\\d+ placeholder";
145  return false;
146  }
147  }
148  }
149 
150  return true;
151 }
152 
155 bool checkItemsTrimmed(const QString &hlFilename, QXmlStreamReader &xml)
156 {
157  if (xml.name() == QLatin1String("item")) {
158  const QString keyword = xml.readElementText();
159  if (keyword != keyword.trimmed()) {
160  qWarning() << hlFilename << "line" << xml.lineNumber() << "keyword with leading/trailing spaces:" << keyword;
161  return false;
162  }
163  }
164 
165  return true;
166 }
167 
170 bool checkSingleChars(const QString &hlFilename, QXmlStreamReader &xml)
171 {
172  const bool testChar1 = xml.name() == QLatin1String("Detect2Chars");
173  const bool testChar = testChar1 || xml.name() == QLatin1String("DetectChar");
174 
175  if (testChar) {
176  const QString c = xml.attributes().value(QLatin1String("char")).toString();
177  if (c.size() != 1) {
178  qWarning() << hlFilename << "line" << xml.lineNumber() << "'char' must contain exactly one char:" << c;
179  return false;
180  }
181  }
182 
183  if (testChar1) {
184  const QString c = xml.attributes().value(QLatin1String("char1")).toString();
185  if (c.size() != 1) {
186  qWarning() << hlFilename << "line" << xml.lineNumber() << "'char1' must contain exactly one char:" << c;
187  return false;
188  }
189  }
190 
191  return true;
192 }
193 
196 bool checkLookAhead(const QString &hlFilename, QXmlStreamReader &xml)
197 {
198  if (xml.attributes().hasAttribute(QStringLiteral("lookAhead"))) {
199  auto lookAhead = xml.attributes().value(QStringLiteral("lookAhead"));
200  if (lookAhead == QStringLiteral("true")) {
201  auto context = xml.attributes().value(QStringLiteral("context"));
202  if (context == QStringLiteral("#stay")) {
203  qWarning() << hlFilename << "line" << xml.lineNumber() << "Infinite loop: lookAhead with context #stay";
204  return false;
205  }
206  }
207  }
208  return true;
209 }
210 
214 class KeywordIncludeChecker
215 {
216 public:
217  void processElement(const QString &hlFilename, const QString &hlName, QXmlStreamReader &xml)
218  {
219  if (xml.name() == QLatin1String("list")) {
220  auto &keywords = m_keywordMap[hlName];
221  keywords.filename = hlFilename;
222  auto name = xml.attributes().value(QLatin1String("name")).toString();
223  m_currentIncludes = &keywords.includes[name];
224  } else if (xml.name() == QLatin1String("include")) {
225  if (!m_currentIncludes) {
226  qWarning() << hlFilename << "line" << xml.lineNumber() << "<include> tag ouside <list>";
227  m_success = false;
228  } else {
229  m_currentIncludes->push_back({xml.lineNumber(), xml.readElementText()});
230  }
231  }
232  }
233 
234  bool check() const
235  {
236  bool success = m_success;
237  for (auto &keywords : m_keywordMap) {
238  QMapIterator<QString, QVector<Keywords::Include>> includes(keywords.includes);
239  while (includes.hasNext()) {
240  includes.next();
241  for (auto &include : includes.value()) {
242  bool containsKeywordName = true;
243  int const idx = include.name.indexOf(QStringLiteral("##"));
244  if (idx == -1) {
245  auto &keywordName = includes.key();
246  containsKeywordName = keywords.includes.contains(keywordName);
247  } else {
248  auto defName = include.name.mid(idx + 2);
249  auto listName = include.name.left(idx);
250  auto it = m_keywordMap.find(defName);
251  if (it == m_keywordMap.end()) {
252  qWarning() << keywords.filename << "line" << include.line << "unknown definition in" << include.name;
253  success = false;
254  } else {
255  containsKeywordName = it->includes.contains(listName);
256  }
257  }
258 
259  if (!containsKeywordName) {
260  qWarning() << keywords.filename << "line" << include.line << "unknown keyword name in" << include.name;
261  success = false;
262  }
263  }
264  }
265  }
266  return success;
267  }
268 
269 private:
270  struct Keywords {
271  QString filename;
272  struct Include {
273  qint64 line;
274  QString name;
275  };
277  };
278  QHash<QString, Keywords> m_keywordMap;
279  QVector<Keywords::Include> *m_currentIncludes = nullptr;
280  bool m_success = true;
281 };
282 
286 class KeywordChecker
287 {
288 public:
289  KeywordChecker(const QString &filename)
290  : m_filename(filename)
291  {
292  }
293 
294  void processElement(QXmlStreamReader &xml)
295  {
296  if (xml.name() == QLatin1String("list")) {
297  const QString name = xml.attributes().value(QLatin1String("name")).toString();
298  if (m_existingNames.contains(name)) {
299  qWarning() << m_filename << "list duplicate:" << name;
300  m_success = false;
301  }
302  m_existingNames.insert(name);
303  } else if (xml.name() == QLatin1String("keyword")) {
304  const QString context = xml.attributes().value(QLatin1String("String")).toString();
305  if (!context.isEmpty())
306  m_usedNames.insert(context);
307  }
308  }
309 
310  bool check() const
311  {
312  bool success = m_success;
313  const auto invalidNames = m_usedNames - m_existingNames;
314  if (!invalidNames.isEmpty()) {
315  qWarning() << m_filename << "Reference of non-existing keyword list:" << invalidNames;
316  success = false;
317  }
318 
319  const auto unusedNames = m_existingNames - m_usedNames;
320  if (!unusedNames.isEmpty()) {
321  qWarning() << m_filename << "Unused keyword lists:" << unusedNames;
322  success = false;
323  }
324 
325  return success;
326  }
327 
328 private:
329  QString m_filename;
330  QSet<QString> m_usedNames;
331  QSet<QString> m_existingNames;
332  bool m_success = true;
333 };
334 
338 class ContextChecker
339 {
340 public:
341  void setKateVersion(const QStringRef &verStr, const QString &hlFilename, const QString &hlName)
342  {
343  const auto idx = verStr.indexOf(QLatin1Char('.'));
344  if (idx <= 0) {
345  qWarning() << hlFilename << "invalid kateversion" << verStr;
346  m_success = false;
347  } else {
348  auto &language = m_contextMap[hlName];
349  language.version = {verStr.left(idx).toInt(), verStr.mid(idx + 1).toInt()};
350  }
351  }
352 
353  void processElement(const QString &hlFilename, const QString &hlName, QXmlStreamReader &xml)
354  {
355  if (xml.name() == QLatin1String("context")) {
356  auto &language = m_contextMap[hlName];
357  language.hlFilename = hlFilename;
358  const QString name = xml.attributes().value(QLatin1String("name")).toString();
359  if (language.isFirstContext) {
360  language.isFirstContext = false;
361  language.usedContextNames.insert(name);
362  }
363 
364  if (language.existingContextNames.contains(name)) {
365  qWarning() << hlFilename << "Duplicate context:" << name;
366  m_success = false;
367  } else {
368  language.existingContextNames.insert(name);
369  }
370 
371  if (xml.attributes().value(QLatin1String("fallthroughContext")).toString() == QLatin1String("#stay")) {
372  qWarning() << hlFilename << "possible infinite loop due to fallthroughContext=\"#stay\" in context " << name;
373  m_success = false;
374  }
375 
376  processContext(hlName, xml.attributes().value(QLatin1String("lineEndContext")).toString());
377  processContext(hlName, xml.attributes().value(QLatin1String("lineEmptyContext")).toString());
378  processContext(hlName, xml.attributes().value(QLatin1String("fallthroughContext")).toString());
379  } else if (xml.name() == QLatin1String("include")) {
380  // <include> tag inside <list>
381  processVersion(hlFilename, hlName, xml, {5, 53}, QLatin1String("<include>"));
382  } else {
383  if (xml.attributes().hasAttribute(QLatin1String("context"))) {
384  const QString context = xml.attributes().value(QLatin1String("context")).toString();
385  if (context.isEmpty()) {
386  qWarning() << hlFilename << "Missing context name in line" << xml.lineNumber();
387  m_success = false;
388  } else {
389  processContext(hlName, context);
390  }
391  }
392  }
393  }
394 
395  bool check() const
396  {
397  bool success = m_success;
398 
399  // recursive search for the required miximal version
400  struct GetRequiredVersion {
402 
403  Version operator()(const QHash<QString, Language> &contextMap, const Language &language)
404  {
405  auto &version = versionMap[&language];
406  if (version < language.version) {
407  version = language.version;
408  for (auto &languageName : language.usedLanguageName) {
409  auto it = contextMap.find(languageName);
410  if (it != contextMap.end()) {
411  version = std::max(operator()(contextMap, *it), version);
412  }
413  }
414  }
415  return version;
416  };
417  };
418  GetRequiredVersion getRequiredVersion;
419 
420  for (auto &language : m_contextMap) {
421  const auto invalidContextNames = language.usedContextNames - language.existingContextNames;
422  if (!invalidContextNames.isEmpty()) {
423  qWarning() << language.hlFilename << "Reference of non-existing contexts:" << invalidContextNames;
424  success = false;
425  }
426 
427  const auto unusedNames = language.existingContextNames - language.usedContextNames;
428  if (!unusedNames.isEmpty()) {
429  qWarning() << language.hlFilename << "Unused contexts:" << unusedNames;
430  success = false;
431  }
432 
433  auto requiredVersion = getRequiredVersion(m_contextMap, language);
434  if (language.version < requiredVersion) {
435  qWarning().nospace() << language.hlFilename << " depends on a language in version " << requiredVersion.majorRevision << "." << requiredVersion.minorRevision << ". Please, increase kateversion.";
436  success = false;
437  }
438  }
439 
440  return success;
441  }
442 
443 private:
452  void processContext(const QString &language, QString context)
453  {
454  if (context.isEmpty())
455  return;
456 
457  // filter out #stay and #pop
458  static QRegularExpression stayPop(QStringLiteral("^(#stay|#pop)+"));
459  context.remove(stayPop);
460 
461  // handle cross-language context references
462  if (context.contains(QStringLiteral("##"))) {
463 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
464  const QStringList list = context.split(QStringLiteral("##"), QString::SkipEmptyParts);
465 #else
466  const QStringList list = context.split(QStringLiteral("##"), Qt::SkipEmptyParts);
467 #endif
468  if (list.size() == 1) {
469  // nothing to do, other language is included: e.g. ##Doxygen
470  } else if (list.size() == 2) {
471  // specific context of other language, e.g. Comment##ISO C++
472  m_contextMap[list[1]].usedContextNames.insert(list[0]);
473  m_contextMap[language].usedLanguageName.insert(list[1]);
474  }
475  return;
476  }
477 
478  // handle #pop!context" (#pop was already removed above)
479  if (context.startsWith(QLatin1Char('!')))
480  context.remove(0, 1);
481 
482  if (!context.isEmpty())
483  m_contextMap[language].usedContextNames.insert(context);
484  }
485 
486 private:
487  struct Version {
488  int majorRevision;
489  int minorRevision;
490 
491  Version(int majorRevision = 0, int minorRevision = 0)
492  : majorRevision(majorRevision)
493  , minorRevision(minorRevision)
494  {
495  }
496 
497  bool operator<(const Version &version) const
498  {
499  return majorRevision < version.majorRevision || (majorRevision == version.majorRevision && minorRevision < version.minorRevision);
500  }
501  };
502 
503  void processVersion(const QString &hlFilename, const QString &hlName, QXmlStreamReader &xml, Version const &requiredVersion, QLatin1String item)
504  {
505  auto &language = m_contextMap[hlName];
506 
507  if (language.version < requiredVersion) {
508  qWarning().nospace() << hlFilename << " " << item << " in line " << xml.lineNumber() << " is only available since version " << requiredVersion.majorRevision << "." << requiredVersion.minorRevision
509  << ". Please, increase kateversion.";
510  // update the version to cancel future warnings
511  language.version = requiredVersion;
512  m_success = false;
513  }
514  }
515 
516  class Language
517  {
518  public:
519  // filename on disk or in Qt resource
520  QString hlFilename;
521 
522  // Is true, if the first context in xml file is encountered, and
523  // false in all other cases. This is required, since the first context
524  // is typically not referenced explicitly. So we will simply add the
525  // first context to the usedContextNames list.
526  bool isFirstContext = true;
527 
528  // holds all contexts that were referenced
529  QSet<QString> usedContextNames;
530 
531  // holds all existing context names
532  QSet<QString> existingContextNames;
533 
534  // holds all existing language names
535  QSet<QString> usedLanguageName;
536 
537  // kateversion language attribute
539  };
540 
545  QHash<QString, Language> m_contextMap;
546  bool m_success = true;
547 };
548 
552 class AttributeChecker
553 {
554 public:
555  AttributeChecker(const QString &filename)
556  : m_filename(filename)
557  {
558  }
559 
560  void processElement(QXmlStreamReader &xml)
561  {
562  if (xml.name() == QLatin1String("itemData")) {
563  const QString name = xml.attributes().value(QLatin1String("name")).toString();
564  if (!name.isEmpty()) {
565  if (m_existingAttributeNames.contains(name)) {
566  qWarning() << m_filename << "itemData duplicate:" << name;
567  m_success = false;
568  } else {
569  m_existingAttributeNames.insert(name);
570  }
571  }
572  } else if (xml.attributes().hasAttribute(QLatin1String("attribute"))) {
573  const QString name = xml.attributes().value(QLatin1String("attribute")).toString();
574  if (name.isEmpty()) {
575  qWarning() << m_filename << "specified attribute is empty:" << xml.name();
576  m_success = false;
577  } else {
578  m_usedAttributeNames.insert(name);
579  }
580  }
581  }
582 
583  bool check() const
584  {
585  bool success = m_success;
586  const auto invalidNames = m_usedAttributeNames - m_existingAttributeNames;
587  if (!invalidNames.isEmpty()) {
588  qWarning() << m_filename << "Reference of non-existing itemData attributes:" << invalidNames;
589  success = false;
590  }
591 
592  auto unusedNames = m_existingAttributeNames - m_usedAttributeNames;
593  if (!unusedNames.isEmpty()) {
594  qWarning() << m_filename << "Unused itemData:" << unusedNames;
595  success = false;
596  }
597 
598  return success;
599  }
600 
601 private:
602  QString m_filename;
603  QSet<QString> m_usedAttributeNames;
604  QSet<QString> m_existingAttributeNames;
605  bool m_success = true;
606 };
607 
608 }
609 
610 int main(int argc, char *argv[])
611 {
612  // get app instance
613  QCoreApplication app(argc, argv);
614 
615  // ensure enough arguments are passed
616  if (app.arguments().size() < 3)
617  return 1;
618 
619 #ifdef QT_XMLPATTERNS_LIB
620  // open schema
621  QXmlSchema schema;
622  if (!schema.load(QUrl::fromLocalFile(app.arguments().at(2))))
623  return 2;
624 #endif
625 
626  const QString hlFilenamesListing = app.arguments().value(3);
627  if (hlFilenamesListing.isEmpty()) {
628  return 1;
629  }
630 
631  QStringList hlFilenames = readListing(hlFilenamesListing);
632  if (hlFilenames.isEmpty()) {
633  qWarning("Failed to read %s", qPrintable(hlFilenamesListing));
634  return 3;
635  }
636 
637  // text attributes
638  const QStringList textAttributes = QStringList() << QStringLiteral("name") << QStringLiteral("section") << QStringLiteral("mimetype") << QStringLiteral("extensions") << QStringLiteral("style") << QStringLiteral("author")
639  << QStringLiteral("license") << QStringLiteral("indenter");
640 
641  // index all given highlightings
642  ContextChecker contextChecker;
643  KeywordIncludeChecker keywordIncludeChecker;
644  QVariantMap hls;
645  int anyError = 0;
646  for (const QString &hlFilename : qAsConst(hlFilenames)) {
647  QFile hlFile(hlFilename);
648  if (!hlFile.open(QIODevice::ReadOnly)) {
649  qWarning("Failed to open %s", qPrintable(hlFilename));
650  anyError = 3;
651  continue;
652  }
653 
654 #ifdef QT_XMLPATTERNS_LIB
655  // validate against schema
656  QXmlSchemaValidator validator(schema);
657  if (!validator.validate(&hlFile, QUrl::fromLocalFile(hlFile.fileName()))) {
658  anyError = 4;
659  continue;
660  }
661 #endif
662 
663  // read the needed attributes from toplevel language tag
664  hlFile.reset();
665  QXmlStreamReader xml(&hlFile);
666  if (xml.readNextStartElement()) {
667  if (xml.name() != QLatin1String("language")) {
668  anyError = 5;
669  continue;
670  }
671  } else {
672  anyError = 6;
673  continue;
674  }
675 
676  // map to store hl info
677  QVariantMap hl;
678 
679  // transfer text attributes
680  for (const QString &attribute : qAsConst(textAttributes)) {
681  hl[attribute] = xml.attributes().value(attribute).toString();
682  }
683 
684  // check if extensions have the right format
685  if (!checkExtensions(hl[QStringLiteral("extensions")].toString())) {
686  qWarning() << hlFilename << "'extensions' wildcards invalid:" << hl[QStringLiteral("extensions")].toString();
687  anyError = 23;
688  }
689 
690  // numerical attributes
691  hl[QStringLiteral("version")] = xml.attributes().value(QLatin1String("version")).toInt();
692  hl[QStringLiteral("priority")] = xml.attributes().value(QLatin1String("priority")).toInt();
693 
694  // add boolean one
695  const QString hidden = xml.attributes().value(QLatin1String("hidden")).toString();
696  hl[QStringLiteral("hidden")] = (hidden == QLatin1String("true") || hidden == QLatin1String("1"));
697 
698  // remember hl
699  hls[QFileInfo(hlFile).fileName()] = hl;
700 
701  AttributeChecker attributeChecker(hlFilename);
702  KeywordChecker keywordChecker(hlFilename);
703 
704  const QString hlName = hl[QStringLiteral("name")].toString();
705 
706  contextChecker.setKateVersion(xml.attributes().value(QStringLiteral("kateversion")), hlFilename, hlName);
707 
708  // scan for broken regex or keywords with spaces
709  while (!xml.atEnd()) {
710  xml.readNext();
711  if (!xml.isStartElement()) {
712  continue;
713  }
714 
715  // search for used/existing contexts if applicable
716  contextChecker.processElement(hlFilename, hlName, xml);
717 
718  // search for existing keyword includes
719  keywordIncludeChecker.processElement(hlFilename, hlName, xml);
720 
721  // search for used/existing attributes if applicable
722  attributeChecker.processElement(xml);
723 
724  // search for used/existing keyword lists if applicable
725  keywordChecker.processElement(xml);
726 
727  // scan for bad regex
728  if (!checkRegularExpression(hlFilename, xml)) {
729  anyError = 7;
730  continue;
731  }
732 
733  // scan for bogus <item> lala </item> spaces
734  if (!checkItemsTrimmed(hlFilename, xml)) {
735  anyError = 8;
736  continue;
737  }
738 
739  // check single chars in DetectChar and Detect2Chars
740  if (!checkSingleChars(hlFilename, xml)) {
741  anyError = 8;
742  continue;
743  }
744 
745  // scan for lookAhead="true" with context="#stay"
746  if (!checkLookAhead(hlFilename, xml)) {
747  anyError = 7;
748  continue;
749  }
750  }
751 
752  if (!attributeChecker.check()) {
753  anyError = 7;
754  }
755 
756  if (!keywordChecker.check()) {
757  anyError = 7;
758  }
759  }
760 
761  if (!contextChecker.check())
762  anyError = 7;
763 
764  if (!keywordIncludeChecker.check())
765  anyError = 7;
766 
767  // bail out if any problem was seen
768  if (anyError)
769  return anyError;
770 
771  // create outfile, after all has worked!
772  QFile outFile(app.arguments().at(1));
773  if (!outFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
774  return 9;
775 
776  // write out json
777  outFile.write(QCborValue::fromVariant(QVariant(hls)).toCbor());
778 
779  // be done
780  return 0;
781 }
Keywords
void clear()
bool atEnd() const const
int toInt(bool *ok, int base) const const
bool readNextStartElement()
QString name(const QVariant &location)
QString toString() const const
int size() const const
int indexOf(const QString &str, int from, Qt::CaseSensitivity cs) const const
QStringRef value(const QString &namespaceUri, const QString &name) const const
QString & remove(int position, int n)
int size() const const
void append(const T &value)
QString & insert(int position, QChar ch)
QString readElementText(QXmlStreamReader::ReadElementTextBehaviour behaviour)
QString fileName() const const
bool isEmpty() const const
bool isEmpty() const const
QString trimmed() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QCborValue fromVariant(const QVariant &variant)
QStringRef left(int n) const const
QXmlStreamReader::TokenType readNext()
QHash::iterator find(const Key &key)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
SkipEmptyParts
bool hasAttribute(const QString &qualifiedName) const const
bool isStartElement() const const
char * toString(const T &value)
void insert(int i, const T &value)
bool load(const QUrl &source)
Language
QStringRef mid(int position, int n) const const
qint64 write(const char *data, qint64 maxSize)
QXmlStreamAttributes attributes() const const
qint64 lineNumber() const const
QHash::iterator end()
QStringRef name() const const
QUrl fromLocalFile(const QString &localFile)
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
KDB_EXPORT KDbVersionInfo version()
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Tue Aug 4 2020 22:58:14 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.