KCoreAddons

desktopfileparser.cpp
1 /*
2  SPDX-FileCopyrightText: 2013-2014 Sebastian K├╝gler <[email protected]>
3  SPDX-FileCopyrightText: 2014 Alex Richardson <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7 
8 
9 #include "desktopfileparser_p.h"
10 
11 #include <QCache>
12 #include <QDir>
13 #include <QFile>
14 #include <QJsonArray>
15 #include <QJsonObject>
16 #include <QMutex>
17 #include <QStandardPaths>
18 #include <QRegularExpression>
19 #include <QRegularExpressionMatch>
20 
21 // in the desktoptojson binary enable debug messages by default, in the library only warning messages
22 #ifdef BUILDING_DESKTOPTOJSON_TOOL
23 Q_LOGGING_CATEGORY(DESKTOPPARSER, "kf5.kcoreaddons.desktopparser", QtDebugMsg)
24 #else
25 Q_LOGGING_CATEGORY(DESKTOPPARSER, "kf5.kcoreaddons.desktopparser", QtWarningMsg)
26 #endif
27 
28 
29 #ifdef BUILDING_DESKTOPTOJSON_TOOL
30 // use if not else to prevent wrong scoping
31 #define DESKTOPTOJSON_VERBOSE_DEBUG if (!DesktopFileParser::s_verbose) {} else qCDebug(DESKTOPPARSER)
32 #define DESKTOPTOJSON_VERBOSE_WARNING if (!DesktopFileParser::s_verbose) {} else qCWarning(DESKTOPPARSER)
33 #else
34 #define DESKTOPTOJSON_VERBOSE_DEBUG QT_NO_QDEBUG_MACRO()
35 #define DESKTOPTOJSON_VERBOSE_WARNING QT_NO_QDEBUG_MACRO()
36 #endif
37 
38 
39 using namespace DesktopFileParser;
40 
41 // This code was taken from KConfigGroupPrivate::deserializeList
42 QStringList DesktopFileParser::deserializeList(const QString &data, char separator)
43 {
44  if (data.isEmpty()) {
45  return QStringList();
46  }
47  if (data == QLatin1String("\\0")) {
48  return QStringList(QString());
49  }
50  QStringList value;
51  QString val;
52  val.reserve(data.size());
53  bool quoted = false;
54  for (int p = 0; p < data.length(); p++) {
55  if (quoted) {
56  val += data[p];
57  quoted = false;
58  } else if (data[p].unicode() == '\\') {
59  quoted = true;
60  } else if (data[p].unicode() == separator) {
61  value.append(val);
62  if (p == data.length() - 1) {
63  // don't add an empty entry to the end if the last character is a separator
64  return value;
65  }
66  val.clear();
67  val.reserve(data.size() - p);
68  } else {
69  val += data[p];
70  }
71  }
72  value.append(val);
73  return value;
74 }
75 
76 QByteArray DesktopFileParser::escapeValue(const QByteArray &input)
77 {
78  const int start = input.indexOf('\\');
79  if (start < 0) {
80  return input;
81  }
82 
83  // we could do this in place, but this code is simpler
84  // this tool is probably only transitional, so no need to optimize
85  QByteArray result;
86  result.reserve(input.size());
87  result.append(input.data(), start);
88  for (int i = start; i < input.length(); ++i) {
89  if (input[i] != '\\') {
90  result.append(input[i]);
91  } else {
92  if (i + 1 >= input.length()) {
93  // just append the backslash if we are at end of line
94  result.append(input[i]);
95  break;
96  }
97  i++; // consume next character
98  char nextChar = input[i];
99  switch (nextChar) {
100  case 's':
101  result.append(' ');
102  break;
103  case 'n':
104  result.append('\n');
105  break;
106  case 't':
107  result.append('\t');
108  break;
109  case 'r':
110  result.append('\r');
111  break;
112  case '\\':
113  result.append('\\');
114  break;
115  default:
116  result.append('\\');
117  result.append(nextChar); // just ignore the escape sequence
118  }
119  }
120  }
121  return result;
122 }
123 
124 struct CustomPropertyDefinition {
125  // default ctor needed for QVector
126  CustomPropertyDefinition() : type(QVariant::String) {}
127  CustomPropertyDefinition(const QByteArray &key, QVariant::Type type)
128  : key(key) , type(type) {}
129  QJsonValue fromString(const QString &str) const
130  {
131  switch (type) {
132  case QVariant::String:
133  return str;
134  case QVariant::StringList:
136  case QVariant::Int: {
137  bool ok = false;
138  int result = str.toInt(&ok);
139  if (!ok) {
140  qCWarning(DESKTOPPARSER) << "Invalid integer value for key" << key << "-" << str;
141  return QJsonValue();
142  }
143  return QJsonValue(result);
144  }
145  case QVariant::Double: {
146  bool ok = false;
147  double result = str.toDouble(&ok);
148  if (!ok) {
149  qCWarning(DESKTOPPARSER) << "Invalid double value for key" << key << "-" << str;
150  return QJsonValue();
151  }
152  return QJsonValue(result);
153  }
154  case QVariant::Bool: {
155  bool result = str.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0;
156  if (!result && str.compare(QLatin1String("false"), Qt::CaseInsensitive) != 0) {
157  qCWarning(DESKTOPPARSER) << "Invalid boolean value for key" << key << "-" << str;
158  return QJsonValue();
159  }
160  return QJsonValue(result);
161  }
162  default:
163  // This was checked when parsing the file, no other QVariant::Type values are possible
164  Q_UNREACHABLE();
165  }
166  }
167  QByteArray key;
168  QVariant::Type type;
169 };
170 
171 namespace {
172 
173 bool readUntilDesktopEntryGroup(QFile &file, const QString &path, int &lineNr)
174 {
175  if (!file.open(QFile::ReadOnly)) {
176  qCWarning(DESKTOPPARSER) << "Error: Failed to open " << path;
177  return false;
178  }
179  // we only convert data inside the [Desktop Entry] group
180  while (!file.atEnd()) {
181  const QByteArray line = file.readLine().trimmed();
182  lineNr++;
183  if (line == "[Desktop Entry]") {
184  return true;
185  }
186  }
187  qCWarning(DESKTOPPARSER) << "Error: Could not find [Desktop Entry] group in " << path;
188  return false;
189 }
190 
191 
192 QByteArray readTypeEntryForCurrentGroup(QFile &df, QByteArray *nextGroup, QByteArray *pName)
193 {
194  QByteArray group = *nextGroup;
195  QByteArray type;
196  if (group.isEmpty()) {
197  qCWarning(DESKTOPPARSER, "Read empty .desktop file group name! Invalid file?");
198  }
199  while (!df.atEnd()) {
200  QByteArray line = df.readLine().trimmed();
201  // skip empty lines and comments
202  if (line.isEmpty() || line.startsWith('#')) {
203  continue;
204  }
205  if (line.startsWith('[')) {
206  if (!line.endsWith(']')) {
207  qCWarning(DESKTOPPARSER) << "Illegal .desktop group definition (does not end with ']'):" << line;
208  }
209  QByteArray name = line.mid(1, line.lastIndexOf(']') - 1).trimmed();
210  // we have reached the next group -> return current group and Type= value
211  *nextGroup = name;
212  break;
213  }
214 
215  const static QRegularExpression typeEntryRegex(
216  QStringLiteral("^Type\\s*=\\s*(.*)$"));
217  const auto match = typeEntryRegex.match(QString::fromUtf8(line));
218  if (match.hasMatch()) {
219  type = match.captured(1).toUtf8();
220  } else if (pName) {
221  const static QRegularExpression nameEntryRegex(
222  QStringLiteral("^X-KDE-ServiceType\\s*=\\s*(.*)$"));
223  const auto nameMatch = nameEntryRegex.match(QString::fromUtf8(line));
224  if (nameMatch.hasMatch()) {
225  *pName = nameMatch.captured(1).toUtf8();
226  }
227  }
228  }
229  return type;
230 }
231 
232 bool tokenizeKeyValue(QFile &df, const QString &src, QByteArray &key, QString &value, int &lineNr)
233 {
234  const QByteArray line = df.readLine().trimmed();
235  lineNr++;
236  if (line.isEmpty()) {
237  DESKTOPTOJSON_VERBOSE_DEBUG << "Line " << lineNr << ": empty";
238  return true;
239  }
240  if (line.startsWith('#')) {
241  DESKTOPTOJSON_VERBOSE_DEBUG << "Line " << lineNr << ": comment";
242  return true; // skip comments
243  }
244  if (line.startsWith('[')) {
245  // start of new group -> doesn't interest us anymore
246  DESKTOPTOJSON_VERBOSE_DEBUG << "Line " << lineNr << ": start of new group " << line;
247  return false;
248  }
249  // must have form key=value now
250  const int equalsIndex = line.indexOf('=');
251  if (equalsIndex == -1) {
252  qCWarning(DESKTOPPARSER).nospace() << qPrintable(src) << ':' << lineNr << ": Line is neither comment nor group "
253  "and doesn't contain an '=' character: \"" << line.constData() << '\"';
254  return true;
255  }
256  // trim key and value to remove spaces around the '=' char
257  key = line.mid(0, equalsIndex).trimmed();
258  if (key.isEmpty()) {
259  qCWarning(DESKTOPPARSER).nospace() << qPrintable(src) << ':' << lineNr << ": Key name is missing: \"" << line.constData() << '\"';
260  return true;
261  }
262 
263  const QByteArray valueRaw = line.mid(equalsIndex + 1).trimmed();
264  const QByteArray valueEscaped = escapeValue(valueRaw);
265  value = QString::fromUtf8(valueEscaped);
266 
267 #ifdef BUILDING_DESKTOPTOJSON_TOOL
268  DESKTOPTOJSON_VERBOSE_DEBUG.nospace() << "Line " << lineNr << ": key=" << key << ", value=" << value;
269  if (valueEscaped != valueRaw) {
270  DESKTOPTOJSON_VERBOSE_DEBUG << "Line " << lineNr << " contained escape sequences";
271  }
272 #endif
273 
274  return true;
275 }
276 
277 static QString locateRelativeServiceType(const QString &relPath)
278 {
279  return QStandardPaths::locate(QStandardPaths::GenericDataLocation,
280  QStringLiteral("kservicetypes5/") + relPath);
281 }
282 
283 static ServiceTypeDefinition* parseServiceTypesFile(const QString &inputPath)
284 {
285  int lineNr = 0;
286  QString path = inputPath;
287  if (QDir::isRelativePath(path)) {
288  path = locateRelativeServiceType(path);
289  QString rcPath;
290  if (path.isEmpty()) {
291  rcPath = QLatin1String(":/kservicetypes5/") + inputPath;
292  if (QFileInfo::exists(rcPath)) {
293  path = rcPath;
294  }
295  }
296  if (path.isEmpty()) {
297  qCWarning(DESKTOPPARSER).nospace() << "Could not locate service type file kservicetypes5/" << qPrintable(inputPath) << ", tried " << QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation) << " and " << rcPath;
298  return nullptr;
299  }
300  }
301  QFile df(path);
302  if (!df.exists()) {
303  qCCritical(DESKTOPPARSER) << "Service type file" << path << "does not exist";
304  return nullptr;
305  }
306  if (!readUntilDesktopEntryGroup(df, path, lineNr)) {
307  return nullptr;
308  }
309  ServiceTypeDefinition result;
310  // TODO: passing nextGroup by pointer is inefficient as it will make deep copies every time
311  // Not exactly performance critical code though so low priority
312  QByteArray nextGroup = "Desktop Entry";
313  // Type must be ServiceType now
314  QByteArray typeStr = readTypeEntryForCurrentGroup(df, &nextGroup, &result.m_serviceTypeName);
315  if (typeStr != QByteArrayLiteral("ServiceType")) {
316  qCWarning(DESKTOPPARSER) << path << "is not a valid service type: Type entry should be 'ServiceType', got"
317  << typeStr << "instead.";
318  return nullptr;
319  }
320  while (!df.atEnd()) {
321  QByteArray currentGroup = nextGroup;
322  typeStr = readTypeEntryForCurrentGroup(df, &nextGroup, nullptr);
323  if (!currentGroup.startsWith(QByteArrayLiteral("PropertyDef::"))) {
324  qCWarning(DESKTOPPARSER) << "Skipping invalid group" << currentGroup << "in service type" << path;
325  continue;
326  }
327  if (typeStr.isEmpty()) {
328  qCWarning(DESKTOPPARSER) << "Could not find Type= key in group" << currentGroup;
329  continue;
330  }
331  QByteArray propertyName = currentGroup.mid(qstrlen("PropertyDef::"));
332  QVariant::Type type = QVariant::nameToType(typeStr.constData());
333  switch (type) {
334  case QVariant::String:
335  case QVariant::StringList:
336  case QVariant::Int:
337  case QVariant::Double:
338  case QVariant::Bool:
339  qCDebug(DESKTOPPARSER) << "Found property definition" << propertyName << "with type" << typeStr;
340  result.m_propertyDefs.push_back(CustomPropertyDefinition(propertyName, type));
341  break;
342  case QVariant::Invalid:
343  qCWarning(DESKTOPPARSER) << "Property type" << typeStr << "is not a known QVariant type."
344  " Found while parsing property definition for" << propertyName << "in" << path;
345  break;
346  default:
347  qCWarning(DESKTOPPARSER) << "Unsupported property type" << typeStr << "for property" << propertyName
348  << "found in" << path << "\nOnly QString, QStringList, int, double and bool are supported.";
349  }
350  }
351  return new ServiceTypeDefinition(result);
352 }
353 
354 // a lazy map of service type definitions
355 typedef QCache<QString /*path*/, ServiceTypeDefinition> ServiceTypesHash;
356 Q_GLOBAL_STATIC(ServiceTypesHash, s_serviceTypes)
357 // access must be guarded by serviceTypesMutex as this code could be executed by multiple threads
358 QBasicMutex s_serviceTypesMutex;
359 } // end of anonymous namespace
360 
361 
362 ServiceTypeDefinitions ServiceTypeDefinitions::fromFiles(const QStringList &paths)
363 {
364  ServiceTypeDefinitions ret;
365  ret.m_definitions.reserve(paths.size());
366  // as we might modify the cache we need to acquire a mutex here
367  for (const QString &serviceTypePath : paths) {
368  bool added = ret.addFile(serviceTypePath);
369  if (!added) {
370 #ifdef BUILDING_DESKTOPTOJSON_TOOL
371  exit(1); // this is a fatal error when using kcoreaddons_desktop_to_json()
372 #endif
373  }
374  }
375  return ret;
376 }
377 
378 bool ServiceTypeDefinitions::addFile(const QString& path)
379 {
380  QMutexLocker lock(&s_serviceTypesMutex);
381  ServiceTypeDefinition* def = s_serviceTypes->object(path);
382 
383  if (def) {
384  // in cache but we still must make our own copy
385  m_definitions << *def;
386  } else {
387  // not found in cache -> we need to parse the file
388  qCDebug(DESKTOPPARSER) << "About to parse service type file" << path;
389  def = parseServiceTypesFile(path);
390  if (!def) {
391  return false;
392  }
393 
394  m_definitions << *def; // This must *precede* insert call, insert might delete
395  s_serviceTypes->insert(path, def);
396  }
397  return true;
398 }
399 
400 QJsonValue ServiceTypeDefinitions::parseValue(const QByteArray &key, const QString &value) const
401 {
402  // check whether the key has a special type associated with it
403  for (const auto &def : m_definitions) {
404  for (const CustomPropertyDefinition &propertyDef : def.m_propertyDefs) {
405  if (propertyDef.key == key) {
406  return propertyDef.fromString(value);
407  }
408  }
409  }
410  qCDebug(DESKTOPPARSER) << "Unknown property type for key" << key << "-> falling back to string";
411  return QJsonValue(value);
412 }
413 
414 bool ServiceTypeDefinitions::hasServiceType(const QByteArray &serviceTypeName) const
415 {
416  const auto it = std::find_if(m_definitions.begin(), m_definitions.end(), [&serviceTypeName](const ServiceTypeDefinition &def) {
417  return def.m_serviceTypeName == serviceTypeName;
418  });
419  return it != m_definitions.end();
420 }
421 
422 void DesktopFileParser::convertToJson(const QByteArray &key, ServiceTypeDefinitions &serviceTypes, const QString &value,
423  QJsonObject &json, QJsonObject &kplugin, int lineNr)
424 {
425  /* The following keys are recognized (and added to a "KPlugin" object):
426 
427  Icon=mypluginicon
428  Type=Service
429  ServiceTypes=KPluginInfo
430  MimeType=text/plain;image/png
431 
432  Name=User Visible Name (translatable)
433  Comment=Description of what the plugin does (translatable)
434 
435  X-KDE-PluginInfo-Author=Author's Name
436  [email protected]
437  X-KDE-PluginInfo-Name=internalname
438  X-KDE-PluginInfo-Version=1.1
439  X-KDE-PluginInfo-Website=http://www.plugin.org/
440  X-KDE-PluginInfo-Category=playlist
441  X-KDE-PluginInfo-Depends=plugin1,plugin3
442  X-KDE-PluginInfo-License=GPL
443  X-KDE-PluginInfo-EnabledByDefault=true
444  X-KDE-FormFactors=desktop
445  */
446  if (key == QByteArrayLiteral("Icon")) {
447  kplugin[QStringLiteral("Icon")] = value;
448  } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Name")) {
449  kplugin[QStringLiteral("Id")] = value;
450  } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Category")) {
451  kplugin[QStringLiteral("Category")] = value;
452  } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-License")) {
453  kplugin[QStringLiteral("License")] = value;
454  } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Version")) {
455  kplugin[QStringLiteral("Version")] = value;
456  } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Website")) {
457  kplugin[QStringLiteral("Website")] = value;
458  } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Depends")) {
459  kplugin[QStringLiteral("Dependencies")] = QJsonArray::fromStringList(deserializeList(value));
460  } else if (key == QByteArrayLiteral("X-KDE-ServiceTypes") || key == QByteArrayLiteral("ServiceTypes")) {
461  //NOTE: "X-KDE-ServiceTypes" and "ServiceTypes" were already managed in the first parse step, so this second one is almost a noop
462  const auto services = deserializeList(value);
463  kplugin[QStringLiteral("ServiceTypes")] = QJsonArray::fromStringList(services);
464  } else if (key == QByteArrayLiteral("MimeType")) {
465  // MimeType is a XDG string list and not a KConfig list so we need to use ';' as the separator
466  kplugin[QStringLiteral("MimeTypes")] = QJsonArray::fromStringList(deserializeList(value, ';'));
467  // make sure that applications using kcoreaddons_desktop_to_json() that depend on reading
468  // the MimeType property still work (see https://git.reviewboard.kde.org/r/125527/)
469  json[QStringLiteral("MimeType")] = value; // TODO KF6 remove this compatibility code
470  } else if (key == QByteArrayLiteral("X-KDE-FormFactors")) {
471  kplugin[QStringLiteral("FormFactors")] = QJsonArray::fromStringList(deserializeList(value));
472  } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-EnabledByDefault")) {
473  bool boolValue = false;
474  // should only be lower case, but be tolerant here
475  if (value.toLower() == QLatin1String("true")) {
476  boolValue = true;
477  } else {
478  if (value.toLower() != QLatin1String("false")) {
479  qCWarning(DESKTOPPARSER).nospace() << "Expected boolean value for key \"" << key
480  << "\" at line " << lineNr << "but got \"" << value << "\" instead.";
481  }
482  }
483  kplugin[QStringLiteral("EnabledByDefault")] = boolValue;
484  } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Author")) {
485  QJsonObject authorsObject = kplugin.value(QStringLiteral("Authors")).toArray().at(0).toObject();
486  // if the authors object doesn't exist yet this will create it
487  authorsObject[QStringLiteral("Name")] = value;
488  QJsonArray array;
489  array.append(authorsObject);
490  kplugin[QStringLiteral("Authors")] = array;
491  } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Email")) {
492  QJsonObject authorsObject = kplugin.value(QStringLiteral("Authors")).toArray().at(0).toObject();
493  // if the authors object doesn't exist yet this will create it
494  authorsObject[QStringLiteral("Email")] = value;
495  QJsonArray array;
496  array.append(authorsObject);
497  kplugin[QStringLiteral("Authors")] = array;
498  } else if (key == QByteArrayLiteral("Name") || key.startsWith(QByteArrayLiteral("Name["))) {
499  // TODO: also handle GenericName? does that make any sense, or is X-KDE-PluginInfo-Category enough?
500  kplugin[QString::fromUtf8(key)] = value;
501  } else if (key == QByteArrayLiteral("Comment")) {
502  kplugin[QStringLiteral("Description")] = value;
503  } else if (key.startsWith(QByteArrayLiteral("Comment["))) {
504  kplugin[QStringLiteral("Description") + QString::fromUtf8(key.mid(qstrlen("Comment")))] = value;
505  } else if (key == QByteArrayLiteral("InitialPreference")) {
506  kplugin[QStringLiteral("InitialPreference")] = value.toInt();
507  } else if (key == QByteArrayLiteral("Hidden")) {
508  DESKTOPTOJSON_VERBOSE_WARNING << "Hidden= key found in desktop file, this makes no sense"
509  " with metadata inside the plugin.";
510  kplugin[QString::fromUtf8(key)] = (value.toLower() == QLatin1String("true"));
511  } else if (key == QByteArrayLiteral("Exec") || key == QByteArrayLiteral("Type")
512  || key == QByteArrayLiteral("X-KDE-Library") || key == QByteArrayLiteral("Encoding")) {
513  // Exec= doesn't make sense here, however some .desktop files (like e.g. in kdevelop) have a dummy value here
514  // also the Type=Service entry is no longer needed
515  // X-KDE-Library is also not needed since we already have the library to read this metadata
516  // Encoding= is also not converted as we always use utf-8 for reading
517  DESKTOPTOJSON_VERBOSE_DEBUG << "Not converting key " << key << "=" << value;
518  } else {
519  // check service type definitions or fall back to QString
520  json[QString::fromUtf8(key)] = serviceTypes.parseValue(key, value);
521  }
522 }
523 
524 bool DesktopFileParser::convert(const QString &src, const QStringList &serviceTypes, QJsonObject &json, QString *libraryPath)
525 {
526  QFile df(src);
527  int lineNr = 0;
528  ServiceTypeDefinitions serviceTypeDef = ServiceTypeDefinitions::fromFiles(serviceTypes);
529  readUntilDesktopEntryGroup(df, src, lineNr);
530  DESKTOPTOJSON_VERBOSE_DEBUG << "Found [Desktop Entry] group in line" << lineNr;
531  auto startPos = df.pos();
532 
533  //parse it a first time to know servicetype
534  while (!df.atEnd()) {
535  QByteArray key;
536  QString value;
537  if (!tokenizeKeyValue(df, src, key, value, lineNr)) {
538  break;
539  }
540  // some .desktop files still use the legacy ServiceTypes= key
541  if (key == QByteArrayLiteral("X-KDE-ServiceTypes") || key == QByteArrayLiteral("ServiceTypes")) {
542  const QString dotDesktop = QStringLiteral(".desktop");
543  const QChar slashChar(QLatin1Char('/'));
544  const auto serviceList = deserializeList(value);
545 
546  for (const auto &service : serviceList) {
547  if (!serviceTypeDef.hasServiceType(service.toLatin1())) {
548  // Make up the filename from the service type name. This assumes consistent naming...
549  QString absFileName = locateRelativeServiceType(
550  service.toLower().replace(slashChar, QLatin1Char('-')) + dotDesktop);
551  if (absFileName.isEmpty()) {
552  absFileName = locateRelativeServiceType(
553  service.toLower().remove(slashChar) + dotDesktop);
554  }
555  if (absFileName.isEmpty()) {
556  qCWarning(DESKTOPPARSER) << "Unable to find service type for service" << service << "listed in" << src;
557  } else {
558  serviceTypeDef.addFile(absFileName);
559  }
560  }
561  }
562  break;
563  }
564  }
565  lineNr=0;
566  df.seek(startPos);
567 
568  QJsonObject kplugin; // the "KPlugin" key of the metadata
569  //QJsonObject json;
570  while (!df.atEnd()) {
571  QByteArray key;
572  QString value;
573  if (!tokenizeKeyValue(df, src, key, value, lineNr)) {
574  break;
575  } else if (key.isEmpty()) {
576  continue;
577  }
578 #ifdef BUILDING_DESKTOPTOJSON_TOOL
579  if (s_compatibilityMode) {
580  convertToCompatibilityJson(QString::fromUtf8(key), value, json, lineNr);
581  } else {
582  convertToJson(key, serviceTypeDef, value, json, kplugin, lineNr);
583  }
584 #else
585  convertToJson(key, serviceTypeDef, value, json, kplugin, lineNr);
586 #endif
587  if (libraryPath && key == QByteArrayLiteral("X-KDE-Library")) {
588  *libraryPath = value;
589  }
590  }
591  json[QStringLiteral("KPlugin")] = kplugin;
592  return true;
593 }
594 
KDB_EXPORT QStringList deserializeList(const QString &data)
QByteArray trimmed() const const
void reserve(int size)
int lastIndexOf(char ch, int from) const const
int size() const const
bool isEmpty() const const
bool startsWith(const QByteArray &ba) const const
int length() const const
bool exists() const const
double toDouble(bool *ok) const const
QStringList standardLocations(QStandardPaths::StandardLocation type)
QString name(StandardShortcut id)
void clear()
int indexOf(char ch, int from) const const
void append(const T &value)
QString fromUtf8(const char *str, int size)
QJsonValue at(int i) const const
QJsonObject toObject() const const
QVariant::Type nameToType(const char *name)
int toInt(bool *ok, int base) const const
QJsonArray toArray() const const
bool isEmpty() const const
const char * constData() const const
QByteArray mid(int pos, int len) const const
virtual bool open(QIODevice::OpenMode mode) override
void append(const QJsonValue &value)
QByteArray & append(char ch)
void push_back(char ch)
QString toLower() const const
bool exists() const const
bool isRelativePath(const QString &path)
virtual bool atEnd() const const override
virtual bool seek(qint64 pos) override
int length() const const
void reserve(int size)
char * data()
QJsonValue value(const QString &key) const const
virtual qint64 pos() const const override
int size() const const
int compare(const QString &other, Qt::CaseSensitivity cs) const const
bool endsWith(const QByteArray &ba) const const
QString locate(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
qint64 readLine(char *data, qint64 maxSize)
QJsonArray fromStringList(const QStringList &list)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sun May 31 2020 23:11:13 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.