Revision control
++ // Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "nodeidgenerator.h"
++ #include "recursivedescentparser.h"
++
++ #include <QtCore/qcommandlineoption.h>
++ #include <QtCore/qcommandlineparser.h>
++ #include <QtCore/qdebug.h>
++ #include <QtCore/qfile.h>
++ #include <QtCore/qregularexpression.h>
++ #include <QtCore/qstring.h>
++ #include <QtCore/qstringlist.h>
++ #include <QtCore/qtextstream.h>
++ #include <QtCore/qxmlstream.h>
++
++ #include <cstdlib>
++
++ using namespace Qt::Literals::StringLiterals;
++
++ bool readBsdFile(RecursiveDescentParser &recursiveDescentParser,
++ const QString &fileName,
++ bool dependencyInput)
++ {
++ switch (recursiveDescentParser.parseFile(fileName, dependencyInput)) {
++ case RecursiveDescentParser::NoError:
++ return true;
++ case RecursiveDescentParser::InvalidFileName:
++ qCritical() << "Error: File does not exist:" << fileName;
++ return false;
++ case RecursiveDescentParser::InvalidTypeDictionaryEntry:
++ qCritical() << "Error: Invalid TypeDictionary entry in" << fileName;
++ return false;
++ case RecursiveDescentParser::InvalidStructuredTypeEntry:
++ qCritical() << "Error: Invalid StructuredType entry in" << fileName;
++ return false;
++ case RecursiveDescentParser::InvalidEnumeratedTypeEntry:
++ qCritical() << "Error: Invalid EnumeratedType entry in" << fileName;
++ return false;
++ case RecursiveDescentParser::InvalidImportEntry:
++ qCritical() << "Error: Invalid Import entry in" << fileName;
++ return false;
++ case RecursiveDescentParser::InvalidFieldEntry:
++ qCritical() << "Error: Invalid Field entry in" << fileName;
++ return false;
++ case RecursiveDescentParser::InvalidEnumeratedValueEntry:
++ qCritical() << "Error: Invalid EnumeratedValue entry in" << fileName;
++ return false;
++ case RecursiveDescentParser::CannotFullyGenerateNamespaceZero:
++ qCritical() << "Error: Full generation of namespace 0 is currently not "
++ "supported";
++ return false;
++ case RecursiveDescentParser::MissingDependency:
++ qCritical() << "Error: Missing dependency Type found in" << fileName;
++ return false;
++ default:
++ qCritical() << "Error: Unknown parsing error occurred";
++ return false;
++ }
++ }
++
++ bool generateBsdFiles(RecursiveDescentParser &recursiveDescentParser,
++ const QString &outputPath,
++ const QString &dataPrefix,
++ const QString &outputFileHeader,
++ bool generateBundleFiles)
++ {
++ switch (recursiveDescentParser.generateInputFiles(outputPath,
++ dataPrefix,
++ outputFileHeader,
++ generateBundleFiles)) {
++ case RecursiveDescentParser::NoError:
++ return true;
++ case RecursiveDescentParser::UnableToWriteFile:
++ qCritical() << "Error: Unable to write files at specified path.";
++ return false;
++ case RecursiveDescentParser::MissingDependency:
++ qCritical() << "Error: Unresolved dependent type occurred.";
++ return false;
++ case RecursiveDescentParser::UnableToResolveDependency:
++ qCritical() << "Error: Unresolvable mapping occurred.";
++ return false;
++ default:
++ qCritical() << "Error: Unknown file generating error occurred.";
++ return false;
++ }
++ }
++
++ int main(int argc, char *argv[])
++ {
++ QCoreApplication a(argc, argv);
++
++ const QString appName = u"qopcuaxmldatatypes2cpp"_s;
++ const QString appVersion = u"1.1"_s;
++
++ auto arguments = a.arguments();
++ arguments.replace(0, appName);
++
++ const auto outputFileHeader = u"/*\n"
++ " * This file was generated by %1 version %2\n"
++ " * Command line used: %3\n"
++ " */"_s
++ .arg(appName,
++ appVersion,
++ arguments.join(QLatin1Char(' ')));
++
++ QCoreApplication::setApplicationName(appName);
++ QCoreApplication::setApplicationVersion(appVersion);
++ QCommandLineParser parser;
++ parser.setApplicationDescription(
++ u"Code generator for custom data models.\n"
++ "Converts OPC UA .bsd files into enums and C++ data classes and generates a class "
++ "to decode and encode the values from/to a QOpcUaExtensionObject with binary body or a QByteArray."_s);
++ parser.addHelpOption();
++ parser.addVersionOption();
++
++ const QCommandLineOption inputFileOption(QStringList() << u"i"_s
++ << u"input"_s,
++ u"A primary input file. Will generate code for all contained types and "
++ "check for missing dependencies"_s,
++ u"file"_s);
++ parser.addOption(inputFileOption);
++ const QCommandLineOption nodeIdFileOption(QStringList() << u"n"_s
++ << u"nodeids"_s,
++ u"A CSV file with NodeIds. Will add an enum class <name>NodeId to the <prefix>nodeids.h file"_s,
++ u"name:path"_s);
++ parser.addOption(nodeIdFileOption);
++ const QCommandLineOption inputDependencyFileOption(
++ QStringList() << u"d"_s
++ << u"dependencyinput"_s,
++ u"A dependency input file. Only types required by primary input files will be generated"_s,
++ u"file"_s);
++ parser.addOption(inputDependencyFileOption);
++ const QCommandLineOption outputDirectoryPathOption(QStringList() << u"o"_s
++ << u"output"_s,
++ u"output directory for the generated C++ files."_s,
++ u"path"_s);
++ parser.addOption(outputDirectoryPathOption);
++ const QCommandLineOption
++ outputPrefixOption(QStringList() << u"p"_s
++ << u"prefix"_s,
++ u"prefix for the generated files, default is GeneratedOpcUa"_s,
++ u"prefix"_s,
++ u"GeneratedOpcUa"_s);
++ parser.addOption(outputPrefixOption);
++ const QCommandLineOption bundleFilesOption(QStringList() << u"b"_s << u"bundle"_s,
++ u"Create bundle .h and .cpp file"_s);
++ parser.addOption(bundleFilesOption);
++
++ parser.process(a);
++
++ if (!parser.isSet(inputFileOption) && !parser.isSet(nodeIdFileOption)) {
++ qCritical() << "Error: At least one bsd or csv input file must be specified";
++ parser.showHelp(1);
++ return EXIT_FAILURE;
++ }
++
++ if (!parser.isSet(outputDirectoryPathOption)) {
++ qCritical() << "Error: The output path must be specified.";
++ parser.showHelp(1);
++ return EXIT_FAILURE;
++ }
++
++ if (parser.values(outputPrefixOption).size() > 1)
++ qInfo() << "Info: The first output prefix will be used";
++ if (!parser.values(outputPrefixOption)
++ .at(0)
++ .contains(QRegularExpression(u"^[A-Za-z]+[A-Za-z0-9]*$"_s))) {
++ qCritical() << "Error: The prefix contains illegal characters";
++ qInfo() << "Info: The prefix must consist of letters and numbers and start with a letter";
++ return EXIT_FAILURE;
++ }
++
++ const auto dataPrefix = parser.value(outputPrefixOption);
++
++ NodeIdGenerator nodeIdGen;
++
++ if (parser.isSet(nodeIdFileOption)) {
++ const auto nodeIdEntries = parser.values(nodeIdFileOption);
++
++ for (const auto &entry : nodeIdEntries) {
++ const auto index = entry.indexOf(':'_L1);
++ if (index == -1 || index == 0 || entry.size() <= index + 1) {
++ qWarning() << "Invalid value:" << entry << "- NodeId entries must be given as name:filepath";
++ return EXIT_FAILURE;
++ }
++
++ const auto success = nodeIdGen.parseNodeIds(entry.first(index), entry.mid(index + 1));
++ if (!success)
++ return EXIT_FAILURE;
++ }
++ }
++
++ if (!parser.isSet(inputFileOption) && !nodeIdGen.hasNodeIds())
++ return EXIT_FAILURE;
++
++ if (nodeIdGen.hasNodeIds()) {
++ const auto success = nodeIdGen.generateNodeIdsHeader(dataPrefix, parser.value(outputDirectoryPathOption), outputFileHeader);
++ if (success)
++ qInfo() << "All node ids were successfully generated";
++
++ if (!parser.isSet(inputFileOption))
++ return success ? EXIT_SUCCESS : EXIT_FAILURE;
++ }
++
++ auto success = true;
++ RecursiveDescentParser recursiveDescentParser;
++ const QStringList inputFileNames = parser.values(inputFileOption);
++ for (const auto &fileName : inputFileNames)
++ success &= readBsdFile(recursiveDescentParser, fileName, false);
++ const QStringList dependencyInputFileNames = parser.values(inputDependencyFileOption);
++ for (const auto &fileName : dependencyInputFileNames)
++ success &= readBsdFile(recursiveDescentParser, fileName, true);
++ if (success) {
++ const auto outputPath = parser.value(outputDirectoryPathOption);
++ if (generateBsdFiles(recursiveDescentParser,
++ outputPath,
++ dataPrefix,
++ outputFileHeader,
++ parser.isSet(bundleFilesOption))) {
++
++ qInfo() << "Info: All types were successfully generated";
++ return EXIT_SUCCESS;
++ }
++ }
++ return EXIT_FAILURE;
++ }