KConfig

KConfigXmlParser.cpp
1 /*
2  This file is part of the KDE libraries
3 
4  SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
5  SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
6  SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
7  SPDX-FileCopyrightText: 2006 MichaĆ«l Larouche <michael.larouche@kdemail.net>
8  SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org>
9  SPDX-FileCopyrightText: 2020 Tomaz Cananbrava <tcanabrava@kde.org>
10 
11  SPDX-License-Identifier: LGPL-2.0-or-later
12 */
13 
14 #include "KConfigXmlParser.h"
15 
16 #include <QDebug>
17 #include <QDomAttr>
18 #include <QDomElement>
19 #include <QDomNode>
20 #include <QFile>
21 #include <QList>
22 #include <QStringList>
23 #include <QTextStream>
24 #include <iostream>
25 // TODO: Move preprocessDefault to Header / CPP implementation.
26 // it makes no sense for a parser to process those values and generate code.
27 
28 static void preProcessDefault(QString &defaultValue,
29  const QString &name,
30  const QString &type,
31  const CfgEntry::Choices &cfgChoices,
32  QString &code,
33  const KConfigParameters &cfg)
34 {
35  if (type == QLatin1String("String") && !defaultValue.isEmpty()) {
36  defaultValue = literalString(defaultValue);
37 
38  } else if (type == QLatin1String("Path") && !defaultValue.isEmpty()) {
39  defaultValue = literalString(defaultValue);
40  } else if (type == QLatin1String("Url") && !defaultValue.isEmpty()) {
41  // Use fromUserInput in order to support absolute paths and absolute urls, like KDE4's KUrl(QString) did.
42  defaultValue = QLatin1String("QUrl::fromUserInput( %1)").arg(literalString(defaultValue));
43  } else if ((type == QLatin1String("UrlList") || type == QLatin1String("StringList") || type == QLatin1String("PathList")) && !defaultValue.isEmpty()) {
45  if (!code.isEmpty()) {
46  cpp << '\n';
47  }
48 
49  if (type == QLatin1String("UrlList")) {
50  cpp << " QList<QUrl> default" << name << ";\n";
51  } else {
52  cpp << " QStringList default" << name << ";\n";
53  }
54  const QStringList defaults = defaultValue.split(QLatin1Char(','));
55  for (const auto &val : defaults) {
56  cpp << " default" << name << ".append( ";
57  if (type == QLatin1String("UrlList")) {
58  cpp << "QUrl::fromUserInput(";
59  }
60  cpp << "QString::fromUtf8( \"" << val << "\" ) ";
61  if (type == QLatin1String("UrlList")) {
62  cpp << ") ";
63  }
64  cpp << ");\n";
65  }
66  defaultValue = QLatin1String("default") + name;
67 
68  } else if (type == QLatin1String("Color") && !defaultValue.isEmpty()) {
69  static const QRegularExpression colorRe(QRegularExpression::anchoredPattern(QStringLiteral("\\d+,\\s*\\d+,\\s*\\d+(,\\s*\\d+)?")));
70 
71  if (colorRe.match(defaultValue).hasMatch()) {
72  defaultValue = QLatin1String("QColor( %1 )").arg(defaultValue);
73  } else {
74  defaultValue = QLatin1String("QColor( \"%1\" )").arg(defaultValue);
75  }
76 
77  } else if (type == QLatin1String("Enum")) {
78  for (const auto &choice : cfgChoices.choices) {
79  if (choice.name == defaultValue) {
80  if (cfg.globalEnums && cfgChoices.name().isEmpty()) {
81  defaultValue.prepend(cfgChoices.prefix);
82  } else {
83  defaultValue.prepend(enumTypeQualifier(name, cfgChoices) + cfgChoices.prefix);
84  }
85  break;
86  }
87  }
88 
89  } else if (type == QLatin1String("IntList")) {
91  if (!code.isEmpty()) {
92  cpp << '\n';
93  }
94 
95  cpp << " QList<int> default" << name << ";\n";
96  if (!defaultValue.isEmpty()) {
97  const QStringList defaults = defaultValue.split(QLatin1Char(','));
98  for (const auto &defaultVal : defaults) {
99  cpp << " default" << name << ".append( " << defaultVal << " );\n";
100  }
101  }
102  defaultValue = QLatin1String("default") + name;
103  }
104 }
105 
106 static QString dumpNode(const QDomNode &node)
107 {
108  QString msg;
110  node.save(s, 0);
111 
112  msg = msg.simplified();
113  if (msg.length() > 40) {
114  return msg.left(37) + QLatin1String("...");
115  }
116  return msg;
117 }
118 
119 void KConfigXmlParser::readParameterFromEntry(CfgEntry &readEntry, const QDomElement &e)
120 {
121  readEntry.param = e.attribute(QStringLiteral("name"));
122  readEntry.paramType = e.attribute(QStringLiteral("type"));
123 
124  if (readEntry.param.isEmpty()) {
125  std::cerr << "Parameter must have a name: " << qPrintable(dumpNode(e)) << std::endl;
126  exit(1);
127  }
128 
129  if (readEntry.paramType.isEmpty()) {
130  std::cerr << "Parameter must have a type: " << qPrintable(dumpNode(e)) << std::endl;
131  exit(1);
132  }
133 
134  if ((readEntry.paramType == QLatin1String("Int")) || (readEntry.paramType == QLatin1String("UInt"))) {
135  bool ok;
136  readEntry.paramMax = e.attribute(QStringLiteral("max")).toInt(&ok);
137  if (!ok) {
138  std::cerr << "Integer parameter must have a maximum (e.g. max=\"0\"): " << qPrintable(dumpNode(e)) << std::endl;
139  exit(1);
140  }
141  } else if (readEntry.paramType == QLatin1String("Enum")) {
142  for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) {
143  if (e2.tagName() == QLatin1String("values")) {
144  for (QDomElement e3 = e2.firstChildElement(); !e3.isNull(); e3 = e3.nextSiblingElement()) {
145  if (e3.tagName() == QLatin1String("value")) {
146  readEntry.paramValues.append(e3.text());
147  }
148  }
149  break;
150  }
151  }
152  if (readEntry.paramValues.isEmpty()) {
153  std::cerr << "No values specified for parameter '" << qPrintable(readEntry.param) << "'." << std::endl;
154  exit(1);
155  }
156  readEntry.paramMax = readEntry.paramValues.count() - 1;
157  } else {
158  std::cerr << "Parameter '" << qPrintable(readEntry.param) << "' has type " << qPrintable(readEntry.paramType)
159  << " but must be of type int, uint or Enum." << std::endl;
160  exit(1);
161  }
162 }
163 
164 bool KConfigXmlParser::hasDefaultCode(CfgEntry &readEntry, const QDomElement &element)
165 {
166  Q_UNUSED(readEntry)
167 
168  for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
169  if (e.attribute(QStringLiteral("param")).isEmpty()) {
170  if (e.attribute(QStringLiteral("code")) == QLatin1String("true")) {
171  return true;
172  }
173  }
174  }
175  return false;
176 }
177 
178 void KConfigXmlParser::readChoicesFromEntry(CfgEntry &readEntry, const QDomElement &e)
179 {
181  const auto choiceNameRegex = QRegularExpression(QStringLiteral("\\w+"));
182 
183  for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) {
184  if (e2.tagName() != QLatin1String("choice")) {
185  continue;
186  }
187  CfgEntry::Choice choice;
188  choice.name = e2.attribute(QStringLiteral("name"));
189  if (choice.name.isEmpty()) {
190  std::cerr << "Tag <choice> requires attribute 'name'." << std::endl;
191  } else if (!choiceNameRegex.match(choice.name).hasMatch()) {
192  std::cerr << "Tag <choice> attribute 'name' must be compatible with Enum naming. name was '" << qPrintable(choice.name)
193  << "'. You can use attribute 'value' to pass any string as the choice value." << std::endl;
194  }
195  choice.val = e2.attribute(QStringLiteral("value"));
196  for (QDomElement e3 = e2.firstChildElement(); !e3.isNull(); e3 = e3.nextSiblingElement()) {
197  if (e3.tagName() == QLatin1String("label")) {
198  choice.label = e3.text();
199  choice.context = e3.attribute(QStringLiteral("context"));
200  }
201  if (e3.tagName() == QLatin1String("tooltip")) {
202  choice.toolTip = e3.text();
203  choice.context = e3.attribute(QStringLiteral("context"));
204  }
205  if (e3.tagName() == QLatin1String("whatsthis")) {
206  choice.whatsThis = e3.text();
207  choice.context = e3.attribute(QStringLiteral("context"));
208  }
209  }
210  chlist.append(choice);
211  }
212 
213  QString name = e.attribute(QStringLiteral("name"));
214  QString prefix = e.attribute(QStringLiteral("prefix"));
215 
216  readEntry.choices = CfgEntry::Choices(chlist, name, prefix);
217 }
218 
219 void KConfigXmlParser::readGroupElements(CfgEntry &readEntry, const QDomElement &element)
220 {
221  for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
222  QString tag = e.tagName();
223  if (tag == QLatin1String("label")) {
224  readEntry.label = e.text();
225  readEntry.labelContext = e.attribute(QStringLiteral("context"));
226  } else if (tag == QLatin1String("tooltip")) {
227  readEntry.toolTip = e.text();
228  readEntry.toolTipContext = e.attribute(QStringLiteral("context"));
229  } else if (tag == QLatin1String("whatsthis")) {
230  readEntry.whatsThis = e.text();
231  readEntry.whatsThisContext = e.attribute(QStringLiteral("context"));
232  } else if (tag == QLatin1String("min")) {
233  readEntry.min = e.text();
234  } else if (tag == QLatin1String("max")) {
235  readEntry.max = e.text();
236  } else if (tag == QLatin1String("code")) {
237  readEntry.code = e.text();
238  } else if (tag == QLatin1String("parameter")) {
239  readParameterFromEntry(readEntry, e);
240  } else if (tag == QLatin1String("default")) {
241  if (e.attribute(QStringLiteral("param")).isEmpty()) {
242  readEntry.defaultValue = e.text();
243  }
244  } else if (tag == QLatin1String("choices")) {
245  readChoicesFromEntry(readEntry, e);
246  } else if (tag == QLatin1String("emit")) {
247  Signal signal;
248  signal.name = e.attribute(QStringLiteral("signal"));
249  readEntry.signalList.append(signal);
250  }
251  }
252 }
253 
254 void KConfigXmlParser::createChangedSignal(CfgEntry &readEntry)
255 {
256  if (cfg.generateProperties && (cfg.allMutators || cfg.mutators.contains(readEntry.name))) {
257  Signal s;
258  s.name = changeSignalName(readEntry.name);
259  s.modify = true;
260  readEntry.signalList.append(s);
261  }
262 }
263 
264 void KConfigXmlParser::validateNameAndKey(CfgEntry &readEntry, const QDomElement &element)
265 {
266  bool nameIsEmpty = readEntry.name.isEmpty();
267  if (nameIsEmpty && readEntry.key.isEmpty()) {
268  std::cerr << "Entry must have a name or a key: " << qPrintable(dumpNode(element)) << std::endl;
269  exit(1);
270  }
271 
272  if (readEntry.key.isEmpty()) {
273  readEntry.key = readEntry.name;
274  }
275 
276  if (nameIsEmpty) {
277  readEntry.name = readEntry.key;
278  readEntry.name.remove(QLatin1Char(' '));
279  } else if (readEntry.name.contains(QLatin1Char(' '))) {
280  std::cout << "Entry '" << qPrintable(readEntry.name) << "' contains spaces! <name> elements can not contain spaces!" << std::endl;
281  readEntry.name.remove(QLatin1Char(' '));
282  }
283 
284  if (readEntry.name.contains(QStringLiteral("$("))) {
285  if (readEntry.param.isEmpty()) {
286  std::cerr << "Name may not be parameterized: " << qPrintable(readEntry.name) << std::endl;
287  exit(1);
288  }
289  } else {
290  if (!readEntry.param.isEmpty()) {
291  std::cerr << "Name must contain '$(" << qPrintable(readEntry.param) << ")': " << qPrintable(readEntry.name) << std::endl;
292  exit(1);
293  }
294  }
295 }
296 
297 void KConfigXmlParser::readParamDefaultValues(CfgEntry &readEntry, const QDomElement &element)
298 {
299  if (readEntry.param.isEmpty()) {
300  return;
301  }
302  // Adjust name
303  readEntry.paramName = readEntry.name;
304 
305  readEntry.name.remove(QStringLiteral("$(") + readEntry.param + QLatin1Char(')'));
306  // Lookup defaults for indexed entries
307  for (int i = 0; i <= readEntry.paramMax; i++) {
308  readEntry.paramDefaultValues.append(QString());
309  }
310 
311  for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
312  QString tag = e.tagName();
313  if (tag != QLatin1String("default")) {
314  continue;
315  }
316  QString index = e.attribute(QStringLiteral("param"));
317  if (index.isEmpty()) {
318  continue;
319  }
320 
321  bool ok;
322  int i = index.toInt(&ok);
323  if (!ok) {
324  i = readEntry.paramValues.indexOf(index);
325  if (i == -1) {
326  std::cerr << "Index '" << qPrintable(index) << "' for default value is unknown." << std::endl;
327  exit(1);
328  }
329  }
330 
331  if ((i < 0) || (i > readEntry.paramMax)) {
332  std::cerr << "Index '" << i << "' for default value is out of range [0, " << readEntry.paramMax << "]." << std::endl;
333  exit(1);
334  }
335 
336  QString tmpDefaultValue = e.text();
337 
338  if (e.attribute(QStringLiteral("code")) != QLatin1String("true")) {
339  preProcessDefault(tmpDefaultValue, readEntry.name, readEntry.type, readEntry.choices, readEntry.code, cfg);
340  }
341 
342  readEntry.paramDefaultValues[i] = tmpDefaultValue;
343  }
344 }
345 
346 CfgEntry *KConfigXmlParser::parseEntry(const QString &group, const QString &parentGroup, const QDomElement &element)
347 {
348  CfgEntry readEntry;
349  readEntry.type = element.attribute(QStringLiteral("type"));
350  readEntry.name = element.attribute(QStringLiteral("name"));
351  readEntry.key = element.attribute(QStringLiteral("key"));
352  readEntry.hidden = element.attribute(QStringLiteral("hidden")) == QLatin1String("true");
353  ;
354  readEntry.group = group;
355  readEntry.parentGroup = parentGroup;
356 
357  const bool nameIsEmpty = readEntry.name.isEmpty();
358 
359  readGroupElements(readEntry, element);
360 
361  validateNameAndKey(readEntry, element);
362 
363  if (readEntry.label.isEmpty()) {
364  readEntry.label = readEntry.key;
365  }
366 
367  if (readEntry.type.isEmpty()) {
368  readEntry.type = QStringLiteral("String"); // XXX : implicit type might be bad
369  }
370 
371  readParamDefaultValues(readEntry, element);
372 
373  if (!mValidNameRegexp.match(readEntry.name).hasMatch()) {
374  if (nameIsEmpty) {
375  std::cerr << "The key '" << qPrintable(readEntry.key)
376  << "' can not be used as name for the entry because "
377  "it is not a valid name. You need to specify a valid name for this entry."
378  << std::endl;
379  } else {
380  std::cerr << "The name '" << qPrintable(readEntry.name) << "' is not a valid name for an entry." << std::endl;
381  }
382  exit(1);
383  }
384 
385  if (mAllNames.contains(readEntry.name)) {
386  if (nameIsEmpty) {
387  std::cerr << "The key '" << qPrintable(readEntry.key)
388  << "' can not be used as name for the entry because "
389  "it does not result in a unique name. You need to specify a unique name for this entry."
390  << std::endl;
391  } else {
392  std::cerr << "The name '" << qPrintable(readEntry.name) << "' is not unique." << std::endl;
393  }
394  exit(1);
395  }
396 
397  mAllNames.append(readEntry.name);
398 
399  if (!hasDefaultCode(readEntry, element)) {
400  // TODO: Move all the options to CfgEntry.
401  preProcessDefault(readEntry.defaultValue, readEntry.name, readEntry.type, readEntry.choices, readEntry.code, cfg);
402  }
403 
404  // TODO: Try to Just return the CfgEntry we populated instead of
405  // creating another one to fill the code.
406  CfgEntry *result = new CfgEntry();
407  result->group = readEntry.group;
408  result->parentGroup = readEntry.parentGroup;
409  result->type = readEntry.type;
410  result->key = readEntry.key;
411  result->name = readEntry.name;
412  result->labelContext = readEntry.labelContext;
413  result->label = readEntry.label;
414  result->toolTipContext = readEntry.toolTipContext;
415  result->toolTip = readEntry.toolTip;
416  result->whatsThisContext = readEntry.whatsThisContext;
417  result->whatsThis = readEntry.whatsThis;
418  result->code = readEntry.code;
419  result->defaultValue = readEntry.defaultValue;
420  result->choices = readEntry.choices;
421  result->signalList = readEntry.signalList;
422  result->hidden = readEntry.hidden;
423 
424  if (!readEntry.param.isEmpty()) {
425  result->param = readEntry.param;
426  result->paramName = readEntry.paramName;
427  result->paramType = readEntry.paramType;
428  result->paramValues = readEntry.paramValues;
429  result->paramDefaultValues = readEntry.paramDefaultValues;
430  result->paramMax = readEntry.paramMax;
431  }
432  result->min = readEntry.min;
433  result->max = readEntry.max;
434  createChangedSignal(*result);
435 
436  return result;
437 }
438 
439 // TODO: Change the name of the config variable.
440 KConfigXmlParser::KConfigXmlParser(const KConfigParameters &cfg, const QString &inputFileName)
441  : cfg(cfg)
442  , mInputFileName(inputFileName)
443 {
444  mValidNameRegexp.setPattern(QRegularExpression::anchoredPattern(QStringLiteral("[a-zA-Z_][a-zA-Z0-9_]*")));
445 }
446 
448 {
449  QFile input(mInputFileName);
450  if (!input.open(QIODevice::ReadOnly)) {
451  qFatal("Could not open input file: %s", qUtf8Printable(mInputFileName));
452  }
453  QDomDocument doc;
454  const QDomDocument::ParseResult parseResult = doc.setContent(&input);
455  if (!parseResult) {
456  std::cerr << "Unable to load document." << std::endl;
457  std::cerr << "Parse error in " << qPrintable(mInputFileName) << ", line " << parseResult.errorLine << ", col " << parseResult.errorColumn << ": "
458  << qPrintable(parseResult.errorMessage) << std::endl;
459  exit(1);
460  }
461 
462  QDomElement cfgElement = doc.documentElement();
463  if (cfgElement.isNull()) {
464  std::cerr << "No document in kcfg file" << std::endl;
465  exit(1);
466  }
467 
468  for (QDomElement element = cfgElement.firstChildElement(); !element.isNull(); element = element.nextSiblingElement()) {
469  QString tag = element.tagName();
470 
471  if (tag == QLatin1String("include")) {
472  readIncludeTag(element);
473  } else if (tag == QLatin1String("kcfgfile")) {
474  readKcfgfileTag(element);
475  } else if (tag == QLatin1String("group")) {
476  readGroupTag(element);
477  } else if (tag == QLatin1String("signal")) {
478  readSignalTag(element);
479  }
480  }
481 }
482 
483 ParseResult KConfigXmlParser::getParseResult() const
484 {
485  return mParseResult;
486 }
487 
488 void KConfigXmlParser::readIncludeTag(const QDomElement &e)
489 {
490  QString includeFile = e.text();
491  if (!includeFile.isEmpty()) {
492  mParseResult.includes.append(includeFile);
493  }
494 }
495 
496 void KConfigXmlParser::readGroupTag(const QDomElement &e)
497 {
498  QString group = e.attribute(QStringLiteral("name"));
499  if (group.isEmpty()) {
500  std::cerr << "Group without name" << std::endl;
501  exit(1);
502  }
503 
504  const QString parentGroup = e.attribute(QStringLiteral("parentGroupName"));
505 
506  for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) {
507  if (e2.tagName() != QLatin1String("entry")) {
508  continue;
509  }
510  CfgEntry *entry = parseEntry(group, parentGroup, e2);
511  if (entry) {
512  mParseResult.entries.append(entry);
513  } else {
514  std::cerr << "Can not parse entry." << std::endl;
515  exit(1);
516  }
517  }
518 }
519 
520 void KConfigXmlParser::readKcfgfileTag(const QDomElement &e)
521 {
522  mParseResult.cfgFileName = e.attribute(QStringLiteral("name"));
523  mParseResult.cfgStateConfig = e.attribute(QStringLiteral("stateConfig")).toLower() == QLatin1String("true");
524  mParseResult.cfgFileNameArg = e.attribute(QStringLiteral("arg")).toLower() == QLatin1String("true");
525  for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) {
526  if (e2.tagName() == QLatin1String("parameter")) {
527  Param p;
528  p.name = e2.attribute(QStringLiteral("name"));
529  p.type = e2.attribute(QStringLiteral("type"));
530  if (p.type.isEmpty()) {
531  p.type = QStringLiteral("String");
532  }
533  mParseResult.parameters.append(p);
534  }
535  }
536 }
537 
538 void KConfigXmlParser::readSignalTag(const QDomElement &e)
539 {
540  QString signalName = e.attribute(QStringLiteral("name"));
541  if (signalName.isEmpty()) {
542  std::cerr << "Signal without name." << std::endl;
543  exit(1);
544  }
545  Signal theSignal;
546  theSignal.name = signalName;
547 
548  for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) {
549  if (e2.tagName() == QLatin1String("argument")) {
550  Param argument;
551  argument.type = e2.attribute(QStringLiteral("type"));
552  if (argument.type.isEmpty()) {
553  std::cerr << "Signal argument without type." << std::endl;
554  exit(1);
555  }
556  argument.name = e2.text();
557  theSignal.arguments.append(argument);
558  } else if (e2.tagName() == QLatin1String("label")) {
559  theSignal.label = e2.text();
560  }
561  }
562 
563  mParseResult.signalList.append(theSignal);
564 }
void append(const T &value)
QString anchoredPattern(const QString &expression)
QString text() const const
Configuration Compiler Configuration.
QString tagName() const const
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool isNull() const const
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QString arg(Args &&... args) const const
Q_SCRIPTABLE Q_NOREPLY void start()
QString & prepend(QChar ch)
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
QString simplified() const const
bool isEmpty() const const
int length() const const
int toInt(bool *ok, int base) const const
QDomElement nextSiblingElement(const QString &tagName) const const
QString name(StandardAction id)
QDomElement documentElement() const const
QDomElement firstChildElement(const QString &tagName) const const
QString toLower() const const
QString left(int n) const const
KGuiItem defaults()
void save(QTextStream &stream, int indent, QDomNode::EncodingPolicy encodingPolicy) const const
QString attribute(const QString &name, const QString &defValue) const const
QString & append(QChar ch)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Thu Feb 15 2024 04:07:59 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.