1// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "nodeidgenerator.h"
5#include "recursivedescentparser.h"
6
7#include <QtCore/qcommandlineoption.h>
8#include <QtCore/qcommandlineparser.h>
9#include <QtCore/qdebug.h>
10#include <QtCore/qfile.h>
11#include <QtCore/qregularexpression.h>
12#include <QtCore/qstring.h>
13#include <QtCore/qstringlist.h>
14#include <QtCore/qtextstream.h>
15#include <QtCore/qxmlstream.h>
16
17#include <cstdlib>
18
19using namespace Qt::Literals::StringLiterals;
20
21bool readBsdFile(RecursiveDescentParser &recursiveDescentParser,
22 const QString &fileName,
23 bool dependencyInput)
24{
25 switch (recursiveDescentParser.parseFile(fileName, dependencyTypeDictionary: dependencyInput)) {
26 case RecursiveDescentParser::NoError:
27 return true;
28 case RecursiveDescentParser::InvalidFileName:
29 qCritical() << "Error: File does not exist:" << fileName;
30 return false;
31 case RecursiveDescentParser::InvalidTypeDictionaryEntry:
32 qCritical() << "Error: Invalid TypeDictionary entry in" << fileName;
33 return false;
34 case RecursiveDescentParser::InvalidStructuredTypeEntry:
35 qCritical() << "Error: Invalid StructuredType entry in" << fileName;
36 return false;
37 case RecursiveDescentParser::InvalidEnumeratedTypeEntry:
38 qCritical() << "Error: Invalid EnumeratedType entry in" << fileName;
39 return false;
40 case RecursiveDescentParser::InvalidImportEntry:
41 qCritical() << "Error: Invalid Import entry in" << fileName;
42 return false;
43 case RecursiveDescentParser::InvalidFieldEntry:
44 qCritical() << "Error: Invalid Field entry in" << fileName;
45 return false;
46 case RecursiveDescentParser::InvalidEnumeratedValueEntry:
47 qCritical() << "Error: Invalid EnumeratedValue entry in" << fileName;
48 return false;
49 case RecursiveDescentParser::CannotFullyGenerateNamespaceZero:
50 qCritical() << "Error: Full generation of namespace 0 is currently not "
51 "supported";
52 return false;
53 case RecursiveDescentParser::MissingDependency:
54 qCritical() << "Error: Missing dependency Type found in" << fileName;
55 return false;
56 default:
57 qCritical() << "Error: Unknown parsing error occurred";
58 return false;
59 }
60}
61
62bool generateBsdFiles(RecursiveDescentParser &recursiveDescentParser,
63 const QString &outputPath,
64 const QString &dataPrefix,
65 const QString &outputFileHeader,
66 bool generateBundleFiles)
67{
68 switch (recursiveDescentParser.generateInputFiles(path: outputPath,
69 prefix: dataPrefix,
70 header: outputFileHeader,
71 generateBundleFiles)) {
72 case RecursiveDescentParser::NoError:
73 return true;
74 case RecursiveDescentParser::UnableToWriteFile:
75 qCritical() << "Error: Unable to write files at specified path.";
76 return false;
77 case RecursiveDescentParser::MissingDependency:
78 qCritical() << "Error: Unresolved dependent type occurred.";
79 return false;
80 case RecursiveDescentParser::UnableToResolveDependency:
81 qCritical() << "Error: Unresolvable mapping occurred.";
82 return false;
83 default:
84 qCritical() << "Error: Unknown file generating error occurred.";
85 return false;
86 }
87}
88
89int main(int argc, char *argv[])
90{
91 QCoreApplication a(argc, argv);
92
93 const QString appName = u"qopcuaxmldatatypes2cpp"_s;
94 const QString appVersion = u"1.1"_s;
95
96 auto arguments = a.arguments();
97 arguments.replace(i: 0, t: appName);
98
99 const auto outputFileHeader = u"/*\n"
100 " * This file was generated by %1 version %2\n"
101 " * Command line used: %3\n"
102 " */"_s
103 .arg(args: appName,
104 args: appVersion,
105 args: arguments.join(sep: QLatin1Char(' ')));
106
107 QCoreApplication::setApplicationName(appName);
108 QCoreApplication::setApplicationVersion(appVersion);
109 QCommandLineParser parser;
110 parser.setApplicationDescription(
111 u"Code generator for custom data models.\n"
112 "Converts OPC UA .bsd files into enums and C++ data classes and generates a class "
113 "to decode and encode the values from/to a QOpcUaExtensionObject with binary body or a QByteArray."_s);
114 parser.addHelpOption();
115 parser.addVersionOption();
116
117 const QCommandLineOption inputFileOption(QStringList() << u"i"_s
118 << u"input"_s,
119 u"A primary input file. Will generate code for all contained types and "
120 "check for missing dependencies"_s,
121 u"file"_s);
122 parser.addOption(commandLineOption: inputFileOption);
123 const QCommandLineOption nodeIdFileOption(QStringList() << u"n"_s
124 << u"nodeids"_s,
125 u"A CSV file with NodeIds. Will add an enum class <name>NodeId to the <prefix>nodeids.h file"_s,
126 u"name:path"_s);
127 parser.addOption(commandLineOption: nodeIdFileOption);
128 const QCommandLineOption inputDependencyFileOption(
129 QStringList() << u"d"_s
130 << u"dependencyinput"_s,
131 u"A dependency input file. Only types required by primary input files will be generated"_s,
132 u"file"_s);
133 parser.addOption(commandLineOption: inputDependencyFileOption);
134 const QCommandLineOption outputDirectoryPathOption(QStringList() << u"o"_s
135 << u"output"_s,
136 u"output directory for the generated C++ files."_s,
137 u"path"_s);
138 parser.addOption(commandLineOption: outputDirectoryPathOption);
139 const QCommandLineOption
140 outputPrefixOption(QStringList() << u"p"_s
141 << u"prefix"_s,
142 u"prefix for the generated files, default is GeneratedOpcUa"_s,
143 u"prefix"_s,
144 u"GeneratedOpcUa"_s);
145 parser.addOption(commandLineOption: outputPrefixOption);
146 const QCommandLineOption bundleFilesOption(QStringList() << u"b"_s << u"bundle"_s,
147 u"Create bundle .h and .cpp file"_s);
148 parser.addOption(commandLineOption: bundleFilesOption);
149
150 parser.process(app: a);
151
152 if (!parser.isSet(option: inputFileOption) && !parser.isSet(option: nodeIdFileOption)) {
153 qCritical() << "Error: At least one bsd or csv input file must be specified";
154 parser.showHelp(exitCode: 1);
155 return EXIT_FAILURE;
156 }
157
158 if (!parser.isSet(option: outputDirectoryPathOption)) {
159 qCritical() << "Error: The output path must be specified.";
160 parser.showHelp(exitCode: 1);
161 return EXIT_FAILURE;
162 }
163
164 if (parser.values(option: outputPrefixOption).size() > 1)
165 qInfo() << "Info: The first output prefix will be used";
166 if (!parser.values(option: outputPrefixOption)
167 .at(i: 0)
168 .contains(re: QRegularExpression(u"^[A-Za-z]+[A-Za-z0-9]*$"_s))) {
169 qCritical() << "Error: The prefix contains illegal characters";
170 qInfo() << "Info: The prefix must consist of letters and numbers and start with a letter";
171 return EXIT_FAILURE;
172 }
173
174 const auto dataPrefix = parser.value(option: outputPrefixOption);
175
176 NodeIdGenerator nodeIdGen;
177
178 if (parser.isSet(option: nodeIdFileOption)) {
179 const auto nodeIdEntries = parser.values(option: nodeIdFileOption);
180
181 for (const auto &entry : nodeIdEntries) {
182 const auto index = entry.indexOf(ch: QChar::fromLatin1(c: ':'));
183 if (index == -1 || index == 0 || entry.size() <= index + 1) {
184 qWarning() << "Invalid value:" << entry << "- NodeId entries must be given as name:filepath";
185 return EXIT_FAILURE;
186 }
187
188 const auto success = nodeIdGen.parseNodeIds(name: entry.first(n: index), path: entry.mid(position: index + 1));
189 if (!success)
190 return EXIT_FAILURE;
191 }
192 }
193
194 if (!parser.isSet(option: inputFileOption) && !nodeIdGen.hasNodeIds())
195 return EXIT_FAILURE;
196
197 if (nodeIdGen.hasNodeIds()) {
198 const auto success = nodeIdGen.generateNodeIdsHeader(prefix: dataPrefix, path: parser.value(option: outputDirectoryPathOption), header: outputFileHeader);
199 if (success)
200 qInfo() << "All node ids were successfully generated";
201
202 if (!parser.isSet(option: inputFileOption))
203 return success ? EXIT_SUCCESS : EXIT_FAILURE;
204 }
205
206 auto success = true;
207 RecursiveDescentParser recursiveDescentParser;
208 const QStringList inputFileNames = parser.values(option: inputFileOption);
209 for (const auto &fileName : inputFileNames)
210 success &= readBsdFile(recursiveDescentParser, fileName, dependencyInput: false);
211 const QStringList dependencyInputFileNames = parser.values(option: inputDependencyFileOption);
212 for (const auto &fileName : dependencyInputFileNames)
213 success &= readBsdFile(recursiveDescentParser, fileName, dependencyInput: true);
214 if (success) {
215 const auto outputPath = parser.value(option: outputDirectoryPathOption);
216 if (generateBsdFiles(recursiveDescentParser,
217 outputPath,
218 dataPrefix,
219 outputFileHeader,
220 generateBundleFiles: parser.isSet(option: bundleFilesOption))) {
221
222 qInfo() << "Info: All types were successfully generated";
223 return EXIT_SUCCESS;
224 }
225 }
226 return EXIT_FAILURE;
227}
228

source code of qtopcua/tools/datatypecodegenerator/main.cpp