File indexing completed on 2024-05-12 03:54:31
0001 /* 0002 This file is part of the KDE libraries 0003 0004 SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org> 0005 SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org> 0006 SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org> 0007 SPDX-FileCopyrightText: 2006 Michaƫl Larouche <michael.larouche@kdemail.net> 0008 SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org> 0009 SPDX-FileCopyrightText: 2020 Tomaz Cananbrava <tcanabrava@kde.org> 0010 0011 SPDX-License-Identifier: LGPL-2.0-or-later 0012 */ 0013 0014 #include "KConfigXmlParser.h" 0015 0016 #include <QDebug> 0017 #include <QDomAttr> 0018 #include <QDomElement> 0019 #include <QDomNode> 0020 #include <QFile> 0021 #include <QList> 0022 #include <QStringList> 0023 #include <QTextStream> 0024 #include <iostream> 0025 // TODO: Move preprocessDefault to Header / CPP implementation. 0026 // it makes no sense for a parser to process those values and generate code. 0027 0028 static void preProcessDefault(QString &defaultValue, 0029 const QString &name, 0030 const QString &type, 0031 const CfgEntry::Choices &cfgChoices, 0032 QString &code, 0033 const KConfigParameters &cfg) 0034 { 0035 if (type == QLatin1String("String") && !defaultValue.isEmpty()) { 0036 defaultValue = literalString(defaultValue); 0037 0038 } else if (type == QLatin1String("Path") && !defaultValue.isEmpty()) { 0039 defaultValue = literalString(defaultValue); 0040 } else if (type == QLatin1String("Url") && !defaultValue.isEmpty()) { 0041 // Use fromUserInput in order to support absolute paths and absolute urls, like KDE4's KUrl(QString) did. 0042 defaultValue = QLatin1String("QUrl::fromUserInput( %1)").arg(literalString(defaultValue)); 0043 } else if ((type == QLatin1String("UrlList") || type == QLatin1String("StringList") || type == QLatin1String("PathList")) && !defaultValue.isEmpty()) { 0044 QTextStream cpp(&code, QIODevice::WriteOnly | QIODevice::Append); 0045 if (!code.isEmpty()) { 0046 cpp << '\n'; 0047 } 0048 0049 if (type == QLatin1String("UrlList")) { 0050 cpp << " QList<QUrl> default" << name << ";\n"; 0051 } else { 0052 cpp << " QStringList default" << name << ";\n"; 0053 } 0054 const QStringList defaults = defaultValue.split(QLatin1Char(',')); 0055 for (const auto &val : defaults) { 0056 cpp << " default" << name << ".append( "; 0057 if (type == QLatin1String("UrlList")) { 0058 cpp << "QUrl::fromUserInput("; 0059 } 0060 cpp << "QString::fromUtf8( \"" << val << "\" ) "; 0061 if (type == QLatin1String("UrlList")) { 0062 cpp << ") "; 0063 } 0064 cpp << ");\n"; 0065 } 0066 defaultValue = QLatin1String("default") + name; 0067 0068 } else if (type == QLatin1String("Color") && !defaultValue.isEmpty()) { 0069 static const QRegularExpression colorRe(QRegularExpression::anchoredPattern(QStringLiteral("\\d+,\\s*\\d+,\\s*\\d+(,\\s*\\d+)?"))); 0070 0071 if (colorRe.match(defaultValue).hasMatch()) { 0072 defaultValue = QLatin1String("QColor( %1 )").arg(defaultValue); 0073 } else { 0074 defaultValue = QLatin1String("QColor( \"%1\" )").arg(defaultValue); 0075 } 0076 0077 } else if (type == QLatin1String("Enum")) { 0078 for (const auto &choice : cfgChoices.choices) { 0079 if (choice.name == defaultValue) { 0080 if (cfg.globalEnums && cfgChoices.name().isEmpty()) { 0081 defaultValue.prepend(cfgChoices.prefix); 0082 } else { 0083 defaultValue.prepend(enumTypeQualifier(name, cfgChoices) + cfgChoices.prefix); 0084 } 0085 break; 0086 } 0087 } 0088 0089 } else if (type == QLatin1String("IntList")) { 0090 QTextStream cpp(&code, QIODevice::WriteOnly | QIODevice::Append); 0091 if (!code.isEmpty()) { 0092 cpp << '\n'; 0093 } 0094 0095 cpp << " QList<int> default" << name << ";\n"; 0096 if (!defaultValue.isEmpty()) { 0097 const QStringList defaults = defaultValue.split(QLatin1Char(',')); 0098 for (const auto &defaultVal : defaults) { 0099 cpp << " default" << name << ".append( " << defaultVal << " );\n"; 0100 } 0101 } 0102 defaultValue = QLatin1String("default") + name; 0103 } 0104 } 0105 0106 static QString dumpNode(const QDomNode &node) 0107 { 0108 QString msg; 0109 QTextStream s(&msg, QIODevice::WriteOnly); 0110 node.save(s, 0); 0111 0112 msg = msg.simplified(); 0113 if (msg.length() > 40) { 0114 return msg.left(37) + QLatin1String("..."); 0115 } 0116 return msg; 0117 } 0118 0119 void KConfigXmlParser::readParameterFromEntry(CfgEntry &readEntry, const QDomElement &e) 0120 { 0121 readEntry.param = e.attribute(QStringLiteral("name")); 0122 readEntry.paramType = e.attribute(QStringLiteral("type")); 0123 0124 if (readEntry.param.isEmpty()) { 0125 std::cerr << "Parameter must have a name: " << qPrintable(dumpNode(e)) << std::endl; 0126 exit(1); 0127 } 0128 0129 if (readEntry.paramType.isEmpty()) { 0130 std::cerr << "Parameter must have a type: " << qPrintable(dumpNode(e)) << std::endl; 0131 exit(1); 0132 } 0133 0134 if ((readEntry.paramType == QLatin1String("Int")) || (readEntry.paramType == QLatin1String("UInt"))) { 0135 bool ok; 0136 readEntry.paramMax = e.attribute(QStringLiteral("max")).toInt(&ok); 0137 if (!ok) { 0138 std::cerr << "Integer parameter must have a maximum (e.g. max=\"0\"): " << qPrintable(dumpNode(e)) << std::endl; 0139 exit(1); 0140 } 0141 } else if (readEntry.paramType == QLatin1String("Enum")) { 0142 for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { 0143 if (e2.tagName() == QLatin1String("values")) { 0144 for (QDomElement e3 = e2.firstChildElement(); !e3.isNull(); e3 = e3.nextSiblingElement()) { 0145 if (e3.tagName() == QLatin1String("value")) { 0146 readEntry.paramValues.append(e3.text()); 0147 } 0148 } 0149 break; 0150 } 0151 } 0152 if (readEntry.paramValues.isEmpty()) { 0153 std::cerr << "No values specified for parameter '" << qPrintable(readEntry.param) << "'." << std::endl; 0154 exit(1); 0155 } 0156 readEntry.paramMax = readEntry.paramValues.count() - 1; 0157 } else { 0158 std::cerr << "Parameter '" << qPrintable(readEntry.param) << "' has type " << qPrintable(readEntry.paramType) 0159 << " but must be of type int, uint or Enum." << std::endl; 0160 exit(1); 0161 } 0162 } 0163 0164 bool KConfigXmlParser::hasDefaultCode(CfgEntry &readEntry, const QDomElement &element) 0165 { 0166 Q_UNUSED(readEntry) 0167 0168 for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { 0169 if (e.attribute(QStringLiteral("param")).isEmpty()) { 0170 if (e.attribute(QStringLiteral("code")) == QLatin1String("true")) { 0171 return true; 0172 } 0173 } 0174 } 0175 return false; 0176 } 0177 0178 void KConfigXmlParser::readChoicesFromEntry(CfgEntry &readEntry, const QDomElement &e) 0179 { 0180 QList<CfgEntry::Choice> chlist; 0181 const auto choiceNameRegex = QRegularExpression(QStringLiteral("\\w+")); 0182 0183 for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { 0184 if (e2.tagName() != QLatin1String("choice")) { 0185 continue; 0186 } 0187 CfgEntry::Choice choice; 0188 choice.name = e2.attribute(QStringLiteral("name")); 0189 if (choice.name.isEmpty()) { 0190 std::cerr << "Tag <choice> requires attribute 'name'." << std::endl; 0191 } else if (!choiceNameRegex.match(choice.name).hasMatch()) { 0192 std::cerr << "Tag <choice> attribute 'name' must be compatible with Enum naming. name was '" << qPrintable(choice.name) 0193 << "'. You can use attribute 'value' to pass any string as the choice value." << std::endl; 0194 } 0195 choice.val = e2.attribute(QStringLiteral("value")); 0196 for (QDomElement e3 = e2.firstChildElement(); !e3.isNull(); e3 = e3.nextSiblingElement()) { 0197 if (e3.tagName() == QLatin1String("label")) { 0198 choice.label = e3.text(); 0199 choice.context = e3.attribute(QStringLiteral("context")); 0200 } 0201 if (e3.tagName() == QLatin1String("tooltip")) { 0202 choice.toolTip = e3.text(); 0203 choice.context = e3.attribute(QStringLiteral("context")); 0204 } 0205 if (e3.tagName() == QLatin1String("whatsthis")) { 0206 choice.whatsThis = e3.text(); 0207 choice.context = e3.attribute(QStringLiteral("context")); 0208 } 0209 } 0210 chlist.append(choice); 0211 } 0212 0213 QString name = e.attribute(QStringLiteral("name")); 0214 QString prefix = e.attribute(QStringLiteral("prefix")); 0215 0216 readEntry.choices = CfgEntry::Choices(chlist, name, prefix); 0217 } 0218 0219 void KConfigXmlParser::readGroupElements(CfgEntry &readEntry, const QDomElement &element) 0220 { 0221 for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { 0222 QString tag = e.tagName(); 0223 if (tag == QLatin1String("label")) { 0224 readEntry.label = e.text(); 0225 readEntry.labelContext = e.attribute(QStringLiteral("context")); 0226 } else if (tag == QLatin1String("tooltip")) { 0227 readEntry.toolTip = e.text(); 0228 readEntry.toolTipContext = e.attribute(QStringLiteral("context")); 0229 } else if (tag == QLatin1String("whatsthis")) { 0230 readEntry.whatsThis = e.text(); 0231 readEntry.whatsThisContext = e.attribute(QStringLiteral("context")); 0232 } else if (tag == QLatin1String("min")) { 0233 readEntry.min = e.text(); 0234 } else if (tag == QLatin1String("max")) { 0235 readEntry.max = e.text(); 0236 } else if (tag == QLatin1String("code")) { 0237 readEntry.code = e.text(); 0238 } else if (tag == QLatin1String("parameter")) { 0239 readParameterFromEntry(readEntry, e); 0240 } else if (tag == QLatin1String("default")) { 0241 if (e.attribute(QStringLiteral("param")).isEmpty()) { 0242 readEntry.defaultValue = e.text(); 0243 } 0244 } else if (tag == QLatin1String("choices")) { 0245 readChoicesFromEntry(readEntry, e); 0246 } else if (tag == QLatin1String("emit")) { 0247 Signal signal; 0248 signal.name = e.attribute(QStringLiteral("signal")); 0249 readEntry.signalList.append(signal); 0250 } 0251 } 0252 } 0253 0254 void KConfigXmlParser::createChangedSignal(CfgEntry &readEntry) 0255 { 0256 if (cfg.generateProperties && (cfg.allMutators || cfg.mutators.contains(readEntry.name))) { 0257 Signal s; 0258 s.name = changeSignalName(readEntry.name); 0259 s.modify = true; 0260 readEntry.signalList.append(s); 0261 } 0262 } 0263 0264 void KConfigXmlParser::validateNameAndKey(CfgEntry &readEntry, const QDomElement &element) 0265 { 0266 bool nameIsEmpty = readEntry.name.isEmpty(); 0267 if (nameIsEmpty && readEntry.key.isEmpty()) { 0268 std::cerr << "Entry must have a name or a key: " << qPrintable(dumpNode(element)) << std::endl; 0269 exit(1); 0270 } 0271 0272 if (readEntry.key.isEmpty()) { 0273 readEntry.key = readEntry.name; 0274 } 0275 0276 if (nameIsEmpty) { 0277 readEntry.name = readEntry.key; 0278 readEntry.name.remove(QLatin1Char(' ')); 0279 } else if (readEntry.name.contains(QLatin1Char(' '))) { 0280 std::cout << "Entry '" << qPrintable(readEntry.name) << "' contains spaces! <name> elements can not contain spaces!" << std::endl; 0281 readEntry.name.remove(QLatin1Char(' ')); 0282 } 0283 0284 if (readEntry.name.contains(QStringLiteral("$("))) { 0285 if (readEntry.param.isEmpty()) { 0286 std::cerr << "Name may not be parameterized: " << qPrintable(readEntry.name) << std::endl; 0287 exit(1); 0288 } 0289 } else { 0290 if (!readEntry.param.isEmpty()) { 0291 std::cerr << "Name must contain '$(" << qPrintable(readEntry.param) << ")': " << qPrintable(readEntry.name) << std::endl; 0292 exit(1); 0293 } 0294 } 0295 } 0296 0297 void KConfigXmlParser::readParamDefaultValues(CfgEntry &readEntry, const QDomElement &element) 0298 { 0299 if (readEntry.param.isEmpty()) { 0300 return; 0301 } 0302 // Adjust name 0303 readEntry.paramName = readEntry.name; 0304 0305 readEntry.name.remove(QStringLiteral("$(") + readEntry.param + QLatin1Char(')')); 0306 // Lookup defaults for indexed entries 0307 for (int i = 0; i <= readEntry.paramMax; i++) { 0308 readEntry.paramDefaultValues.append(QString()); 0309 } 0310 0311 for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { 0312 QString tag = e.tagName(); 0313 if (tag != QLatin1String("default")) { 0314 continue; 0315 } 0316 QString index = e.attribute(QStringLiteral("param")); 0317 if (index.isEmpty()) { 0318 continue; 0319 } 0320 0321 bool ok; 0322 int i = index.toInt(&ok); 0323 if (!ok) { 0324 i = readEntry.paramValues.indexOf(index); 0325 if (i == -1) { 0326 std::cerr << "Index '" << qPrintable(index) << "' for default value is unknown." << std::endl; 0327 exit(1); 0328 } 0329 } 0330 0331 if ((i < 0) || (i > readEntry.paramMax)) { 0332 std::cerr << "Index '" << i << "' for default value is out of range [0, " << readEntry.paramMax << "]." << std::endl; 0333 exit(1); 0334 } 0335 0336 QString tmpDefaultValue = e.text(); 0337 0338 if (e.attribute(QStringLiteral("code")) != QLatin1String("true")) { 0339 preProcessDefault(tmpDefaultValue, readEntry.name, readEntry.type, readEntry.choices, readEntry.code, cfg); 0340 } 0341 0342 readEntry.paramDefaultValues[i] = tmpDefaultValue; 0343 } 0344 } 0345 0346 CfgEntry *KConfigXmlParser::parseEntry(const QString &group, const QString &parentGroup, const QDomElement &element) 0347 { 0348 CfgEntry readEntry; 0349 readEntry.type = element.attribute(QStringLiteral("type")); 0350 readEntry.name = element.attribute(QStringLiteral("name")); 0351 readEntry.key = element.attribute(QStringLiteral("key")); 0352 readEntry.hidden = element.attribute(QStringLiteral("hidden")) == QLatin1String("true"); 0353 ; 0354 readEntry.group = group; 0355 readEntry.parentGroup = parentGroup; 0356 0357 const bool nameIsEmpty = readEntry.name.isEmpty(); 0358 0359 readGroupElements(readEntry, element); 0360 0361 validateNameAndKey(readEntry, element); 0362 0363 if (readEntry.label.isEmpty()) { 0364 readEntry.label = readEntry.key; 0365 } 0366 0367 if (readEntry.type.isEmpty()) { 0368 readEntry.type = QStringLiteral("String"); // XXX : implicit type might be bad 0369 } 0370 0371 readParamDefaultValues(readEntry, element); 0372 0373 if (!mValidNameRegexp.match(readEntry.name).hasMatch()) { 0374 if (nameIsEmpty) { 0375 std::cerr << "The key '" << qPrintable(readEntry.key) 0376 << "' can not be used as name for the entry because " 0377 "it is not a valid name. You need to specify a valid name for this entry." 0378 << std::endl; 0379 } else { 0380 std::cerr << "The name '" << qPrintable(readEntry.name) << "' is not a valid name for an entry." << std::endl; 0381 } 0382 exit(1); 0383 } 0384 0385 if (mAllNames.contains(readEntry.name)) { 0386 if (nameIsEmpty) { 0387 std::cerr << "The key '" << qPrintable(readEntry.key) 0388 << "' can not be used as name for the entry because " 0389 "it does not result in a unique name. You need to specify a unique name for this entry." 0390 << std::endl; 0391 } else { 0392 std::cerr << "The name '" << qPrintable(readEntry.name) << "' is not unique." << std::endl; 0393 } 0394 exit(1); 0395 } 0396 0397 mAllNames.append(readEntry.name); 0398 0399 if (!hasDefaultCode(readEntry, element)) { 0400 // TODO: Move all the options to CfgEntry. 0401 preProcessDefault(readEntry.defaultValue, readEntry.name, readEntry.type, readEntry.choices, readEntry.code, cfg); 0402 } 0403 0404 // TODO: Try to Just return the CfgEntry we populated instead of 0405 // creating another one to fill the code. 0406 CfgEntry *result = new CfgEntry(); 0407 result->group = readEntry.group; 0408 result->parentGroup = readEntry.parentGroup; 0409 result->type = readEntry.type; 0410 result->key = readEntry.key; 0411 result->name = readEntry.name; 0412 result->labelContext = readEntry.labelContext; 0413 result->label = readEntry.label; 0414 result->toolTipContext = readEntry.toolTipContext; 0415 result->toolTip = readEntry.toolTip; 0416 result->whatsThisContext = readEntry.whatsThisContext; 0417 result->whatsThis = readEntry.whatsThis; 0418 result->code = readEntry.code; 0419 result->defaultValue = readEntry.defaultValue; 0420 result->choices = readEntry.choices; 0421 result->signalList = readEntry.signalList; 0422 result->hidden = readEntry.hidden; 0423 0424 if (!readEntry.param.isEmpty()) { 0425 result->param = readEntry.param; 0426 result->paramName = readEntry.paramName; 0427 result->paramType = readEntry.paramType; 0428 result->paramValues = readEntry.paramValues; 0429 result->paramDefaultValues = readEntry.paramDefaultValues; 0430 result->paramMax = readEntry.paramMax; 0431 } 0432 result->min = readEntry.min; 0433 result->max = readEntry.max; 0434 createChangedSignal(*result); 0435 0436 return result; 0437 } 0438 0439 // TODO: Change the name of the config variable. 0440 KConfigXmlParser::KConfigXmlParser(const KConfigParameters &cfg, const QString &inputFileName) 0441 : cfg(cfg) 0442 , mInputFileName(inputFileName) 0443 { 0444 mValidNameRegexp.setPattern(QRegularExpression::anchoredPattern(QStringLiteral("[a-zA-Z_][a-zA-Z0-9_]*"))); 0445 } 0446 0447 void KConfigXmlParser::start() 0448 { 0449 QFile input(mInputFileName); 0450 if (!input.open(QIODevice::ReadOnly)) { 0451 qFatal("Could not open input file: %s", qUtf8Printable(mInputFileName)); 0452 } 0453 QDomDocument doc; 0454 const QDomDocument::ParseResult parseResult = doc.setContent(&input); 0455 if (!parseResult) { 0456 std::cerr << "Unable to load document." << std::endl; 0457 std::cerr << "Parse error in " << qPrintable(mInputFileName) << ", line " << parseResult.errorLine << ", col " << parseResult.errorColumn << ": " 0458 << qPrintable(parseResult.errorMessage) << std::endl; 0459 exit(1); 0460 } 0461 0462 QDomElement cfgElement = doc.documentElement(); 0463 if (cfgElement.isNull()) { 0464 std::cerr << "No document in kcfg file" << std::endl; 0465 exit(1); 0466 } 0467 0468 for (QDomElement element = cfgElement.firstChildElement(); !element.isNull(); element = element.nextSiblingElement()) { 0469 QString tag = element.tagName(); 0470 0471 if (tag == QLatin1String("include")) { 0472 readIncludeTag(element); 0473 } else if (tag == QLatin1String("kcfgfile")) { 0474 readKcfgfileTag(element); 0475 } else if (tag == QLatin1String("group")) { 0476 readGroupTag(element); 0477 } else if (tag == QLatin1String("signal")) { 0478 readSignalTag(element); 0479 } 0480 } 0481 } 0482 0483 ParseResult KConfigXmlParser::getParseResult() const 0484 { 0485 return mParseResult; 0486 } 0487 0488 void KConfigXmlParser::readIncludeTag(const QDomElement &e) 0489 { 0490 QString includeFile = e.text(); 0491 if (!includeFile.isEmpty()) { 0492 mParseResult.includes.append(includeFile); 0493 } 0494 } 0495 0496 void KConfigXmlParser::readGroupTag(const QDomElement &e) 0497 { 0498 QString group = e.attribute(QStringLiteral("name")); 0499 if (group.isEmpty()) { 0500 std::cerr << "Group without name" << std::endl; 0501 exit(1); 0502 } 0503 0504 const QString parentGroup = e.attribute(QStringLiteral("parentGroupName")); 0505 0506 for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { 0507 if (e2.tagName() != QLatin1String("entry")) { 0508 continue; 0509 } 0510 CfgEntry *entry = parseEntry(group, parentGroup, e2); 0511 if (entry) { 0512 mParseResult.entries.append(entry); 0513 } else { 0514 std::cerr << "Can not parse entry." << std::endl; 0515 exit(1); 0516 } 0517 } 0518 } 0519 0520 void KConfigXmlParser::readKcfgfileTag(const QDomElement &e) 0521 { 0522 mParseResult.cfgFileName = e.attribute(QStringLiteral("name")); 0523 mParseResult.cfgStateConfig = e.attribute(QStringLiteral("stateConfig")).toLower() == QLatin1String("true"); 0524 mParseResult.cfgFileNameArg = e.attribute(QStringLiteral("arg")).toLower() == QLatin1String("true"); 0525 for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { 0526 if (e2.tagName() == QLatin1String("parameter")) { 0527 Param p; 0528 p.name = e2.attribute(QStringLiteral("name")); 0529 p.type = e2.attribute(QStringLiteral("type")); 0530 if (p.type.isEmpty()) { 0531 p.type = QStringLiteral("String"); 0532 } 0533 mParseResult.parameters.append(p); 0534 } 0535 } 0536 } 0537 0538 void KConfigXmlParser::readSignalTag(const QDomElement &e) 0539 { 0540 QString signalName = e.attribute(QStringLiteral("name")); 0541 if (signalName.isEmpty()) { 0542 std::cerr << "Signal without name." << std::endl; 0543 exit(1); 0544 } 0545 Signal theSignal; 0546 theSignal.name = signalName; 0547 0548 for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { 0549 if (e2.tagName() == QLatin1String("argument")) { 0550 Param argument; 0551 argument.type = e2.attribute(QStringLiteral("type")); 0552 if (argument.type.isEmpty()) { 0553 std::cerr << "Signal argument without type." << std::endl; 0554 exit(1); 0555 } 0556 argument.name = e2.text(); 0557 theSignal.arguments.append(argument); 0558 } else if (e2.tagName() == QLatin1String("label")) { 0559 theSignal.label = e2.text(); 0560 } 0561 } 0562 0563 mParseResult.signalList.append(theSignal); 0564 }