Revision control

Copy as Markdown

// 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;
}