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 }